Add 'Select Sessions" tab to agenda pages. Commit ready for merge.
- Legacy-Id: 19183
This commit is contained in:
parent
044293b4a9
commit
82ad0402f8
|
@ -166,6 +166,16 @@ def get_schedule_by_name(meeting, owner, name):
|
|||
return meeting.schedule_set.filter(name = name).first()
|
||||
|
||||
def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefetches=()):
|
||||
"""Add computed properties to assignments
|
||||
|
||||
For each assignment a, adds
|
||||
a.start_timestamp
|
||||
a.end_timestamp
|
||||
a.session.historic_group
|
||||
a.session.historic_parent
|
||||
a.session.rescheduled_to (if rescheduled)
|
||||
a.session.prefetched_active_materials
|
||||
"""
|
||||
assignments_queryset = assignments_queryset.prefetch_related(
|
||||
'timeslot', 'timeslot__type', 'timeslot__meeting',
|
||||
'timeslot__location', 'timeslot__location__floorplan', 'timeslot__location__urlresource_set',
|
||||
|
@ -260,9 +270,9 @@ def filter_keywords_for_session(session):
|
|||
if group.state_id == 'bof':
|
||||
keywords.add('bof')
|
||||
keywords.add(group.acronym.lower())
|
||||
token = session.docname_token_only_for_multiple()
|
||||
if token is not None:
|
||||
keywords.add(group.acronym.lower() + "-" + token)
|
||||
specific_kw = filter_keyword_for_specific_session(session)
|
||||
if specific_kw is not None:
|
||||
keywords.add(specific_kw)
|
||||
area = getattr(group, 'historic_parent', group.parent)
|
||||
|
||||
# Only sessions belonging to "regular" groups should respond to the
|
||||
|
@ -276,6 +286,18 @@ def filter_keywords_for_session(session):
|
|||
keywords.update(['officehours', session.name.lower().replace(' ', '')])
|
||||
return sorted(list(keywords))
|
||||
|
||||
def filter_keyword_for_specific_session(session):
|
||||
"""Get keyword that identifies a specific session
|
||||
|
||||
Returns None if the session cannot be selected individually.
|
||||
"""
|
||||
group = getattr(session, 'historic_group', session.group)
|
||||
if group is None:
|
||||
return None
|
||||
kw = group.acronym.lower() # start with this
|
||||
token = session.docname_token_only_for_multiple()
|
||||
return kw if token is None else '{}-{}'.format(kw, token)
|
||||
|
||||
def read_session_file(type, num, doc):
|
||||
# XXXX FIXME: the path fragment in the code below should be moved to
|
||||
# settings.py. The *_PATH settings should be generalized to format()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
|
||||
from django import template
|
||||
from django.urls import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -60,3 +61,10 @@ def args(obj, arg):
|
|||
obj.__callArg += [arg]
|
||||
return obj
|
||||
|
||||
@register.simple_tag(name='webcal_url', takes_context=True)
|
||||
def webcal_url(context, viewname, *args, **kwargs):
|
||||
"""webcal URL for a view"""
|
||||
return 'webcal://{}{}'.format(
|
||||
context.request.get_host(),
|
||||
reverse(viewname, args=args, kwargs=kwargs)
|
||||
)
|
|
@ -1355,7 +1355,65 @@ class AgendaTests(IetfSeleniumTestCase):
|
|||
wait.until(in_iframe_href('tz=america/halifax', 'weekview'))
|
||||
except:
|
||||
self.fail('iframe href not updated to contain selected time zone')
|
||||
|
||||
|
||||
def test_agenda_session_selection(self):
|
||||
wait = WebDriverWait(self.driver, 2)
|
||||
url = self.absreverse('ietf.meeting.views.agenda_personalize', kwargs={'num': self.meeting.number})
|
||||
self.driver.get(url)
|
||||
|
||||
# Verify that elements are all updated when the filters change. That the correct elements
|
||||
# have the appropriate classes is a separate test.
|
||||
elements_to_check = self.driver.find_elements_by_css_selector('.agenda-link.filterable')
|
||||
self.assertGreater(len(elements_to_check), 0, 'No elements with agenda links to update were found')
|
||||
|
||||
self.assertFalse(
|
||||
any(checkbox.is_selected()
|
||||
for checkbox in self.driver.find_elements_by_css_selector(
|
||||
'input.checkbox[name="selected-sessions"]')),
|
||||
'Sessions were selected before being clicked',
|
||||
)
|
||||
|
||||
mars_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="mars"]')
|
||||
break_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessb"]')
|
||||
registration_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessa"]')
|
||||
secretariat_button = self.driver.find_element_by_css_selector('button[data-filter-item="secretariat"]')
|
||||
|
||||
mars_checkbox.click() # select mars session
|
||||
try:
|
||||
wait.until(
|
||||
lambda driver: all('?show=mars' in el.get_attribute('href') for el in elements_to_check)
|
||||
)
|
||||
except TimeoutException:
|
||||
self.fail('Some agenda links were not updated when mars session was selected')
|
||||
self.assertTrue(mars_checkbox.is_selected(), 'mars session checkbox was not selected after being clicked')
|
||||
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was selected without being clicked')
|
||||
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
|
||||
|
||||
mars_checkbox.click() # deselect mars session
|
||||
try:
|
||||
wait.until(
|
||||
lambda driver: not any('?show=mars' in el.get_attribute('href') for el in elements_to_check)
|
||||
)
|
||||
except TimeoutException:
|
||||
self.fail('Some agenda links were not updated when mars session was de-selected')
|
||||
self.assertFalse(mars_checkbox.is_selected(), 'mars session checkbox was still selected after being clicked')
|
||||
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was selected without being clicked')
|
||||
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
|
||||
|
||||
secretariat_button.click() # turn on all secretariat sessions
|
||||
break_checkbox.click() # also select the break
|
||||
|
||||
try:
|
||||
wait.until(
|
||||
lambda driver: all(
|
||||
'?show=secretariat&hide=secretariat-sessb' in el.get_attribute('href')
|
||||
for el in elements_to_check
|
||||
))
|
||||
except TimeoutException:
|
||||
self.fail('Some agenda links were not updated when secretariat group but not break was selected')
|
||||
self.assertFalse(mars_checkbox.is_selected(), 'mars session checkbox was unexpectedly selected')
|
||||
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was unexpectedly selected')
|
||||
self.assertTrue(registration_checkbox.is_selected(), 'registration checkbox was expected to be selected')
|
||||
|
||||
@ifSeleniumEnabled
|
||||
class WeekviewTests(IetfSeleniumTestCase):
|
||||
|
@ -1693,7 +1751,10 @@ class InterimTests(IetfSeleniumTestCase):
|
|||
self.assert_upcoming_view_filter_matches_ics_filter(querystring)
|
||||
|
||||
# Check the ical links
|
||||
simplified_querystring = querystring.replace(' ', '%20') # encode spaces'
|
||||
simplified_querystring = querystring.replace(' ', '') # remove spaces
|
||||
if simplified_querystring in ['?show=', '?hide=', '?show=&hide=']:
|
||||
simplified_querystring = '' # these empty querystrings will be dropped (not an exhaustive list)
|
||||
|
||||
ics_link = self.driver.find_element_by_link_text('Download as .ics')
|
||||
self.assertIn(simplified_querystring, ics_link.get_attribute('href'))
|
||||
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')
|
||||
|
|
|
@ -35,6 +35,7 @@ from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_r
|
|||
from ietf.meeting.helpers import send_interim_approval_request
|
||||
from ietf.meeting.helpers import send_interim_meeting_cancellation_notice, send_interim_session_cancellation_notice
|
||||
from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates
|
||||
from ietf.meeting.helpers import filter_keyword_for_specific_session
|
||||
from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission, SchedulingEvent, Room, Constraint, ConstraintName
|
||||
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting, make_interim_test_data
|
||||
from ietf.meeting.utils import finalize, condition_slide_order
|
||||
|
@ -375,6 +376,63 @@ class MeetingTests(TestCase):
|
|||
self.assertEqual(r_with_tz.status_code,200)
|
||||
self.assertEqual(r.content, r_with_tz.content)
|
||||
|
||||
def test_agenda_personalize(self):
|
||||
"""Session selection page should have a checkbox for each session with appropriate keywords"""
|
||||
meeting = make_meeting_test_data()
|
||||
url = urlreverse("ietf.meeting.views.agenda_personalize",kwargs=dict(num=meeting.number))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
for assignment in SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[meeting.schedule, meeting.schedule.base],
|
||||
timeslot__type__private=False,
|
||||
):
|
||||
row = q('#row-{}'.format(assignment.slug()))
|
||||
self.assertIsNotNone(row, 'No row for assignment {}'.format(assignment))
|
||||
checkboxes = row('input[type="checkbox"][name="selected-sessions"]')
|
||||
self.assertEqual(len(checkboxes), 1,
|
||||
'Row for assignment {} does not have a checkbox input'.format(assignment))
|
||||
checkbox = checkboxes.eq(0)
|
||||
self.assertEqual(
|
||||
checkbox.attr('data-filter-item'),
|
||||
filter_keyword_for_specific_session(assignment.session),
|
||||
)
|
||||
|
||||
def test_agenda_personalize_updates_urls(self):
|
||||
"""The correct URLs should be updated when filter settings change on the personalize agenda view
|
||||
|
||||
Tests that the expected elements have the necessary classes. The actual update of these fields
|
||||
is tested in the JS tests
|
||||
"""
|
||||
meeting = make_meeting_test_data()
|
||||
url = urlreverse("ietf.meeting.views.agenda_personalize",kwargs=dict(num=meeting.number))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
# Find all the elements expected to be updated
|
||||
expected_elements = []
|
||||
nav_tab_anchors = q('ul.nav.nav-tabs > li > a')
|
||||
for anchor in nav_tab_anchors.items():
|
||||
text = anchor.text().strip()
|
||||
if text in ['Agenda', 'UTC Agenda', 'Select Sessions']:
|
||||
expected_elements.append(anchor)
|
||||
for btn in q('.buttonlist a.btn').items():
|
||||
text = btn.text().strip()
|
||||
if text in ['View customized agenda', 'Download as .ics', 'Subscribe with webcal']:
|
||||
expected_elements.append(btn)
|
||||
|
||||
# Check that all the expected elements have the correct classes
|
||||
for elt in expected_elements:
|
||||
self.assertTrue(elt.has_class('agenda-link'))
|
||||
self.assertTrue(elt.has_class('filterable'))
|
||||
|
||||
# Finally, check that there are no unexpected elements marked to be updated.
|
||||
# If there are, they should be added to the test above.
|
||||
self.assertEqual(len(expected_elements),
|
||||
len(q('.agenda-link.filterable')),
|
||||
'Unexpected elements updated')
|
||||
|
||||
@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=False, MEETING_DOC_HREFS = settings.MEETING_DOC_CDN_HREFS)
|
||||
def test_materials_through_cdn(self):
|
||||
meeting = make_meeting_test_data(create_interims=True)
|
||||
|
|
|
@ -47,6 +47,7 @@ type_ietf_only_patterns = [
|
|||
url(r'^agenda/by-type$', views.agenda_by_type),
|
||||
url(r'^agenda/by-type/(?P<type>[a-z]+)$', views.agenda_by_type),
|
||||
url(r'^agenda/by-type/(?P<type>[a-z]+)/ics$', views.agenda_by_type_ics),
|
||||
url(r'^agenda/personalize', views.agenda_personalize),
|
||||
url(r'^agendas/list$', views.list_schedules),
|
||||
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_schedules', permanent=True)),
|
||||
url(r'^agendas/diff/$', views.diff_schedules),
|
||||
|
|
|
@ -65,7 +65,7 @@ from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting
|
|||
from ietf.meeting.helpers import get_meeting, get_ietf_meeting, get_current_ietf_meeting_num
|
||||
from ietf.meeting.helpers import get_schedule, schedule_permissions, is_regular_agenda_filter_group
|
||||
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
|
||||
from ietf.meeting.helpers import filter_keywords_for_session, tag_assignments_with_filter_keywords
|
||||
from ietf.meeting.helpers import filter_keywords_for_session, tag_assignments_with_filter_keywords, filter_keyword_for_specific_session
|
||||
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
|
||||
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
|
||||
from ietf.meeting.helpers import can_edit_interim_request
|
||||
|
@ -1464,6 +1464,119 @@ def session_materials(request, session_id):
|
|||
assignment = assignments[0]
|
||||
return render(request, 'meeting/session_materials.html', dict(item=assignment))
|
||||
|
||||
|
||||
def get_assignments_for_agenda(schedule):
|
||||
"""Get queryset containing assignments to show on the agenda"""
|
||||
return SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base],
|
||||
timeslot__type__private=False,
|
||||
)
|
||||
|
||||
|
||||
def extract_groups_hierarchy(prepped_assignments):
|
||||
"""Extract groups hierarchy for agenda display
|
||||
|
||||
It's a little bit complicated because we can be dealing with historic groups.
|
||||
"""
|
||||
seen = set()
|
||||
groups = [a.session.historic_group for a in prepped_assignments
|
||||
if a.session
|
||||
and a.session.historic_group
|
||||
and is_regular_agenda_filter_group(a.session.historic_group)
|
||||
and a.session.historic_group.historic_parent]
|
||||
group_parents = []
|
||||
for g in groups:
|
||||
if g.historic_parent.acronym not in seen:
|
||||
group_parents.append(g.historic_parent)
|
||||
seen.add(g.historic_parent.acronym)
|
||||
|
||||
seen = set()
|
||||
for p in group_parents:
|
||||
p.group_list = []
|
||||
for g in groups:
|
||||
if g.acronym not in seen and g.historic_parent.acronym == p.acronym:
|
||||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
return group_parents
|
||||
|
||||
|
||||
def prepare_filter_keywords(tagged_assignments, group_parents):
|
||||
#
|
||||
# The agenda_filter template expects a list of categorized header buttons, each
|
||||
# with a list of children. Make two categories: the IETF areas and the other parent groups.
|
||||
# We also pass a list of 'extra' buttons - currently Office Hours and miscellaneous filters.
|
||||
# All but the last of these are additionally used by the agenda.html template to make
|
||||
# a list of filtered ical buttons. The last group is ignored for this.
|
||||
area_group_filters = []
|
||||
other_group_filters = []
|
||||
extra_filters = []
|
||||
|
||||
for p in group_parents:
|
||||
new_filter = dict(
|
||||
label=p.acronym.upper(),
|
||||
keyword=p.acronym.lower(),
|
||||
children=[
|
||||
dict(
|
||||
label=g.acronym,
|
||||
keyword=g.acronym.lower(),
|
||||
is_bof=g.is_bof(),
|
||||
) for g in p.group_list
|
||||
]
|
||||
)
|
||||
if p.type.slug == 'area':
|
||||
area_group_filters.append(new_filter)
|
||||
else:
|
||||
other_group_filters.append(new_filter)
|
||||
|
||||
office_hours_labels = set()
|
||||
for a in tagged_assignments:
|
||||
suffix = ' office hours'
|
||||
if a.session.name.lower().endswith(suffix):
|
||||
office_hours_labels.add(a.session.name[:-len(suffix)].strip())
|
||||
|
||||
if len(office_hours_labels) > 0:
|
||||
# keyword needs to match what's tagged in filter_keywords_for_session()
|
||||
extra_filters.append(dict(
|
||||
label='Office Hours',
|
||||
keyword='officehours',
|
||||
children=[
|
||||
dict(
|
||||
label=label,
|
||||
keyword=label.lower().replace(' ', '')+'officehours',
|
||||
is_bof=False,
|
||||
) for label in office_hours_labels
|
||||
]
|
||||
))
|
||||
|
||||
# Keywords that should appear in 'non-area' column
|
||||
non_area_labels = [
|
||||
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
]
|
||||
# Remove any unused non-area keywords
|
||||
non_area_filters = [
|
||||
dict(label=label, keyword=label.lower(), is_bof=False)
|
||||
for label in non_area_labels if any([
|
||||
label.lower() in assignment.filter_keywords
|
||||
for assignment in tagged_assignments
|
||||
])
|
||||
]
|
||||
if len(non_area_filters) > 0:
|
||||
extra_filters.append(dict(
|
||||
label=None,
|
||||
keyword=None,
|
||||
children=non_area_filters,
|
||||
))
|
||||
|
||||
area_group_filters.sort(key=lambda p:p['label'])
|
||||
other_group_filters.sort(key=lambda p:p['label'])
|
||||
filter_categories = [category
|
||||
for category in [area_group_filters, other_group_filters, extra_filters]
|
||||
if len(category) > 0]
|
||||
return filter_categories, non_area_labels
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""):
|
||||
base = base if base else 'agenda'
|
||||
|
@ -1486,6 +1599,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
else:
|
||||
raise Http404("No such meeting")
|
||||
|
||||
# Select the schedule to show
|
||||
if name is None:
|
||||
schedule = get_schedule(meeting, name)
|
||||
else:
|
||||
|
@ -1497,112 +1611,23 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
return render(request, "meeting/no-"+base+ext, {'meeting':meeting }, content_type=mimetype[ext])
|
||||
|
||||
updated = meeting.updated()
|
||||
filtered_assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base],
|
||||
timeslot__type__private=False,
|
||||
|
||||
# Select and prepare sessions that should be included
|
||||
filtered_assignments = preprocess_assignments_for_agenda(
|
||||
get_assignments_for_agenda(schedule),
|
||||
meeting
|
||||
)
|
||||
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
|
||||
tag_assignments_with_filter_keywords(filtered_assignments)
|
||||
|
||||
# Done processing for CSV output
|
||||
if ext == ".csv":
|
||||
return agenda_csv(schedule, filtered_assignments)
|
||||
|
||||
# extract groups hierarchy, it's a little bit complicated because
|
||||
# we can be dealing with historic groups
|
||||
seen = set()
|
||||
groups = [a.session.historic_group for a in filtered_assignments
|
||||
if a.session
|
||||
and a.session.historic_group
|
||||
and is_regular_agenda_filter_group(a.session.historic_group)
|
||||
and a.session.historic_group.historic_parent]
|
||||
group_parents = []
|
||||
for g in groups:
|
||||
if g.historic_parent.acronym not in seen:
|
||||
group_parents.append(g.historic_parent)
|
||||
seen.add(g.historic_parent.acronym)
|
||||
|
||||
seen = set()
|
||||
for p in group_parents:
|
||||
p.group_list = []
|
||||
for g in groups:
|
||||
if g.acronym not in seen and g.historic_parent.acronym == p.acronym:
|
||||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
|
||||
# Groups gathered and processed. Now arrange for the filter UI.
|
||||
#
|
||||
# The agenda_filter template expects a list of categorized header buttons, each
|
||||
# with a list of children. Make two categories: the IETF areas and the other parent groups.
|
||||
# We also pass a list of 'extra' buttons - currently Office Hours and miscellaneous filters.
|
||||
# All but the last of these are additionally used by the agenda.html template to make
|
||||
# a list of filtered ical buttons. The last group is ignored for this.
|
||||
area_group_filters = []
|
||||
other_group_filters = []
|
||||
extra_filters = []
|
||||
|
||||
for p in group_parents:
|
||||
new_filter = dict(
|
||||
label=p.acronym.upper(),
|
||||
keyword=p.acronym.lower(),
|
||||
children=[
|
||||
dict(
|
||||
label=g.acronym,
|
||||
keyword=g.acronym.lower(),
|
||||
is_bof=g.is_bof(),
|
||||
) for g in p.group_list
|
||||
]
|
||||
)
|
||||
if p.type.slug == 'area':
|
||||
area_group_filters.append(new_filter)
|
||||
else:
|
||||
other_group_filters.append(new_filter)
|
||||
|
||||
office_hours_labels = set()
|
||||
for a in filtered_assignments:
|
||||
suffix = ' office hours'
|
||||
if a.session.name.lower().endswith(suffix):
|
||||
office_hours_labels.add(a.session.name[:-len(suffix)].strip())
|
||||
|
||||
if len(office_hours_labels) > 0:
|
||||
# keyword needs to match what's tagged in filter_keywords_for_session()
|
||||
extra_filters.append(dict(
|
||||
label='Office Hours',
|
||||
keyword='officehours',
|
||||
children=[
|
||||
dict(
|
||||
label=label,
|
||||
keyword=label.lower().replace(' ', '')+'officehours',
|
||||
is_bof=False,
|
||||
) for label in office_hours_labels
|
||||
]
|
||||
))
|
||||
|
||||
# Keywords that should appear in 'non-area' column
|
||||
non_area_labels = [
|
||||
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
]
|
||||
# Remove any unused non-area keywords
|
||||
non_area_filters = [
|
||||
dict(label=label, keyword=label.lower(), is_bof=False)
|
||||
for label in non_area_labels if any([
|
||||
label.lower() in assignment.filter_keywords
|
||||
for assignment in filtered_assignments
|
||||
])
|
||||
]
|
||||
if len(non_area_filters) > 0:
|
||||
extra_filters.append(dict(
|
||||
label=None,
|
||||
keyword=None,
|
||||
children=non_area_filters,
|
||||
))
|
||||
|
||||
area_group_filters.sort(key=lambda p:p['label'])
|
||||
other_group_filters.sort(key=lambda p:p['label'])
|
||||
filter_categories = [category
|
||||
for category in [area_group_filters, other_group_filters, extra_filters]
|
||||
if len(category) > 0]
|
||||
# Now prep the filter UI
|
||||
filter_categories, non_area_labels = prepare_filter_keywords(
|
||||
filtered_assignments,
|
||||
extract_groups_hierarchy(filtered_assignments),
|
||||
)
|
||||
|
||||
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
|
||||
|
||||
|
@ -1756,6 +1781,45 @@ def agenda_by_type_ics(request,num=None,type=None):
|
|||
updated = meeting.updated()
|
||||
return render(request,"meeting/agenda.ics",{"schedule":schedule,"updated":updated,"assignments":assignments},content_type="text/calendar")
|
||||
|
||||
|
||||
def agenda_personalize(request, num):
|
||||
meeting = get_ietf_meeting(num) # num may be None, which requests the current meeting
|
||||
if meeting is None or meeting.schedule is None:
|
||||
raise Http404('No such meeting')
|
||||
|
||||
# Select and prepare sessions that should be included
|
||||
filtered_assignments = preprocess_assignments_for_agenda(
|
||||
get_assignments_for_agenda(meeting.schedule),
|
||||
meeting
|
||||
)
|
||||
tag_assignments_with_filter_keywords(filtered_assignments)
|
||||
for assignment in filtered_assignments:
|
||||
# may be None for some sessions
|
||||
assignment.session_keyword = filter_keyword_for_specific_session(assignment.session)
|
||||
|
||||
# Now prep the filter UI
|
||||
filter_categories, non_area_labels = prepare_filter_keywords(
|
||||
filtered_assignments,
|
||||
extract_groups_hierarchy(filtered_assignments),
|
||||
)
|
||||
|
||||
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
|
||||
|
||||
return render(
|
||||
request,
|
||||
"meeting/agenda_personalize.html",
|
||||
{
|
||||
'schedule': meeting.schedule,
|
||||
'updated': meeting.updated(),
|
||||
'filtered_assignments': filtered_assignments,
|
||||
'filter_categories': filter_categories,
|
||||
'non_area_labels': non_area_labels,
|
||||
'timezone': meeting.time_zone,
|
||||
'is_current_meeting': is_current_meeting,
|
||||
'cache_time': 150 if is_current_meeting else 3600,
|
||||
}
|
||||
)
|
||||
|
||||
def session_draft_list(num, acronym):
|
||||
try:
|
||||
agendas = Document.objects.filter(type="agenda",
|
||||
|
|
|
@ -5,7 +5,14 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
(function () {
|
||||
'use strict'
|
||||
|
||||
var update_callback; // function(filter_params)
|
||||
/* n.b., const refers to the opts object itself, not its contents.
|
||||
* Use camelCase for easy translation into element.dataset keys,
|
||||
* which are automatically camel-cased from the data attribute name.
|
||||
* (e.g., data-always-show -> elt.dataset.alwaysShow) */
|
||||
const opts = {
|
||||
alwaysShow: false,
|
||||
updateCallback: null // function(filter_params)
|
||||
};
|
||||
|
||||
/* Remove from list, if present */
|
||||
function remove_list_item (list, item) {
|
||||
|
@ -58,7 +65,7 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
}
|
||||
|
||||
function get_filter_params (qparams) {
|
||||
var enabled = !!(qparams.show || qparams.hide);
|
||||
var enabled = opts.alwaysShow || qparams.show || qparams.hide;
|
||||
return {
|
||||
enabled: enabled,
|
||||
show: get_filter_from_qparams(qparams, 'show'),
|
||||
|
@ -114,8 +121,13 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
return;
|
||||
}
|
||||
|
||||
update_href_querystrings(filter_params_as_querystring(filter_params))
|
||||
|
||||
// show the customizer - it will stay visible even if filtering is disabled
|
||||
$('#customize').collapse('show')
|
||||
const customizer = $('#customize');
|
||||
if (customizer.hasClass('collapse')) {
|
||||
customizer.collapse('show')
|
||||
}
|
||||
|
||||
// Update button state to match visibility
|
||||
buttons.each(function (index, elt) {
|
||||
|
@ -140,8 +152,8 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
function update_view () {
|
||||
var filter_params = get_filter_params(parse_query_params(window.location.search))
|
||||
update_filter_ui(filter_params)
|
||||
if (update_callback) {
|
||||
update_callback(filter_params)
|
||||
if (opts.updateCallback) {
|
||||
opts.updateCallback(filter_params)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,20 +163,11 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
* (if supported) or loads the new URL.
|
||||
*/
|
||||
function update_filters (filter_params) {
|
||||
var qparams = []
|
||||
var search = ''
|
||||
if (filter_params.show.length > 0) {
|
||||
qparams.push('show=' + filter_params.show.join())
|
||||
}
|
||||
if (filter_params.hide.length > 0) {
|
||||
qparams.push('hide=' + filter_params.hide.join())
|
||||
}
|
||||
if (qparams.length > 0) {
|
||||
search = '?' + qparams.join('&')
|
||||
}
|
||||
|
||||
// strip out the search / hash, then add back
|
||||
var new_url = window.location.href.replace(/(\?.*)?(#.*)?$/, search + window.location.hash)
|
||||
var new_url = replace_querystring(
|
||||
window.location.href,
|
||||
filter_params_as_querystring(filter_params)
|
||||
)
|
||||
update_href_querystrings(filter_params_as_querystring(filter_params))
|
||||
if (window.history && window.history.replaceState) {
|
||||
// Keep current origin, replace search string, no page reload
|
||||
history.replaceState({}, document.title, new_url)
|
||||
|
@ -175,6 +178,35 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the querystring in the href filterable agenda links
|
||||
*/
|
||||
function update_href_querystrings(querystring) {
|
||||
Array.from(
|
||||
document.getElementsByClassName('agenda-link filterable')
|
||||
).forEach(
|
||||
(elt) => elt.href = replace_querystring(elt.href, querystring)
|
||||
)
|
||||
}
|
||||
|
||||
function filter_params_as_querystring(filter_params) {
|
||||
var qparams = []
|
||||
if (filter_params.show.length > 0) {
|
||||
qparams.push('show=' + filter_params.show.join())
|
||||
}
|
||||
if (filter_params.hide.length > 0) {
|
||||
qparams.push('hide=' + filter_params.hide.join())
|
||||
}
|
||||
if (qparams.length > 0) {
|
||||
return '?' + qparams.join('&')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function replace_querystring(url, new_querystring) {
|
||||
return url.replace(/(\?.*)?(#.*)?$/, new_querystring + window.location.hash)
|
||||
}
|
||||
|
||||
/* Helper for pick group/type button handlers - toggles the appropriate parameter entry
|
||||
* elt - the jquery element that was clicked
|
||||
*/
|
||||
|
@ -225,7 +257,19 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
update_filters(fp);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read options from the template
|
||||
*/
|
||||
function read_template_options() {
|
||||
const opts_elt = document.getElementById('agenda-filter-options');
|
||||
opts.keys().forEach((opt) => {
|
||||
if (opt in opts_elt.dataset) {
|
||||
opts[opt] = opts_elt.dataset[opt];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Entry point to filtering code when page loads
|
||||
*
|
||||
* This must be called if you are using the HTML template to provide a customization
|
||||
|
@ -261,6 +305,6 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
|
|||
keyword_match: keyword_match,
|
||||
parse_query_params: parse_query_params,
|
||||
rows_matching_filter_keyword: rows_matching_filter_keyword,
|
||||
set_update_callback: function (cb) {update_callback = cb}
|
||||
set_update_callback: function (cb) {opts.updateCallback = cb}
|
||||
};
|
||||
})();
|
82
ietf/static/ietf/js/agenda/agenda_personalize.js
Normal file
82
ietf/static/ietf/js/agenda/agenda_personalize.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright The IETF Trust 2021, All Rights Reserved
|
||||
|
||||
/**
|
||||
* Agenda personalization JS methods
|
||||
*
|
||||
* Requires agenda_timezone.js and timezone.js be included.
|
||||
*/
|
||||
const agenda_personalize = (
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
let meeting_timezone = document.getElementById('initial-data').dataset.timezone;
|
||||
let selection_inputs;
|
||||
|
||||
/**
|
||||
* Update the checkbox state to match the filter parameters
|
||||
*/
|
||||
function updateAgendaCheckboxes(filter_params) {
|
||||
selection_inputs.forEach((inp) => {
|
||||
const item_keywords = inp.dataset.filterKeywords.toLowerCase().split(',');
|
||||
if (
|
||||
agenda_filter.keyword_match(item_keywords, filter_params.show)
|
||||
&& !agenda_filter.keyword_match(item_keywords, filter_params.hide)
|
||||
) {
|
||||
inp.checked = true;
|
||||
} else {
|
||||
inp.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleFilterParamUpdate(filter_params) {
|
||||
updateAgendaCheckboxes(filter_params);
|
||||
}
|
||||
|
||||
function handleTableClick(event) {
|
||||
if (event.target.name === 'selected-sessions') {
|
||||
// hide the tooltip after clicking on a checkbox
|
||||
const jqElt = jQuery(event.target);
|
||||
if (jqElt.tooltip) {
|
||||
jqElt.tooltip('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
|
||||
|
||||
// First, initialize_moments(). This must be done before calling any of the update methods.
|
||||
// It does not need timezone info, so safe to call before initializing ietf_timezone.
|
||||
initialize_moments(); // fills in moments in the agenda data
|
||||
|
||||
// Now set up callbacks related to ietf_timezone. This must happen before calling initialize().
|
||||
// In particular, set_current_tz_cb() must be called before the update methods are called.
|
||||
set_current_tz_cb(ietf_timezone.get_current_tz); // give agenda_timezone access to this method
|
||||
ietf_timezone.set_tz_change_callback(function (newtz) {
|
||||
update_times(newtz);
|
||||
}
|
||||
);
|
||||
|
||||
// With callbacks in place, call ietf_timezone.initialize(). This will call the tz_change callback
|
||||
// after setting things up.
|
||||
ietf_timezone.initialize(meeting_timezone);
|
||||
|
||||
// Now make other setup calls from agenda_timezone.js
|
||||
add_tooltips();
|
||||
init_timers();
|
||||
|
||||
selection_inputs = document.getElementsByName('selected-sessions');
|
||||
|
||||
agenda_filter.set_update_callback(handleFilterParamUpdate);
|
||||
agenda_filter.enable();
|
||||
|
||||
document.getElementById('agenda-table')
|
||||
.addEventListener('click', handleTableClick);
|
||||
}
|
||||
);
|
||||
|
||||
// export public interface
|
||||
return { meeting_timezone };
|
||||
}
|
||||
)();
|
|
@ -116,7 +116,7 @@
|
|||
{% endfor %}
|
||||
<div style="display:inline-block">
|
||||
<a class="btn btn-default" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}">Non-area events</a>
|
||||
<a id="ical-link" class="hidden btn btn-primary" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}">Customized schedule</a>
|
||||
<a id="ical-link" class="hidden btn btn-primary agenda-link filterable" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}">Customized schedule</a>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
|
@ -421,14 +421,7 @@
|
|||
|
||||
function update_ical_links(filter_params) {
|
||||
var ical_link = $("#ical-link");
|
||||
if (agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
// Replace the query string in the ical link
|
||||
var orig_link_href = ical_link.attr("href").split("?")[0];
|
||||
ical_link.attr("href", orig_link_href+window.location.search);
|
||||
ical_link.removeClass("hidden");
|
||||
} else {
|
||||
ical_link.addClass("hidden");
|
||||
}
|
||||
ical_link.toggleClass("hidden", !agenda_filter.filtering_is_enabled(filter_params));
|
||||
}
|
||||
|
||||
function update_weekview(filter_params) {
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
{% comment %}
|
||||
Required parameters:
|
||||
filter_categories - filter description structure (see agenda view for example)
|
||||
|
||||
Optional parameters:
|
||||
always_show - if False or absent, menu closes when not in use and "Customize" button is shown
|
||||
customize_button_text - text to show on the "Customize" button (defaults to "Customize...")
|
||||
{% endcomment %}
|
||||
{% load agenda_filter_tags %}
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% if not always_show %}
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#accordion" href="#customize">
|
||||
<span class="fa fa-caret-down"></span> {% firstof customize_button_text "Customize..."%}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="customize" class="panel-collapse collapse">
|
||||
{% endif %}
|
||||
<div id="customize" class="{% if not always_show %}panel-collapse collapse{% endif %}">
|
||||
<span hidden {# options to pass to the JS - correspond to keys in opts object #}
|
||||
id="agenda-filter-options"
|
||||
data-always-show="{% firstof always_show False %}">
|
||||
</span>
|
||||
<div class="panel-body">
|
||||
|
||||
<p>
|
||||
|
|
405
ietf/templates/meeting/agenda_personalize.html
Normal file
405
ietf/templates/meeting/agenda_personalize.html
Normal file
|
@ -0,0 +1,405 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load static %}
|
||||
{% load ietf_filters %}
|
||||
{% load textfilters %}
|
||||
{% load htmlfilters %}
|
||||
|
||||
{% block title %}
|
||||
IETF {{ schedule.meeting.number }} meeting agenda personalization
|
||||
{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
tr:not(:first-child) th.gap {
|
||||
height: 3em !important;
|
||||
background-color: inherit !important;
|
||||
border: none !important;
|
||||
}
|
||||
tr:first-child th.gap {
|
||||
height: 0 !important;
|
||||
background-color: inherit !important;
|
||||
border: none !important;
|
||||
}
|
||||
div.tz-display {
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
.tz-display a {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tz-display label {
|
||||
font-weight: normal;
|
||||
}
|
||||
.tz-display select {
|
||||
min-width: 15em;
|
||||
}
|
||||
#affix .nav li.tz-display {
|
||||
padding: 4px 20px;
|
||||
}
|
||||
#affix .nav li.tz-display a {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="select-sessions" title_extra="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{# cache this part -- it takes 3-6 seconds to generate #}
|
||||
{% load cache %}
|
||||
{% cache cache_time ietf_meeting_agenda_personalize schedule.meeting.number request.path %}
|
||||
<div class="row">
|
||||
<div class="col-xs-6"><h1>Session Selection</h1></div>
|
||||
<div class="col-xs-6">
|
||||
<div class="tz-display">
|
||||
<div><small>
|
||||
<label for="timezone-select">Time zone:</label>
|
||||
<a id="meeting-timezone" onclick="ietf_timezone.use('{{ timezone }}')">Meeting</a> |
|
||||
<a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> |
|
||||
<a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a>
|
||||
</small></div>
|
||||
<select id="timezone-select" class="tz-select">
|
||||
{# Avoid blank while loading. JavaScript replaces the option list after init. #}
|
||||
<option selected>{{ timezone }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if is_current_meeting %}
|
||||
<p class="alert alert-info">
|
||||
<b>Note:</b> IETF agendas are subject to change, up to and during a meeting.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories always_show=True %}
|
||||
|
||||
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %}
|
||||
|
||||
<h2>
|
||||
Individual Sessions
|
||||
</h2>
|
||||
<p>
|
||||
Check boxes below to select individual sessions.
|
||||
</p>
|
||||
|
||||
<table id="agenda-table" class="table table-condensed table-striped">
|
||||
{% for item in filtered_assignments %}
|
||||
|
||||
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
|
||||
<tr>
|
||||
<th class="gap" colspan="7"></th>
|
||||
</tr>
|
||||
<tr class="warning">
|
||||
<th colspan="7">
|
||||
{# The anchor here needs to be in a div, not in the th, in order for the anchor-target margin hack to work #}
|
||||
<div class="anchor-target" id="{{ item.timeslot.time|slugify }}"></div>
|
||||
{{ item.timeslot.time|date:"l, F j, Y" }}
|
||||
</th>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
|
||||
{% if item.timeslot.type_id == 'regular' %}
|
||||
{% ifchanged %}
|
||||
<tr class="info session-label-row"
|
||||
data-slot-start-ts="{{ item.start_timestamp }}"
|
||||
data-slot-end-ts="{{ item.end_timestamp }}">
|
||||
<td class="leftmarker"></td>
|
||||
<th class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
</th>
|
||||
<th colspan="4">
|
||||
<span class="hidden-sm hidden-md hidden-lg">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
{{ item.timeslot.time|date:"l" }}
|
||||
{{ item.timeslot.name|capfirst_allcaps }}
|
||||
</th>
|
||||
<td class="rightmarker"></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if item.timeslot.type.slug == 'break' or item.timeslot.type.slug == 'reg' or item.timeslot.type.slug == 'other' %}
|
||||
<tr id="row-{{ item.slug }}"
|
||||
data-slot-start-ts="{{ item.start_timestamp }}"
|
||||
data-slot-end-ts="{{ item.end_timestamp }}">
|
||||
<td class="leftmarker">
|
||||
{% if item.session_keyword %}
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pickview"
|
||||
title="Select session"
|
||||
name="selected-sessions"
|
||||
value="{{ item.session_keyword }}"
|
||||
data-filter-keywords="{{ item.filter_keywords|join:',' }}"
|
||||
data-filter-item="{{ item.session_keyword }}">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
</td>
|
||||
<td colspan="3">
|
||||
<span class="hidden-sm hidden-md hidden-lg">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
|
||||
{% if schedule.meeting.number|add:"0" < 96 %}
|
||||
<a
|
||||
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% elif item.timeslot.location.floorplan %}
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% else %}
|
||||
{{ item.timeslot.get_html_location }}
|
||||
{% endif %}
|
||||
{% with item.timeslot.location.floorplan as floor %}
|
||||
{% if item.timeslot.location.floorplan %}
|
||||
<span class="hidden-xs">
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}"
|
||||
class="pull-right" title="{{ floor.name }}"><span
|
||||
class="label label-blank label-wide">{{ floor.short }}</span></a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.session.agenda %}
|
||||
<a href="{{ item.session.agenda.get_href }}">
|
||||
{{ item.timeslot.name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ item.timeslot.name }}
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.current_status == 'canceled' %}
|
||||
<span class="label label-danger pull-right">CANCELLED</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="rightmarker"></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %}
|
||||
{% if item.session.historic_group %}
|
||||
<tr id="row-{{ item.slug }}"
|
||||
{% if item.timeslot.type.slug == 'plenary' %}class="{{ item.timeslot.type.slug }}danger"{% endif %}
|
||||
data-slot-start-ts="{{ item.start_timestamp }}"
|
||||
data-slot-end-ts="{{ item.end_timestamp }}">
|
||||
<td class="leftmarker">
|
||||
{% if item.session_keyword %}
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pickview"
|
||||
title="Select session"
|
||||
name="selected-sessions"
|
||||
value="{{ item.session_keyword }}"
|
||||
data-filter-keywords="{{ item.filter_keywords|join:',' }}"
|
||||
data-filter-item="{{ item.session_keyword }}">
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if item.timeslot.type.slug == 'plenary' %}
|
||||
<th class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
</th>
|
||||
<td colspan="3">
|
||||
<span class="hidden-sm hidden-md hidden-lg">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
</span>
|
||||
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
|
||||
{% if schedule.meeting.number|add:"0" < 96 %}
|
||||
<a
|
||||
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% elif item.timeslot.location.floorplan %}
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% else %}
|
||||
{{ item.timeslot.get_html_location }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
{% else %}
|
||||
<td>
|
||||
{% with item.timeslot.location.floorplan as floor %}
|
||||
{% if item.timeslot.location.floorplan %}
|
||||
<span class="hidden-xs">
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}"
|
||||
class="pull-right" title="{{ floor.name }}"><span
|
||||
class="label label-blank">{{ floor.short }}</span></a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
|
||||
{% if schedule.meeting.number|add:"0" < 96 %}
|
||||
<a
|
||||
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% elif item.timeslot.location.floorplan %}
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
|
||||
{% else %}
|
||||
{{ item.timeslot.get_html_location }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td><span class="hidden-xs">{{ item.session.historic_group.historic_parent.acronym }}</span></td>
|
||||
|
||||
<td>
|
||||
{% if item.session.historic_group %}
|
||||
<a
|
||||
href="{% url 'ietf.group.views.group_about' acronym=item.session.historic_group.acronym %}">{{ item.session.historic_group.acronym }}</a>
|
||||
{% else %}
|
||||
{{ item.session.historic_group.acronym }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td>
|
||||
{% if item.session.agenda %}
|
||||
<a href="{{ item.session.agenda.get_href }}">
|
||||
{% endif %}
|
||||
{% if item.timeslot.type.slug == 'plenary' %}
|
||||
{{ item.timeslot.name }}
|
||||
{% else %}
|
||||
{{ item.session.historic_group.name }}
|
||||
{% endif %}
|
||||
{% if item.session.agenda %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.current_status == 'canceled' %}
|
||||
<span class="label label-danger pull-right">CANCELLED</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.historic_group.state_id == "bof" %}
|
||||
<span class="label label-success pull-right">BOF</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.current_status == 'resched' %}
|
||||
<span class="label label-danger pull-right">
|
||||
RESCHEDULED
|
||||
{% if item.session.rescheduled_to %}
|
||||
TO
|
||||
<span class="timetooltip reschedtimetooltip"><span class="time"
|
||||
data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}"
|
||||
data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}"
|
||||
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %}
|
||||
weekday="1"{% endif %}>
|
||||
{% if "-utc" in request.path %}
|
||||
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-
|
||||
{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
|
||||
{% else %}
|
||||
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-
|
||||
{{ item.session.rescheduled_to.end_time|date:"G:i" }}
|
||||
{% endif %}
|
||||
</span></span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.agenda_note|first_url|conference_url %}
|
||||
<br>
|
||||
<a href={{ item.session.agenda_note|first_url }}>{{ item.session.agenda_note|slice:":23" }}</a>
|
||||
{% elif item.session.agenda_note %}
|
||||
<br><span class="text-danger">{{ item.session.agenda_note }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="rightmarker"></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %}
|
||||
|
||||
</div>
|
||||
<div class="col-md-2 hidden-print bs-docs-sidebar" id="affix">
|
||||
<ul class="nav nav-pills nav-stacked small" data-spy="affix">
|
||||
<li><a href="#now">Now</a></li>
|
||||
{% for item in filtered_assignments %}
|
||||
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
|
||||
<li><a href="#{{ item.timeslot.time|slugify }}">{{ item.timeslot.time|date:"l, F j, Y" }}</a></li>
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
<li>
|
||||
<hr/>
|
||||
</li>
|
||||
<li class="tz-display">Showing <span class="current-tz">{{ timezone }}</span> time</li>
|
||||
<li class="tz-display"><span> {# span avoids applying nav link styling to these shortcuts #}
|
||||
<a onclick="ietf_timezone.use('{{ timezone }}')">Meeting time</a> |
|
||||
<a onclick="ietf_timezone.use('local')">Local time</a> |
|
||||
<a onclick="ietf_timezone.use('UTC')">UTC</a></span>
|
||||
</li>
|
||||
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
|
||||
<li>
|
||||
<hr/>
|
||||
</li>
|
||||
<li><span id="current-time"></span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endcache %}
|
||||
|
||||
{# make the timezone available to JS #}
|
||||
<span id="initial-data" hidden data-timezone="{{ timezone }}"></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'moment/min/moment.min.js' %}"></script>
|
||||
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/agenda/agenda_timezone.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/agenda/agenda_personalize.js' %}"></script>
|
||||
<script>
|
||||
|
||||
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
|
||||
speedup = +$.urlParam('speedup')
|
||||
if (speedup < 1) {
|
||||
speedup = 1
|
||||
}
|
||||
start_time = moment().utc()
|
||||
if ($.urlParam('date')) {
|
||||
offset_time = moment.tz(decodeURIComponent($.urlParam('date')), 'UTC')
|
||||
} else {
|
||||
offset_time = start_time
|
||||
}
|
||||
if (speedup > 1 || offset_time != start_time) {
|
||||
moment.now = function () {
|
||||
return (+new Date() - start_time) * speedup + offset_time
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
speedup = 1
|
||||
{% endif %}
|
||||
|
||||
/* pull this from the agenda_personalize js module to make available to agenda_timezone */
|
||||
meeting_timezone = agenda_personalize.meeting_timezone;
|
||||
</script>
|
||||
{% endblock %}
|
20
ietf/templates/meeting/agenda_personalize_buttonlist.html
Normal file
20
ietf/templates/meeting/agenda_personalize_buttonlist.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% comment %}
|
||||
Buttons for the agenda_personalize.html template
|
||||
|
||||
Required parameter: meeting - meeting being displayed
|
||||
{% endcomment %}
|
||||
{% load agenda_custom_tags %}
|
||||
<div class="buttonlist">
|
||||
<a class="btn btn-default agenda-link filterable"
|
||||
href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">
|
||||
View customized agenda
|
||||
</a>
|
||||
<a class="btn btn-default agenda-link filterable"
|
||||
href="{% url 'ietf.meeting.views.agenda_ical' num=meeting.number %}">
|
||||
Download as .ics
|
||||
</a>
|
||||
<a class="btn btn-default agenda-link filterable"
|
||||
href="{% webcal_url 'ietf.meeting.views.agenda_ical' num=meeting.number %}">
|
||||
Subscribe with webcal
|
||||
</a>
|
||||
</div>
|
|
@ -28,30 +28,57 @@
|
|||
</small>
|
||||
</h1>
|
||||
|
||||
{# <a> tags with the agenda-link filterable classes will be updated with show/hide parameters #}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li {% if selected == "agenda" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">Agenda</a></li>
|
||||
<li {% if selected == "agenda-utc" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number utc='-utc' %}">UTC Agenda</a></li>
|
||||
{% if user|has_role:"Secretariat,Area Director,IAB" %}
|
||||
<li {% if selected == "agenda" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}"
|
||||
class="agenda-link filterable">
|
||||
Agenda
|
||||
</a>
|
||||
</li>
|
||||
<li {% if selected == "agenda-utc" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number utc='-utc' %}"
|
||||
class="agenda-link filterable">
|
||||
UTC Agenda
|
||||
</a>
|
||||
</li>
|
||||
{% if user|has_role:"Secretariat,Area Director,IAB" %}
|
||||
{% if schedule != meeting.schedule %}
|
||||
<li {% if selected == "by-room" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by Room</a></li>
|
||||
<li {% if selected == "by-type" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by Type</a></li>
|
||||
<li {% if selected == "room-view" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number name=schedule.name owner=schedule.owner.email %}">Room grid</a></li>
|
||||
{% else %}
|
||||
<li {% if selected == "by-room" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
|
||||
<li {% if selected == "by-type" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
|
||||
<li {% if selected == "room-view" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li {% if selected == "floor-plan" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
|
||||
<li><a href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">Plaintext</a></li>
|
||||
<li><a href="https://tools.ietf.org/agenda/{{meeting.number}}/">Tools-style »</a></li>
|
||||
<li {% if selected == "by-room" %}class="active"{% endif %}>
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
|
||||
Room</a></li>
|
||||
<li {% if selected == "by-type" %}class="active"{% endif %}>
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
|
||||
Type</a></li>
|
||||
<li {% if selected == "room-view" %}class="active"{% endif %}>
|
||||
<a
|
||||
href="{% url 'ietf.meeting.views.room_view' num=meeting.number name=schedule.name owner=schedule.owner.email %}">Room
|
||||
grid</a></li>
|
||||
{% else %}
|
||||
<li {% if selected == "by-room" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
|
||||
<li {% if selected == "by-type" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
|
||||
<li {% if selected == "room-view" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li {% if selected == "floor-plan" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
|
||||
<li>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">
|
||||
Plaintext
|
||||
</a>
|
||||
</li>
|
||||
<li {% if selected == "select-sessions" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_personalize' num=meeting.number %}"
|
||||
class="agenda-link filterable">
|
||||
Select Sessions
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://tools.ietf.org/agenda/{{ meeting.number }}/">Tools-style »</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -83,8 +83,7 @@
|
|||
{% if menu_actions %}
|
||||
<div id="menu-actions" class="buttonlist">
|
||||
{% for action in menu_actions %}
|
||||
<a class="btn btn-default"
|
||||
data-append-filter="{{ action.append_filter }}"
|
||||
<a class="btn btn-default {% if action.append_filter %}agenda-link filterable{% endif %}"
|
||||
href="{{ action.url }}">{{ action.label }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -314,24 +313,8 @@
|
|||
});
|
||||
}
|
||||
|
||||
function update_links(filter_params) {
|
||||
var filtered_links = $("#menu-actions [data-append-filter='True']");
|
||||
var filtering_enabled = agenda_filter.filtering_is_enabled(filter_params);
|
||||
filtered_links.each(function(index, elt) {
|
||||
var orig_link_href = $(elt).attr("href").split("?")[0];
|
||||
if (filtering_enabled) {
|
||||
// append new querystring
|
||||
$(elt).attr("href", orig_link_href+window.location.search);
|
||||
} else {
|
||||
// remove querystring
|
||||
$(elt).attr("href", orig_link_href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update_view(filter_params) {
|
||||
update_meeting_display(filter_params);
|
||||
update_links(filter_params);
|
||||
update_calendar(current_tz, filter_params);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue