Merged in ^/personal/jennifer/7.17.1.dev0 from jennifer@painless-security.com:
This adds support for the simpler show/hide filtering to the ical agenda views. It also significantly rearranges (and, I hope, improves the organization of) the tests. In particular, it specifically tests that the ical and HTML views include equivalent sets of events. Finally, the agenda_filter.html template is reworked to be more modular. - Legacy-Id: 18631
This commit is contained in:
commit
7384c03859
|
@ -240,6 +240,30 @@ def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefe
|
|||
|
||||
return assignments
|
||||
|
||||
def tag_assignments_with_filter_keywords(assignments):
|
||||
"""Add keywords for agenda filtering
|
||||
|
||||
Keywords are all lower case.
|
||||
"""
|
||||
for a in assignments:
|
||||
a.filter_keywords = {a.timeslot.type.slug.lower()}
|
||||
a.filter_keywords.update(filter_keywords_for_session(a.session))
|
||||
|
||||
def filter_keywords_for_session(session):
|
||||
keywords = {session.type.slug.lower()}
|
||||
group = getattr(session, 'historic_group', session.group)
|
||||
if group is not None:
|
||||
if group.state_id == 'bof':
|
||||
keywords.add('bof')
|
||||
keywords.add(group.acronym.lower())
|
||||
area = getattr(group, 'historic_parent', group.parent)
|
||||
if area is not None:
|
||||
keywords.add(area.acronym.lower())
|
||||
office_hours_match = re.match(r'^ *\w+(?: +\w+)* +office hours *$', session.name, re.IGNORECASE)
|
||||
if office_hours_match is not None:
|
||||
keywords.update(['officehours', session.name.lower().replace(' ', '')])
|
||||
return keywords
|
||||
|
||||
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()
|
||||
|
|
20
ietf/meeting/templatetags/agenda_filter_tags.py
Normal file
20
ietf/meeting/templatetags/agenda_filter_tags.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright The IETF Trust 2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Custom tags for the agenda filter template"""
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def agenda_width_scale(filter_categories, spacer_scale):
|
||||
"""Compute the width scale for the agenda filter button table
|
||||
|
||||
Button columns are spacer_scale times as wide as the spacer columns between
|
||||
categories. There is one fewer spacer column than categories.
|
||||
"""
|
||||
category_count = len(filter_categories)
|
||||
column_count = sum([len(cat) for cat in filter_categories])
|
||||
# Refuse to return less than 1 to avoid width calculation problems.
|
||||
return max(spacer_scale * column_count + category_count - 1, 1)
|
97
ietf/meeting/tests_helpers.py
Normal file
97
ietf/meeting/tests_helpers.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Copyright The IETF Trust 2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.meeting.factories import SessionFactory, MeetingFactory
|
||||
from ietf.meeting.helpers import tag_assignments_with_filter_keywords
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
||||
|
||||
class HelpersTests(TestCase):
|
||||
def do_test_tag_assignments_with_filter_keywords(self, bof=False, historic=None):
|
||||
"""Assignments should be tagged properly
|
||||
|
||||
The historic param can be None, group, or parent, to specify whether to test
|
||||
with no historic_group, a historic_group but no historic_parent, or both.
|
||||
"""
|
||||
meeting_types = ['regular', 'plenary']
|
||||
group_state_id = 'bof' if bof else 'active'
|
||||
group = GroupFactory(state_id=group_state_id)
|
||||
historic_group = GroupFactory(state_id=group_state_id)
|
||||
historic_parent = GroupFactory(type_id='area')
|
||||
|
||||
if historic == 'parent':
|
||||
historic_group.historic_parent = historic_parent
|
||||
|
||||
# Create meeting and sessions
|
||||
meeting = MeetingFactory()
|
||||
for meeting_type in meeting_types:
|
||||
sess = SessionFactory(group=group, meeting=meeting, type_id=meeting_type)
|
||||
ts = sess.timeslotassignments.first().timeslot
|
||||
ts.type = sess.type
|
||||
ts.save()
|
||||
|
||||
# Create an office hours session in the group's area (i.e., parent). This is not
|
||||
# currently really needed, but will protect against areas and groups diverging
|
||||
# in a way that breaks keywording.
|
||||
office_hours = SessionFactory(
|
||||
name='some office hours',
|
||||
group=group.parent,
|
||||
meeting=meeting,
|
||||
type_id='other'
|
||||
)
|
||||
ts = office_hours.timeslotassignments.first().timeslot
|
||||
ts.type = office_hours.type
|
||||
ts.save()
|
||||
|
||||
assignments = meeting.schedule.assignments.all()
|
||||
orig_num_assignments = len(assignments)
|
||||
|
||||
# Set up historic groups if needed
|
||||
if historic:
|
||||
for a in assignments:
|
||||
if a.session != office_hours:
|
||||
a.session.historic_group = historic_group
|
||||
|
||||
# Execute the method under test
|
||||
tag_assignments_with_filter_keywords(assignments)
|
||||
|
||||
# Assert expected results
|
||||
self.assertEqual(len(assignments), orig_num_assignments, 'Should not change number of assignments')
|
||||
|
||||
if historic:
|
||||
expected_group = historic_group
|
||||
expected_area = historic_parent if historic == 'parent' else historic_group.parent
|
||||
else:
|
||||
expected_group = group
|
||||
expected_area = group.parent
|
||||
|
||||
for assignment in assignments:
|
||||
expected_filter_keywords = {assignment.timeslot.type.slug, assignment.session.type.slug}
|
||||
|
||||
if assignment.session == office_hours:
|
||||
expected_filter_keywords.update([
|
||||
group.parent.acronym,
|
||||
'officehours',
|
||||
'someofficehours',
|
||||
])
|
||||
else:
|
||||
expected_filter_keywords.update([
|
||||
expected_group.acronym,
|
||||
expected_area.acronym
|
||||
])
|
||||
if bof:
|
||||
expected_filter_keywords.add('bof')
|
||||
|
||||
self.assertCountEqual(
|
||||
assignment.filter_keywords,
|
||||
expected_filter_keywords,
|
||||
'Assignment has incorrect filter keywords'
|
||||
)
|
||||
|
||||
def test_tag_assignments_with_filter_keywords(self):
|
||||
self.do_test_tag_assignments_with_filter_keywords()
|
||||
self.do_test_tag_assignments_with_filter_keywords(historic='group')
|
||||
self.do_test_tag_assignments_with_filter_keywords(historic='parent')
|
||||
self.do_test_tag_assignments_with_filter_keywords(bof=True)
|
||||
self.do_test_tag_assignments_with_filter_keywords(bof=True, historic='group')
|
||||
self.do_test_tag_assignments_with_filter_keywords(bof=True, historic='parent')
|
|
@ -6,6 +6,7 @@ import time
|
|||
import datetime
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
from unittest import skipIf
|
||||
|
||||
import django
|
||||
|
@ -20,14 +21,16 @@ from ietf.doc.factories import DocumentFactory
|
|||
from ietf.group import colors
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.meeting.factories import SessionFactory
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
|
||||
from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
|
||||
Room, TimeSlot, Constraint, ConstraintName,
|
||||
Meeting, SchedulingEvent, SessionStatusName)
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.utils.test_runner import IetfLiveServerTestCase
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.test_runner import IetfLiveServerTestCase
|
||||
from ietf.utils.test_utils import assert_ical_response_is_valid
|
||||
from ietf import settings
|
||||
|
||||
skip_selenium = False
|
||||
|
@ -356,20 +359,6 @@ class AgendaTests(MeetingTestCase):
|
|||
expected_items = self.meeting.schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
self.assertGreater(len(expected_items), 0, 'Test setup generated an empty schedule')
|
||||
return expected_items
|
||||
|
||||
def test_agenda_view_displays_all_items(self):
|
||||
"""By default, all agenda items should be displayed"""
|
||||
self.login()
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
|
||||
|
||||
for item in self.get_expected_items():
|
||||
row_id = 'row-%s' % item.slug()
|
||||
try:
|
||||
item_row = self.driver.find_element_by_id(row_id)
|
||||
except NoSuchElementException:
|
||||
item_row = None
|
||||
self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
|
||||
self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed' % row_id)
|
||||
|
||||
def test_agenda_view_js_func_parse_query_params(self):
|
||||
"""Test parse_query_params() function"""
|
||||
|
@ -445,6 +434,7 @@ class AgendaTests(MeetingTestCase):
|
|||
self.login()
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + querystring)
|
||||
self.assert_agenda_item_visibility(visible_groups)
|
||||
self.assert_agenda_view_filter_matches_ics_filter(querystring)
|
||||
weekview_iframe = self.driver.find_element_by_id('weekview')
|
||||
if len(querystring) == 0:
|
||||
self.assertFalse(weekview_iframe.is_displayed(), 'Weekview should be hidden when filters off')
|
||||
|
@ -454,72 +444,170 @@ class AgendaTests(MeetingTestCase):
|
|||
self.assert_weekview_item_visibility(visible_groups)
|
||||
self.driver.switch_to.default_content()
|
||||
|
||||
def test_agenda_view_filter_show_none(self):
|
||||
"""Filtered agenda view should display only matching rows (no group selected)"""
|
||||
self.do_agenda_view_filter_test('?show=', [])
|
||||
def test_agenda_view_filter_default(self):
|
||||
"""Filtered agenda view should display only matching rows (all groups selected)"""
|
||||
self.do_agenda_view_filter_test('', None) # None means all should be visible
|
||||
|
||||
def test_agenda_view_filter_show_one(self):
|
||||
"""Filtered agenda view should display only matching rows (one group selected)"""
|
||||
def test_agenda_view_filter_show_group(self):
|
||||
self.do_agenda_view_filter_test('?show=', [])
|
||||
self.do_agenda_view_filter_test('?show=mars', ['mars'])
|
||||
self.do_agenda_view_filter_test('?show=mars,ames', ['mars', 'ames'])
|
||||
|
||||
def test_agenda_view_filter_show_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
self.do_agenda_view_filter_test('?show=%s' % area.acronym, ['ames', 'mars'])
|
||||
|
||||
def test_agenda_view_filter_show_two(self):
|
||||
"""Filtered agenda view should display only matching rows (two groups selected)"""
|
||||
self.do_agenda_view_filter_test('?show=mars,ames', ['mars', 'ames'])
|
||||
def test_agenda_view_filter_show_type(self):
|
||||
self.do_agenda_view_filter_test('?show=reg,break', ['secretariat'])
|
||||
|
||||
def test_agenda_view_filter_all(self):
|
||||
"""Filtered agenda view should display only matching rows (all groups selected)"""
|
||||
self.do_agenda_view_filter_test('', None) # None means all should be visible
|
||||
def test_agenda_view_filter_show_bof(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
self.do_agenda_view_filter_test('?show=bof', ['mars'])
|
||||
self.do_agenda_view_filter_test('?show=bof,ames', ['ames', 'mars'])
|
||||
|
||||
def test_agenda_view_filter_show_ad_office_hours(self):
|
||||
area = GroupFactory(type_id='area')
|
||||
SessionFactory(
|
||||
meeting__type_id='ietf',
|
||||
type_id='other',
|
||||
group=area,
|
||||
name='%s Office Hours' % area.acronym,
|
||||
)
|
||||
self.do_agenda_view_filter_test('?show=adofficehours', [area.acronym])
|
||||
|
||||
def test_agenda_view_filter_hide_group(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
area = mars.parent
|
||||
|
||||
# Nothing shown, nothing visible
|
||||
self.do_agenda_view_filter_test('?hide=mars', [])
|
||||
|
||||
# Group shown
|
||||
self.do_agenda_view_filter_test('?show=ames,mars&hide=mars', ['ames'])
|
||||
|
||||
# Area shown
|
||||
self.do_agenda_view_filter_test('?show=%s&hide=mars' % area.acronym, ['ames'])
|
||||
|
||||
# Type shown
|
||||
self.do_agenda_view_filter_test('?show=plenary,regular&hide=mars', ['ames','ietf'])
|
||||
|
||||
# bof shown
|
||||
self.do_agenda_view_filter_test('?show=bof&hide=mars', [])
|
||||
|
||||
def test_agenda_view_filter_hide(self):
|
||||
self.do_agenda_view_filter_test('?hide=ietf', [])
|
||||
|
||||
def test_agenda_view_filter_hide_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
area = mars.parent
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=%s' % area.acronym, [])
|
||||
SessionFactory(
|
||||
meeting__type_id='ietf',
|
||||
type_id='other',
|
||||
group=area,
|
||||
name='%s Office Hours' % area.acronym,
|
||||
)
|
||||
|
||||
def test_agenda_view_filter_show_and_hide(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=ietf', ['mars'])
|
||||
# Nothing shown
|
||||
self.do_agenda_view_filter_test('?hide=%s' % area.acronym, [])
|
||||
|
||||
# Group shown
|
||||
self.do_agenda_view_filter_test('?show=ames,mars&hide=%s' % area.acronym, [])
|
||||
|
||||
# Area shown
|
||||
self.do_agenda_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym), [])
|
||||
|
||||
def test_agenda_view_filter_show_and_hide_same_group(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=mars', [])
|
||||
# Type shown
|
||||
self.do_agenda_view_filter_test('?show=plenary,regular&hide=%s' % area.acronym, ['ietf'])
|
||||
|
||||
def test_agenda_view_filter_showtypes(self):
|
||||
self.do_agenda_view_filter_test('?showtypes=plenary', ['ietf']) # ietf has a plenary session
|
||||
# bof shown
|
||||
self.do_agenda_view_filter_test('?show=bof&hide=%s' % area.acronym, [])
|
||||
|
||||
# AD office hours shown
|
||||
self.do_agenda_view_filter_test('?show=adofficehours&hide=%s' % area.acronym, [])
|
||||
|
||||
def test_agenda_view_filter_hide_type(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
area = mars.parent
|
||||
SessionFactory(
|
||||
meeting__type_id='ietf',
|
||||
type_id='other',
|
||||
group=area,
|
||||
name='%s Office Hours' % area.acronym,
|
||||
)
|
||||
|
||||
# Nothing shown
|
||||
self.do_agenda_view_filter_test('?hide=plenary', [])
|
||||
|
||||
# Group shown
|
||||
self.do_agenda_view_filter_test('?show=ietf,ames&hide=plenary', ['ames'])
|
||||
|
||||
# Area shown
|
||||
self.do_agenda_view_filter_test('?show=%s&hide=regular' % area.acronym, [])
|
||||
|
||||
# Type shown
|
||||
self.do_agenda_view_filter_test('?show=plenary,regular&hide=plenary', ['ames', 'mars'])
|
||||
|
||||
# bof shown
|
||||
self.do_agenda_view_filter_test('?show=bof&hide=regular', [])
|
||||
|
||||
# AD office hours shown
|
||||
self.do_agenda_view_filter_test('?show=adofficehours&hide=other', [])
|
||||
|
||||
def test_agenda_view_filter_hide_bof(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
area = mars.parent
|
||||
|
||||
# Nothing shown
|
||||
self.do_agenda_view_filter_test('?hide=bof', [])
|
||||
|
||||
# Group shown
|
||||
self.do_agenda_view_filter_test('?show=mars,ames&hide=bof', ['ames'])
|
||||
|
||||
# Area shown
|
||||
self.do_agenda_view_filter_test('?show=%s&hide=bof' % area.acronym, ['ames'])
|
||||
|
||||
# Type shown
|
||||
self.do_agenda_view_filter_test('?show=regular&hide=bof', ['ames'])
|
||||
|
||||
# bof shown
|
||||
self.do_agenda_view_filter_test('?show=bof&hide=bof', [])
|
||||
|
||||
def test_agenda_view_filter_hide_ad_office_hours(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars.state_id = 'bof'
|
||||
mars.save()
|
||||
area = mars.parent
|
||||
SessionFactory(
|
||||
meeting__type_id='ietf',
|
||||
type_id='other',
|
||||
group=area,
|
||||
name='%s Office Hours' % area.acronym,
|
||||
)
|
||||
|
||||
# Nothing shown
|
||||
self.do_agenda_view_filter_test('?hide=adofficehours', [])
|
||||
|
||||
# Area shown
|
||||
self.do_agenda_view_filter_test('?show=%s&hide=adofficehours' % area.acronym, ['ames', 'mars'])
|
||||
|
||||
# Type shown
|
||||
self.do_agenda_view_filter_test('?show=plenary,other&hide=adofficehours', ['ietf'])
|
||||
|
||||
# AD office hours shown
|
||||
self.do_agenda_view_filter_test('?show=adofficehours&hide=adofficehours', [])
|
||||
|
||||
def test_agenda_view_filter_whitespace(self):
|
||||
self.do_agenda_view_filter_test('?show= ames , mars &hide= mars ', ['ames'])
|
||||
|
||||
def test_agenda_view_filter_hidetypes(self):
|
||||
self.do_agenda_view_filter_test('?hidetypes=plenary', [])
|
||||
|
||||
def test_agenda_view_filter_showtypes_and_hidetypes(self):
|
||||
self.do_agenda_view_filter_test('?showtypes=plenary&hidetypes=regular', ['ietf']) # ietf has a plenary session
|
||||
|
||||
def test_agenda_view_filter_showtypes_and_hidetypes_same_type(self):
|
||||
self.do_agenda_view_filter_test('?showtypes=plenary&hidetypes=plenary', [])
|
||||
|
||||
def test_agenda_view_filter_show_and_showtypes(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&showtypes=plenary', ['mars', 'ietf']) # ietf has a plenary session
|
||||
|
||||
def test_agenda_view_filter_show_and_hidetypes(self):
|
||||
self.do_agenda_view_filter_test('?show=ietf,mars&hidetypes=plenary', ['mars']) # ietf has a plenary session
|
||||
|
||||
def test_agenda_view_filter_hide_and_hidetypes(self):
|
||||
self.do_agenda_view_filter_test('?hide=ietf,mars&hidetypes=plenary', [])
|
||||
|
||||
def test_agenda_view_filter_show_hide_and_showtypes(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=ames&showtypes=plenary,regular', ['mars', 'ietf']) # ietf has plenary session
|
||||
|
||||
def test_agenda_view_filter_show_hide_and_hidetypes(self):
|
||||
self.do_agenda_view_filter_test('?show=mars,ietf&hide=ames&hidetypes=plenary', ['mars']) # ietf has plenary session
|
||||
|
||||
def test_agenda_view_filter_all_params(self):
|
||||
self.do_agenda_view_filter_test('?show=secretariat,ietf&hide=ames&showtypes=regular&hidetypes=plenary',
|
||||
['secretariat', 'mars'])
|
||||
|
||||
def assert_agenda_item_visibility(self, visible_groups=None):
|
||||
"""Assert that correct items are visible in current browser window
|
||||
|
||||
|
@ -634,6 +722,81 @@ class AgendaTests(MeetingTestCase):
|
|||
# no assertion here - if WebDriverWait raises an exception, the test will fail.
|
||||
# We separately test whether this URL will filter correctly.
|
||||
|
||||
def session_from_agenda_row_id(self, row_id):
|
||||
"""Find session corresponding to a row in the agenda HTML"""
|
||||
components = row_id.split('-', 8)
|
||||
# for regular session:
|
||||
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<parent acro>-<group acro>
|
||||
# for plenary session:
|
||||
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-1plenary-<group acro>
|
||||
# for others (break, reg, other):
|
||||
# row-<meeting#>-<year>-<month>-<day>-<DoW>-<HHMM>-<group acro>-<session name slug>
|
||||
meeting_number = components[1]
|
||||
start_time = datetime.datetime(
|
||||
year=int(components[2]),
|
||||
month=int(components[3]),
|
||||
day=int(components[4]),
|
||||
hour=int(components[6][0:2]),
|
||||
minute=int(components[6][2:4]),
|
||||
)
|
||||
# If labeled as plenary, it's plenary...
|
||||
if components[7] == '1plenary':
|
||||
session_type = 'plenary'
|
||||
group = Group.objects.get(acronym=components[8])
|
||||
else:
|
||||
# If not a plenary, see if the last component is a group
|
||||
try:
|
||||
group = Group.objects.get(acronym=components[8])
|
||||
except Group.DoesNotExist:
|
||||
# Last component was not a group, so this must not be a regular session
|
||||
session_type = 'other'
|
||||
group = Group.objects.get(acronym=components[7])
|
||||
else:
|
||||
# Last component was a group, this is a regular session
|
||||
session_type = 'regular'
|
||||
|
||||
meeting = Meeting.objects.get(number=meeting_number)
|
||||
possible_assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[meeting.schedule, meeting.schedule.base],
|
||||
timeslot__time=start_time,
|
||||
)
|
||||
if session_type == 'other':
|
||||
possible_sessions = [pa.session for pa in possible_assignments.filter(
|
||||
timeslot__type_id__in=['break', 'reg', 'other'], session__group=group
|
||||
) if slugify(pa.session.name) == components[8]]
|
||||
if len(possible_sessions) != 1:
|
||||
raise ValueError('No unique matching session for row %s (found %d)' % (
|
||||
row_id, len(possible_sessions)
|
||||
))
|
||||
session = possible_sessions[0]
|
||||
else:
|
||||
session = possible_assignments.filter(
|
||||
timeslot__type_id=session_type
|
||||
).get(session__group=group).session
|
||||
return session, possible_assignments.get(session=session).timeslot
|
||||
|
||||
def assert_agenda_view_filter_matches_ics_filter(self, filter_string):
|
||||
"""The agenda view and ics view should show the same events for a given filter
|
||||
|
||||
This must be called after using self.driver.get to load the agenda page
|
||||
to be checked.
|
||||
"""
|
||||
ics_url = self.absreverse('ietf.meeting.views.agenda_ical')
|
||||
|
||||
# parse out the events
|
||||
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"')
|
||||
visible_rows = [r for r in agenda_rows if r.is_displayed()]
|
||||
sessions = [self.session_from_agenda_row_id(row.get_attribute("id"))
|
||||
for row in visible_rows]
|
||||
r = self.client.get(ics_url + filter_string)
|
||||
# verify that all expected sessions are found
|
||||
expected_uids = [
|
||||
'ietf-%s-%s-%s' % (session.meeting.number, timeslot.pk, session.group.acronym)
|
||||
for (session, timeslot) in sessions
|
||||
]
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_uids=expected_uids,
|
||||
expected_event_count=len(sessions))
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class InterimTests(MeetingTestCase):
|
||||
|
@ -644,6 +807,17 @@ class InterimTests(MeetingTestCase):
|
|||
settings.AGENDA_PATH = self.materials_dir
|
||||
self.meeting = make_meeting_test_data(create_interims=True)
|
||||
|
||||
# Create a group with a plenary interim session for testing type filters
|
||||
somegroup = GroupFactory(acronym='sg', name='Some Group')
|
||||
sg_interim = make_interim_meeting(somegroup, datetime.date.today() + datetime.timedelta(days=20))
|
||||
sg_sess = sg_interim.session_set.first()
|
||||
sg_slot = sg_sess.timeslotassignments.first().timeslot
|
||||
sg_sess.type_id = 'plenary'
|
||||
sg_slot.type_id = 'plenary'
|
||||
sg_sess.save()
|
||||
sg_slot.save()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
settings.AGENDA_PATH = self.saved_agenda_path
|
||||
shutil.rmtree(self.materials_dir)
|
||||
|
@ -684,6 +858,11 @@ class InterimTests(MeetingTestCase):
|
|||
m.calendar_label = 'IETF %s' % m.number
|
||||
return meetings
|
||||
|
||||
def find_upcoming_meeting_entries(self):
|
||||
return self.driver.find_elements_by_css_selector(
|
||||
'table#upcoming-meeting-table a.ietf-meeting-link, table#upcoming-meeting-table a.interim-meeting-link'
|
||||
)
|
||||
|
||||
def assert_upcoming_meeting_visibility(self, visible_meetings=None):
|
||||
"""Assert that correct items are visible in current browser window
|
||||
|
||||
|
@ -692,20 +871,19 @@ class InterimTests(MeetingTestCase):
|
|||
expected = {mtg.number for mtg in visible_meetings}
|
||||
not_visible = set()
|
||||
unexpected = set()
|
||||
entries = self.driver.find_elements_by_css_selector(
|
||||
'table#upcoming-meeting-table > tbody > tr.entry'
|
||||
)
|
||||
entries = self.find_upcoming_meeting_entries()
|
||||
for entry in entries:
|
||||
nums = [n for n in expected if n in entry.text]
|
||||
entry_text = entry.get_attribute('innerHTML').strip() # gets text, even if element is hidden
|
||||
nums = [n for n in expected if n in entry_text]
|
||||
self.assertLessEqual(len(nums), 1, 'Multiple matching meeting numbers')
|
||||
if len(nums) > 0: # asserted that it's at most 1, so if it's not 0, it's 1.
|
||||
expected.remove(nums[0])
|
||||
if not entry.is_displayed():
|
||||
not_visible.add(nums[0])
|
||||
continue
|
||||
# Found an unexpected row - this is ok as long as it's hidden
|
||||
# Found an unexpected row - this is only a problem if it is visible
|
||||
if entry.is_displayed():
|
||||
unexpected.add(entry.text)
|
||||
unexpected.add(entry_text)
|
||||
|
||||
self.assertEqual(expected, set(), "Missing entries for expected iterim meetings.")
|
||||
self.assertEqual(not_visible, set(), "Hidden rows for expected interim meetings.")
|
||||
|
@ -754,6 +932,7 @@ class InterimTests(MeetingTestCase):
|
|||
self.driver.get(self.absreverse('ietf.meeting.views.upcoming') + querystring)
|
||||
self.assert_upcoming_meeting_visibility(visible_meetings)
|
||||
self.assert_upcoming_meeting_calendar(visible_meetings)
|
||||
self.assert_upcoming_view_filter_matches_ics_filter(querystring)
|
||||
|
||||
# Check the ical links
|
||||
simplified_querystring = querystring.replace(' ', '%20') # encode spaces'
|
||||
|
@ -762,32 +941,151 @@ class InterimTests(MeetingTestCase):
|
|||
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')
|
||||
self.assertIn(simplified_querystring, webcal_link.get_attribute('href'))
|
||||
|
||||
def test_upcoming_view_displays_all_interims(self):
|
||||
def assert_upcoming_view_filter_matches_ics_filter(self, filter_string):
|
||||
"""The upcoming view and ics view should show matching events for a given filter
|
||||
|
||||
The upcoming ics view shows more detail than the upcoming view, so this
|
||||
test expands the upcoming meeting list into the corresponding set of expected
|
||||
sessions.
|
||||
|
||||
This must be called after using self.driver.get to load the upcoming page
|
||||
to be checked.
|
||||
"""
|
||||
ics_url = self.absreverse('ietf.meeting.views.upcoming_ical')
|
||||
|
||||
# parse out the meetings shown on the upcoming view
|
||||
upcoming_meetings = self.find_upcoming_meeting_entries()
|
||||
visible_meetings = [mtg for mtg in upcoming_meetings if mtg.is_displayed()]
|
||||
|
||||
# Have list of meetings, now get sessions that should be shown
|
||||
expected_ietfs = []
|
||||
expected_interim_sessions = []
|
||||
expected_schedules = []
|
||||
for meeting_elt in visible_meetings:
|
||||
# meeting_elt is an anchor element
|
||||
label_text = meeting_elt.get_attribute('innerHTML')
|
||||
match = re.search(r'(?P<ietf>IETF\s+)?(?P<number>\S+)', label_text)
|
||||
meeting = Meeting.objects.get(number=match.group('number'))
|
||||
if match.group('ietf'):
|
||||
expected_ietfs.append(meeting)
|
||||
else:
|
||||
expected_interim_sessions.extend([s.pk for s in meeting.session_set.all()])
|
||||
if meeting.schedule:
|
||||
expected_schedules.extend([meeting.schedule, meeting.schedule.base])
|
||||
|
||||
# Now find the sessions we expect to see - should match the upcoming_ical view
|
||||
expected_assignments = list(SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=expected_schedules,
|
||||
session__in=expected_interim_sessions,
|
||||
timeslot__time__gte=datetime.date.today(),
|
||||
))
|
||||
# The UID formats should match those in the upcoming.ics template
|
||||
expected_uids = [
|
||||
'ietf-%s-%s' % (item.session.meeting.number, item.timeslot.pk)
|
||||
for item in expected_assignments
|
||||
] + [
|
||||
'ietf-%s' % (ietf.number) for ietf in expected_ietfs
|
||||
]
|
||||
r = self.client.get(ics_url + filter_string)
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_uids=expected_uids,
|
||||
expected_event_count=len(expected_uids))
|
||||
|
||||
def test_upcoming_view_default(self):
|
||||
"""By default, all upcoming interims and IETF meetings should be displayed"""
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims())
|
||||
self.do_upcoming_view_filter_test('', meetings)
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('', ietf_meetings.union(self.displayed_interims()))
|
||||
|
||||
def test_upcoming_view_filter_show_none(self):
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?show=', meetings)
|
||||
def test_upcoming_view_filter_show_group(self):
|
||||
# Show none
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?show=', ietf_meetings)
|
||||
|
||||
def test_upcoming_view_filter_show_one(self):
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims(groups=['mars']))
|
||||
self.do_upcoming_view_filter_test('?show=mars', meetings)
|
||||
# Show one
|
||||
self.do_upcoming_view_filter_test('?show=mars',
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['mars'])
|
||||
))
|
||||
|
||||
# Show two
|
||||
self.do_upcoming_view_filter_test('?show=mars,ames',
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['mars', 'ames'])
|
||||
))
|
||||
|
||||
def test_upcoming_view_filter_show_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims(groups=['mars', 'ames']))
|
||||
self.do_upcoming_view_filter_test('?show=%s' % area.acronym, meetings)
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?show=%s' % area.acronym,
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['mars', 'ames'])
|
||||
))
|
||||
|
||||
def test_upcoming_view_filter_show_two(self):
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims(groups=['mars', 'ames']))
|
||||
self.do_upcoming_view_filter_test('?show=mars,ames', meetings)
|
||||
def test_upcoming_view_filter_show_type(self):
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?show=plenary',
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['sg'])
|
||||
))
|
||||
|
||||
def test_upcoming_view_filter_hide_group(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
|
||||
# Without anything shown, should see only ietf meetings
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?hide=mars', ietf_meetings)
|
||||
|
||||
# With group shown
|
||||
self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars',
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['ames'])
|
||||
))
|
||||
# With area shown
|
||||
self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym,
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['ames'])
|
||||
))
|
||||
|
||||
# With type shown
|
||||
self.do_upcoming_view_filter_test('?show=plenary&hide=sg',
|
||||
ietf_meetings)
|
||||
|
||||
def test_upcoming_view_filter_hide_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
|
||||
# Without anything shown, should see only ietf meetings
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?hide=%s' % area.acronym, ietf_meetings)
|
||||
|
||||
# With area shown
|
||||
self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym),
|
||||
ietf_meetings)
|
||||
|
||||
# With group shown
|
||||
self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, ietf_meetings)
|
||||
|
||||
# With type shown
|
||||
self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym, ietf_meetings)
|
||||
|
||||
def test_upcoming_view_filter_hide_type(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
|
||||
# Without anything shown, should see only ietf meetings
|
||||
ietf_meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?hide=regular', ietf_meetings)
|
||||
|
||||
# With group shown
|
||||
self.do_upcoming_view_filter_test('?show=mars&hide=regular', ietf_meetings)
|
||||
|
||||
# With type shown
|
||||
self.do_upcoming_view_filter_test('?show=plenary,regular&hide=%s' % area.acronym,
|
||||
ietf_meetings.union(
|
||||
self.displayed_interims(groups=['sg'])
|
||||
))
|
||||
|
||||
def test_upcoming_view_filter_whitespace(self):
|
||||
"""Whitespace in filter lists should be ignored"""
|
||||
|
@ -795,24 +1093,6 @@ class InterimTests(MeetingTestCase):
|
|||
meetings.update(self.displayed_interims(groups=['mars']))
|
||||
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings)
|
||||
|
||||
def test_upcoming_view_filter_hide(self):
|
||||
# Not a useful application, but test for completeness...
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?hide=mars', meetings)
|
||||
|
||||
def test_upcoming_view_filter_hide_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, meetings)
|
||||
|
||||
def test_upcoming_view_filter_show_and_hide_same_group(self):
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims(groups=['mars']))
|
||||
self.do_upcoming_view_filter_test('?show=mars,ames&hide=ames', meetings)
|
||||
|
||||
# The upcoming meetings view does not handle showtypes / hidetypes
|
||||
|
||||
# The following are useful debugging tools
|
||||
|
||||
# If you add this to a LiveServerTestCase and run just this test, you can browse
|
||||
|
|
|
@ -23,6 +23,7 @@ from django.contrib.auth.models import User
|
|||
from django.test import Client, override_settings
|
||||
from django.db.models import F
|
||||
from django.http import QueryDict
|
||||
from django.template import Context, Template
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -51,7 +52,7 @@ from ietf.meeting.factories import ( SessionFactory, SessionPresentationFactory,
|
|||
MeetingFactory, FloorPlanFactory, TimeSlotFactory, SlideSubmissionFactory )
|
||||
from ietf.doc.factories import DocumentFactory, WgDraftFactory
|
||||
from ietf.submit.tests import submission_file
|
||||
|
||||
from ietf.utils.test_utils import assert_ical_response_is_valid
|
||||
|
||||
if os.path.exists(settings.GHOSTSCRIPT_COMMAND):
|
||||
skip_pdf_tests = False
|
||||
|
@ -63,32 +64,6 @@ else:
|
|||
print(" "+skip_message)
|
||||
|
||||
|
||||
def assert_ical_response_is_valid(test_inst, response, expected_event_summaries=None, expected_event_count=None):
|
||||
"""Validate an HTTP response containing iCal data
|
||||
|
||||
Based on RFC2445, but not exhaustive by any means. Assumes a single iCalendar object. Checks that
|
||||
expected_event_summaries are found, but other events are allowed to be present. Specify the
|
||||
expected_event_count if you want to reject additional events.
|
||||
"""
|
||||
test_inst.assertEqual(response.get('Content-Type'), "text/calendar")
|
||||
|
||||
# Validate iCalendar object
|
||||
test_inst.assertContains(response, 'BEGIN:VCALENDAR', count=1)
|
||||
test_inst.assertContains(response, 'END:VCALENDAR', count=1)
|
||||
test_inst.assertContains(response, 'PRODID:', count=1)
|
||||
test_inst.assertContains(response, 'VERSION', count=1)
|
||||
|
||||
# Validate event objects
|
||||
if expected_event_summaries is not None:
|
||||
for summary in expected_event_summaries:
|
||||
test_inst.assertContains(response, 'SUMMARY:' + summary)
|
||||
|
||||
if expected_event_count is not None:
|
||||
test_inst.assertContains(response, 'BEGIN:VEVENT', count=expected_event_count)
|
||||
test_inst.assertContains(response, 'END:VEVENT', count=expected_event_count)
|
||||
test_inst.assertContains(response, 'UID', count=expected_event_count)
|
||||
|
||||
|
||||
class MeetingTests(TestCase):
|
||||
def setUp(self):
|
||||
self.materials_dir = self.tempdir('materials')
|
||||
|
@ -257,7 +232,28 @@ class MeetingTests(TestCase):
|
|||
r = self.client.get(urlreverse("ietf.meeting.views.week_view", kwargs=dict(num=meeting.number)))
|
||||
self.assertContains(r, 'CANCELLED')
|
||||
self.assertContains(r, session.group.acronym)
|
||||
self.assertContains(r, slot.location.name)
|
||||
self.assertContains(r, slot.location.name)
|
||||
|
||||
def test_meeting_agenda_filters_ignored(self):
|
||||
"""The agenda view should ignore filter querystrings
|
||||
|
||||
(They are handled by javascript on the front end)
|
||||
"""
|
||||
meeting = make_meeting_test_data()
|
||||
expected_items = meeting.schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
expected_rows = ['row-%s' % item.slug() for item in expected_items]
|
||||
|
||||
r = self.client.get(urlreverse('ietf.meeting.views.agenda'))
|
||||
for row_id in expected_rows:
|
||||
self.assertContains(r, row_id)
|
||||
|
||||
r = self.client.get(urlreverse('ietf.meeting.views.agenda') + '?show=mars')
|
||||
for row_id in expected_rows:
|
||||
self.assertContains(r, row_id)
|
||||
|
||||
r = self.client.get(urlreverse('ietf.meeting.views.agenda') + '?show=mars&hide=ames,mars,plenary,ietf,bof')
|
||||
for row_id in expected_rows:
|
||||
self.assertContains(r, row_id)
|
||||
|
||||
def test_agenda_iab_session(self):
|
||||
date = datetime.date.today()
|
||||
|
@ -657,7 +653,7 @@ class MeetingTests(TestCase):
|
|||
'Customized schedule' button.
|
||||
"""
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
|
||||
# get the agenda
|
||||
url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number))
|
||||
r = self.client.get(url)
|
||||
|
@ -665,16 +661,22 @@ class MeetingTests(TestCase):
|
|||
# Check that it has the links we expect
|
||||
ical_url = urlreverse('ietf.meeting.views.agenda_ical', kwargs=dict(num=meeting.number))
|
||||
q = PyQuery(r.content)
|
||||
content = q('#content').html().lower() # don't care about case
|
||||
# Should be a 'non-area events' link showing appropriate types
|
||||
self.assertIn('%s?showtypes=plenary,other' % ical_url, content)
|
||||
content = q('#content').html()
|
||||
|
||||
assignments = meeting.schedule.assignments.exclude(timeslot__type__in=['lead', 'offagenda'])
|
||||
|
||||
# Assume the test meeting is not using historic groups
|
||||
groups = [a.session.group for a in assignments if a.session is not None]
|
||||
for g in groups:
|
||||
if g.parent_id is not None:
|
||||
self.assertIn('%s?show=%s' % (ical_url, g.parent.acronym.lower()), content)
|
||||
|
||||
# Should be a 'non-area events' link showing appropriate types
|
||||
non_area_labels = [
|
||||
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
|
||||
]
|
||||
self.assertIn('%s?show=%s' % (ical_url, ','.join(non_area_labels).lower()), content)
|
||||
|
||||
def test_parse_agenda_filter_params(self):
|
||||
def _r(show=(), hide=(), showtypes=(), hidetypes=()):
|
||||
"""Helper to create expected result dict"""
|
||||
|
@ -723,7 +725,8 @@ class MeetingTests(TestCase):
|
|||
expected_event_summaries=expected_session_summaries,
|
||||
expected_event_count=len(expected_session_summaries))
|
||||
|
||||
def test_ical_filter_default(self):
|
||||
def test_ical_filter(self):
|
||||
# Just a quick check of functionality - permutations tested via tests_js.AgendaTests
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
|
@ -736,179 +739,13 @@ class MeetingTests(TestCase):
|
|||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_show(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=mars',
|
||||
expected_session_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_hide(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?hide=ietf',
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_area(self):
|
||||
meeting = make_meeting_test_data()
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=%s' % area.acronym,
|
||||
querystring='?show=plenary,secretariat,ames&hide=reg',
|
||||
expected_session_summaries=[
|
||||
'Morning Break',
|
||||
'IETF Plenary',
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_hide_area(self):
|
||||
meeting = make_meeting_test_data()
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=mars&hide=%s' % area.acronym,
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_and_hide(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=ames&hide=mars',
|
||||
expected_session_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_and_hide_same_group(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=ames&hide=ames',
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_showtypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
# Show break/plenary types
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?showtypes=break,plenary',
|
||||
expected_session_summaries=[
|
||||
'IETF Plenary',
|
||||
'Morning Break',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_hidetypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?hidetypes=plenary',
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_showtypes_and_hidetypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?showtypes=break&hidetypes=plenary',
|
||||
expected_session_summaries=[
|
||||
'Morning Break',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_showtypes_and_hidetypes_same_type(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?showtypes=plenary&hidetypes=plenary',
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_and_showtypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=mars&showtypes=plenary',
|
||||
expected_session_summaries=[
|
||||
'IETF Plenary',
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_hide_and_showtypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?hide=ames&showtypes=regular',
|
||||
expected_session_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_and_hidetypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=ietf,mars&hidetypes=plenary',
|
||||
expected_session_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_hide_and_hidetypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?hide=ietf,mars&hidetypes=plenary',
|
||||
expected_session_summaries=[]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_hide_and_showtypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
# ames regular session should be suppressed
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=ietf&hide=ames&showtypes=regular',
|
||||
expected_session_summaries=[
|
||||
'IETF Plenary',
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_show_hide_and_hidetypes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
# ietf plenary session should be suppressed
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=mars,ietf&hide=ames&hidetypes=plenary',
|
||||
expected_session_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
def test_ical_filter_all_params(self):
|
||||
meeting = make_meeting_test_data()
|
||||
# should include Morning Break / Registration due to secretariat in show list
|
||||
# should include mars SIG because regular in showtypes list
|
||||
# should not include IETF plenary because plenary in hidetypes list
|
||||
# should not show ames SIG because ames in hide list
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='?show=secretariat,ietf&hide=ames&showtypes=regular&hidetypes=plenary',
|
||||
expected_session_summaries=[
|
||||
'Morning Break',
|
||||
'Registration',
|
||||
'mars - Martian Special Interest Group',
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -2197,141 +2034,30 @@ class InterimTests(TestCase):
|
|||
|
||||
# test_upcoming_filters_ignored removed - we _don't_ want to ignore filters now, and the test passed because it wasn't testing the filtering anyhow (which requires testing the js).
|
||||
|
||||
def do_upcoming_ical_test(self, querystring=None, create_meeting=True):
|
||||
if create_meeting:
|
||||
make_meeting_test_data(create_interims=True)
|
||||
|
||||
# Create a group with a plenary interim session for testing type filters
|
||||
somegroup = GroupFactory(acronym='sg', name='Some Group')
|
||||
sg_interim = make_interim_meeting(somegroup, datetime.date.today() + datetime.timedelta(days=20))
|
||||
sg_sess = sg_interim.session_set.first()
|
||||
sg_slot = sg_sess.timeslotassignments.first().timeslot
|
||||
sg_sess.type_id = 'plenary'
|
||||
sg_slot.type_id = 'plenary'
|
||||
sg_sess.save()
|
||||
sg_slot.save()
|
||||
|
||||
url = urlreverse("ietf.meeting.views.upcoming_ical")
|
||||
if querystring is not None:
|
||||
url += '?' + querystring
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return r
|
||||
|
||||
def test_upcoming_ical(self):
|
||||
meeting = make_meeting_test_data(create_interims=True)
|
||||
populate_important_dates(meeting)
|
||||
r = self.do_upcoming_ical_test(create_meeting=False)
|
||||
|
||||
# Expect events for important dates plus 4 - one for each WG and one for the IETF meeting
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'mars - Martian Special Interest Group',
|
||||
'sg - Some Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=4 + meeting.importantdate_set.count())
|
||||
|
||||
def test_upcoming_ical_filter_show(self):
|
||||
r = self.do_upcoming_ical_test('show=mars,ames')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=3)
|
||||
|
||||
def test_upcoming_ical_filter_hide(self):
|
||||
r = self.do_upcoming_ical_test('hide=mars')
|
||||
assert_ical_response_is_valid(self, r, expected_event_summaries=['IETF 72'], expected_event_count=1)
|
||||
|
||||
def test_upcoming_ical_filter_show_and_hide(self):
|
||||
r = self.do_upcoming_ical_test('show=mars,ames&hide=mars')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=2)
|
||||
|
||||
def test_upcoming_ical_filter_showtypes(self):
|
||||
r = self.do_upcoming_ical_test('showtypes=regular')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'mars - Martian Special Interest Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=3)
|
||||
|
||||
def test_upcoming_ical_filter_hidetypes(self):
|
||||
r = self.do_upcoming_ical_test('hidetypes=regular')
|
||||
assert_ical_response_is_valid(self, r, expected_event_summaries=['IETF 72'])
|
||||
|
||||
def test_upcoming_ical_filter_showtypes_and_hidetypes(self):
|
||||
r = self.do_upcoming_ical_test('showtypes=plenary,regular&hidetypes=regular')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'sg - Some Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=2)
|
||||
|
||||
def test_upcoming_ical_filter_show_and_showtypes(self):
|
||||
r = self.do_upcoming_ical_test('show=mars&showtypes=plenary')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
'sg - Some Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=3)
|
||||
url = urlreverse("ietf.meeting.views.upcoming_ical")
|
||||
|
||||
def test_upcoming_ical_filter_show_and_hidetypes(self):
|
||||
r = self.do_upcoming_ical_test('show=mars,sg&hidetypes=regular')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'sg - Some Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=2)
|
||||
r = self.client.get(url)
|
||||
|
||||
def test_upcoming_ical_filter_hide_and_showtypes(self):
|
||||
r = self.do_upcoming_ical_test('hide=mars&showtypes=regular')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# Expect events for important dates plus 3 - one for each WG and one for the IETF meeting
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'mars - Martian Special Interest Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=2)
|
||||
|
||||
def test_upcoming_ical_filter_hide_and_hidetypes(self):
|
||||
r = self.do_upcoming_ical_test('hide=mars&hidetypes=regular')
|
||||
assert_ical_response_is_valid(self, r, expected_event_summaries=['IETF 72'], expected_event_count=1)
|
||||
expected_event_count=3 + meeting.importantdate_set.count())
|
||||
|
||||
def test_upcoming_ical_filter_show_hide_and_showtypes(self):
|
||||
r = self.do_upcoming_ical_test('show=ames&hide=mars&showtypes=regular,plenary')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'sg - Some Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=3)
|
||||
def test_upcoming_ical_filter(self):
|
||||
# Just a quick check of functionality - details tested by test_js.InterimTests
|
||||
make_meeting_test_data(create_interims=True)
|
||||
url = urlreverse("ietf.meeting.views.upcoming_ical")
|
||||
r = self.client.get(url + '?show=mars')
|
||||
|
||||
def test_upcoming_ical_filter_show_hide_and_hidetypes(self):
|
||||
r = self.do_upcoming_ical_test('show=ames,sg&hide=mars&hidetypes=regular')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'sg - Some Group',
|
||||
'IETF 72'
|
||||
],
|
||||
expected_event_count=2)
|
||||
|
||||
def test_upcoming_ical_filter_all_params(self):
|
||||
r = self.do_upcoming_ical_test('show=sg&hide=ames&showtypes=regular&hidetypes=plenary')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
|
@ -2339,27 +2065,16 @@ class InterimTests(TestCase):
|
|||
],
|
||||
expected_event_count=2)
|
||||
|
||||
def test_upcoming_ical_filter_show_area(self):
|
||||
make_meeting_test_data(create_interims=True)
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
r = self.do_upcoming_ical_test('show=%s' % area.acronym,
|
||||
create_meeting=False)
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'mars - Martian Special Interest Group',
|
||||
'IETF 72',
|
||||
],
|
||||
expected_event_count=3)
|
||||
|
||||
def test_upcoming_ical_filter_hide_area(self):
|
||||
make_meeting_test_data(create_interims=True)
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
r = self.do_upcoming_ical_test('show=mars&hide=%s' % area.acronym,
|
||||
create_meeting=False)
|
||||
assert_ical_response_is_valid(self, r, expected_event_summaries=['IETF 72'], expected_event_count=1)
|
||||
def test_upcoming_ical_filter_invalid_syntaxes(self):
|
||||
make_meeting_test_data()
|
||||
url = urlreverse('ietf.meeting.views.upcoming_ical')
|
||||
|
||||
r = self.client.get(url + '?unknownparam=mars')
|
||||
self.assertEqual(r.status_code, 400, 'Unknown parameter should be rejected')
|
||||
|
||||
r = self.client.get(url + '?mars')
|
||||
self.assertEqual(r.status_code, 400, 'Missing parameter name should be rejected')
|
||||
|
||||
def test_upcoming_json(self):
|
||||
make_meeting_test_data(create_interims=True)
|
||||
|
@ -3872,3 +3587,204 @@ class HasMeetingsTests(TestCase):
|
|||
for session in sessions:
|
||||
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
|
||||
|
||||
|
||||
class AgendaFilterTests(TestCase):
|
||||
"""Tests for the AgendaFilter template"""
|
||||
def test_agenda_width_scale_filter(self):
|
||||
"""Test calculation of UI column width by agenda_width_scale filter"""
|
||||
template = Template('{% load agenda_filter_tags %}{{ categories|agenda_width_scale:spacing }}')
|
||||
|
||||
# Should get '1' as min value when input is empty
|
||||
context = Context({'categories': [], 'spacing': 7})
|
||||
self.assertEqual(template.render(context), '1')
|
||||
|
||||
# 3 columns, no spacers
|
||||
context = Context({'categories': [range(3)], 'spacing': 7})
|
||||
self.assertEqual(template.render(context), '21')
|
||||
|
||||
# 6 columns, 1 spacer
|
||||
context = Context({'categories': [range(3), range(3)], 'spacing': 7})
|
||||
self.assertEqual(template.render(context), '43')
|
||||
|
||||
# 10 columns, 2 spacers
|
||||
context = Context({'categories': [range(3), range(3), range(4)], 'spacing': 7})
|
||||
self.assertEqual(template.render(context), '72')
|
||||
|
||||
# 10 columns, 2 spacers, different spacer scale
|
||||
context = Context({'categories': [range(3), range(3), range(4)], 'spacing': 5})
|
||||
self.assertEqual(template.render(context), '52')
|
||||
|
||||
def test_agenda_filter_template(self):
|
||||
"""Test rendering of input data by the agenda filter template"""
|
||||
def _assert_button_ok(btn, expected_label=None, expected_filter_item=None,
|
||||
expected_filter_keywords=None):
|
||||
"""Test button properties"""
|
||||
self.assertIn(btn.text(), expected_label)
|
||||
self.assertEqual(btn.attr('data-filter-item'), expected_filter_item)
|
||||
self.assertEqual(btn.attr('data-filter-keywords'), expected_filter_keywords)
|
||||
|
||||
template = Template('{% include "meeting/agenda_filter.html" %}')
|
||||
|
||||
# Test with/without custom button text
|
||||
context = Context({'customize_button_text': None, 'filter_categories': []})
|
||||
q = PyQuery(template.render(context))
|
||||
self.assertIn('Customize...', q('h4.panel-title').text())
|
||||
self.assertEqual(q('table'), []) # no filter_categories, so no button table
|
||||
|
||||
context['customize_button_text'] = 'My custom text...'
|
||||
q = PyQuery(template.render(context))
|
||||
self.assertIn(context['customize_button_text'], q('h4.panel-title').text())
|
||||
self.assertEqual(q('table'), []) # no filter_categories, so no button table
|
||||
|
||||
# Now add a non-trivial set of filters
|
||||
context['filter_categories'] = [
|
||||
[ # first category
|
||||
dict(
|
||||
label='area0',
|
||||
keyword='keyword0',
|
||||
children=[
|
||||
dict(
|
||||
label='child00',
|
||||
keyword='keyword00',
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child01',
|
||||
keyword='keyword01',
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
dict(
|
||||
label='area1',
|
||||
keyword='keyword1',
|
||||
children=[
|
||||
dict(
|
||||
label='child10',
|
||||
keyword='keyword10',
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child11',
|
||||
keyword='keyword11',
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
],
|
||||
[ # second category
|
||||
dict(
|
||||
label='area2',
|
||||
keyword='keyword2',
|
||||
children=[
|
||||
dict(
|
||||
label='child20',
|
||||
keyword='keyword20',
|
||||
is_bof=True,
|
||||
),
|
||||
dict(
|
||||
label='child21',
|
||||
keyword='keyword21',
|
||||
is_bof=False,
|
||||
),
|
||||
]),
|
||||
],
|
||||
[ # third category
|
||||
dict(
|
||||
label=None,
|
||||
keyword=None,
|
||||
children=[
|
||||
dict(
|
||||
label='child30',
|
||||
keyword='keyword30',
|
||||
is_bof=False,
|
||||
),
|
||||
dict(
|
||||
label='child31',
|
||||
keyword='keyword31',
|
||||
is_bof=True,
|
||||
),
|
||||
]),
|
||||
],
|
||||
]
|
||||
|
||||
q = PyQuery(template.render(context))
|
||||
self.assertIn(context['customize_button_text'], q('h4.panel-title').text())
|
||||
self.assertNotEqual(q('table'), []) # should now have table
|
||||
|
||||
# Check that buttons are present for the expected things
|
||||
header_row = q('thead tr')
|
||||
self.assertEqual(len(header_row), 1)
|
||||
button_row = q('tbody tr')
|
||||
self.assertEqual(len(button_row), 1)
|
||||
|
||||
# verify correct headers
|
||||
header_cells = header_row('th')
|
||||
self.assertEqual(len(header_cells), 6) # 4 columns and 2 spacers
|
||||
header_buttons = header_cells('button.pickview')
|
||||
self.assertEqual(len(header_buttons), 3) # last column has blank header, so only 3
|
||||
|
||||
# verify buttons
|
||||
button_cells = button_row('td')
|
||||
|
||||
# area0
|
||||
_assert_button_ok(header_cells.eq(0)('button.keyword0'),
|
||||
expected_label='area0',
|
||||
expected_filter_item='keyword0')
|
||||
|
||||
buttons = button_cells.eq(0)('button.pickview')
|
||||
self.assertEqual(len(buttons), 2) # two children
|
||||
_assert_button_ok(buttons('.keyword00'),
|
||||
expected_label='child00',
|
||||
expected_filter_item='keyword00',
|
||||
expected_filter_keywords='keyword0')
|
||||
_assert_button_ok(buttons('.keyword01'),
|
||||
expected_label='child01',
|
||||
expected_filter_item='keyword01',
|
||||
expected_filter_keywords='keyword0,bof')
|
||||
|
||||
# area1
|
||||
_assert_button_ok(header_cells.eq(1)('button.keyword1'),
|
||||
expected_label='area1',
|
||||
expected_filter_item='keyword1')
|
||||
|
||||
buttons = button_cells.eq(1)('button.pickview')
|
||||
self.assertEqual(len(buttons), 2) # two children
|
||||
_assert_button_ok(buttons('.keyword10'),
|
||||
expected_label='child10',
|
||||
expected_filter_item='keyword10',
|
||||
expected_filter_keywords='keyword1')
|
||||
_assert_button_ok(buttons('.keyword11'),
|
||||
expected_label='child11',
|
||||
expected_filter_item='keyword11',
|
||||
expected_filter_keywords='keyword1,bof')
|
||||
|
||||
# area2
|
||||
# Skip column index 2, which is a spacer column
|
||||
_assert_button_ok(header_cells.eq(3)('button.keyword2'),
|
||||
expected_label='area2',
|
||||
expected_filter_item='keyword2')
|
||||
|
||||
buttons = button_cells.eq(3)('button.pickview')
|
||||
self.assertEqual(len(buttons), 2) # two children
|
||||
_assert_button_ok(buttons('.keyword20'),
|
||||
expected_label='child20',
|
||||
expected_filter_item='keyword20',
|
||||
expected_filter_keywords='keyword2,bof')
|
||||
_assert_button_ok(buttons('.keyword21'),
|
||||
expected_label='child21',
|
||||
expected_filter_item='keyword21',
|
||||
expected_filter_keywords='keyword2')
|
||||
|
||||
# area3 (no label for this one)
|
||||
# Skip column index 4, which is a spacer column
|
||||
self.assertEqual([], header_cells.eq(5)('button')) # no header button
|
||||
buttons = button_cells.eq(5)('button.pickview')
|
||||
self.assertEqual(len(buttons), 2) # two children
|
||||
_assert_button_ok(buttons('.keyword30'),
|
||||
expected_label='child30',
|
||||
expected_filter_item='keyword30',
|
||||
expected_filter_keywords=None)
|
||||
_assert_button_ok(buttons('.keyword31'),
|
||||
expected_label='child31',
|
||||
expected_filter_item='keyword31',
|
||||
expected_filter_keywords='bof')
|
||||
|
||||
|
|
|
@ -66,6 +66,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
|
||||
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 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
|
||||
|
@ -1338,6 +1339,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
timeslot__type__private=False,
|
||||
)
|
||||
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
|
||||
tag_assignments_with_filter_keywords(filtered_assignments)
|
||||
|
||||
if ext == ".csv":
|
||||
return agenda_csv(schedule, filtered_assignments)
|
||||
|
@ -1366,12 +1368,87 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
|
||||
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]
|
||||
|
||||
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
|
||||
|
||||
rendered_page = render(request, "meeting/"+base+ext, {
|
||||
"schedule": schedule,
|
||||
"filtered_assignments": filtered_assignments,
|
||||
"updated": updated,
|
||||
"group_parents": group_parents,
|
||||
"filter_categories": filter_categories,
|
||||
"non_area_keywords": [label.lower() for label in non_area_labels],
|
||||
"now": datetime.datetime.now(),
|
||||
"is_current_meeting": is_current_meeting,
|
||||
"use_codimd": True if meeting.date>=settings.MEETING_USES_CODIMD_DATE else False,
|
||||
|
@ -1641,7 +1718,8 @@ def week_view(request, num=None, name=None, owner=None):
|
|||
# saturday_after = saturday_before + datetime.timedelta(days=7)
|
||||
# filtered_assignments = filtered_assignments.filter(timeslot__time__gte=saturday_before,timeslot__time__lt=saturday_after)
|
||||
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
|
||||
|
||||
tag_assignments_with_filter_keywords(filtered_assignments)
|
||||
|
||||
items = []
|
||||
for a in filtered_assignments:
|
||||
# we don't HTML escape any of these as the week-view code is using createTextNode
|
||||
|
@ -1657,7 +1735,8 @@ def week_view(request, num=None, name=None, owner=None):
|
|||
day_of_month=a.timeslot.time.strftime("%d").lstrip("0"),
|
||||
year=a.timeslot.time.strftime("%Y"),
|
||||
),
|
||||
"type": a.timeslot.type.name
|
||||
"type": a.timeslot.type.name,
|
||||
"filter_keywords": ",".join(a.filter_keywords),
|
||||
}
|
||||
|
||||
if a.session:
|
||||
|
@ -1790,6 +1869,7 @@ def parse_agenda_filter_params(querydict):
|
|||
'Parameter "%s" is not assigned a value (use "key=" for an empty value)' % key
|
||||
)
|
||||
vals = unquote(value).lower().split(',')
|
||||
vals = [v.strip() for v in vals]
|
||||
filt_params[key] = set([v for v in vals if len(v) > 0]) # remove empty strings
|
||||
|
||||
return filt_params
|
||||
|
@ -1801,31 +1881,9 @@ def should_include_assignment(filter_params, assignment):
|
|||
When filtering by wg, uses historic_group if available as an attribute
|
||||
on the session, otherwise falls back to using group.
|
||||
"""
|
||||
historic_group = getattr(assignment.session, 'historic_group', None)
|
||||
if historic_group:
|
||||
group_acronym = historic_group.acronym
|
||||
parent = historic_group.historic_parent
|
||||
parent_acronym = parent.acronym if parent else None
|
||||
else:
|
||||
group = assignment.session.group
|
||||
group_acronym = group.acronym
|
||||
if group.parent:
|
||||
parent_acronym = group.parent.acronym
|
||||
else:
|
||||
parent_acronym = None
|
||||
session_type = assignment.timeslot.type_id
|
||||
|
||||
# Hide if wg or type hide lists apply
|
||||
if ((group_acronym in filter_params['hide']) or
|
||||
(parent_acronym in filter_params['hide']) or
|
||||
(session_type in filter_params['hidetypes'])):
|
||||
return False
|
||||
|
||||
# Show if any of the show lists apply, including showing by parent group
|
||||
return ((group_acronym in filter_params['show']) or
|
||||
(parent_acronym in filter_params['show']) or
|
||||
(session_type in filter_params['showtypes']))
|
||||
|
||||
shown = len(set(filter_params['show']).intersection(assignment.filter_keywords)) > 0
|
||||
hidden = len(set(filter_params['hide']).intersection(assignment.filter_keywords)) > 0
|
||||
return shown and not hidden
|
||||
|
||||
def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
|
||||
"""Agenda ical view
|
||||
|
@ -1857,6 +1915,7 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
|
|||
timeslot__type__private=False,
|
||||
)
|
||||
assignments = preprocess_assignments_for_agenda(assignments, meeting)
|
||||
tag_assignments_with_filter_keywords(assignments)
|
||||
|
||||
try:
|
||||
filt_params = parse_agenda_filter_params(request.GET)
|
||||
|
@ -3261,13 +3320,27 @@ def upcoming(request):
|
|||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
# only one category
|
||||
filter_categories = [[
|
||||
dict(
|
||||
label=p.acronym,
|
||||
keyword=p.acronym.lower(),
|
||||
children=[dict(
|
||||
label=g.acronym,
|
||||
keyword=g.acronym.lower(),
|
||||
is_bof=g.is_bof(),
|
||||
) for g in p.group_list]
|
||||
) for p in group_parents
|
||||
]]
|
||||
|
||||
for session in interim_sessions:
|
||||
session.historic_group = session.group
|
||||
session.filter_keywords = filter_keywords_for_session(session)
|
||||
|
||||
entries = list(ietf_meetings)
|
||||
entries.extend(list(interim_sessions))
|
||||
entries.sort(key = lambda o: pytz.utc.localize(datetime.datetime.combine(o.date, datetime.datetime.min.time())) if isinstance(o,Meeting) else o.official_timeslotassignment().timeslot.utc_start_time())
|
||||
|
||||
|
||||
# add menu entries
|
||||
menu_entries = get_interim_menu_entries(request)
|
||||
selected_menu_entry = 'upcoming'
|
||||
|
@ -3293,7 +3366,7 @@ def upcoming(request):
|
|||
|
||||
return render(request, 'meeting/upcoming.html', {
|
||||
'entries': entries,
|
||||
'group_parents': group_parents,
|
||||
'filter_categories': filter_categories,
|
||||
'menu_actions': actions,
|
||||
'menu_entries': menu_entries,
|
||||
'selected_menu_entry': selected_menu_entry,
|
||||
|
@ -3327,6 +3400,8 @@ def upcoming_ical(request):
|
|||
'session__group', 'session__group__parent', 'timeslot', 'schedule', 'schedule__meeting'
|
||||
).distinct())
|
||||
|
||||
tag_assignments_with_filter_keywords(assignments)
|
||||
|
||||
# apply filters
|
||||
if filter_params is not None:
|
||||
assignments = [a for a in assignments if should_include_assignment(filter_params, a)]
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
var agenda_filter_for_testing = {}; // methods to be accessed for automated testing
|
||||
var agenda_filter = function () {
|
||||
var agenda_filter; // public interface
|
||||
var agenda_filter_for_testing; // methods to be accessed for automated testing
|
||||
|
||||
// closure to create private scope
|
||||
(function () {
|
||||
'use strict'
|
||||
|
||||
var update_callback // function(filter_params)
|
||||
var enable_non_area = false // if true, show the non-area filters
|
||||
|
||||
/* Add to list without duplicates */
|
||||
function add_list_item (list, item) {
|
||||
if (list.indexOf(item) === -1) {
|
||||
list.push(item);
|
||||
}
|
||||
}
|
||||
var update_callback; // function(filter_params)
|
||||
|
||||
/* Remove from list, if present */
|
||||
function remove_list_item (list, item) {
|
||||
|
@ -20,13 +15,18 @@ var agenda_filter = function () {
|
|||
}
|
||||
}
|
||||
|
||||
/* Add to list if not present, remove if present */
|
||||
/* Add to list if not present, remove if present
|
||||
*
|
||||
* Returns true if added to the list, otherwise false.
|
||||
*/
|
||||
function toggle_list_item (list, item) {
|
||||
var item_index = list.indexOf(item);
|
||||
if (item_index === -1) {
|
||||
list.push(item)
|
||||
return true;
|
||||
} else {
|
||||
list.splice(item_index, 1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,101 +48,83 @@ var agenda_filter = function () {
|
|||
if (!qparams[filt] || (qparams[filt] === true)) {
|
||||
return [];
|
||||
}
|
||||
return $.map(qparams[filt].split(','), function(s){return s.trim();});
|
||||
var result = [];
|
||||
var qp = qparams[filt].split(',');
|
||||
|
||||
for (var ii = 0; ii < qp.length; ii++) {
|
||||
result.push(qp[ii].trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function get_filter_params (qparams) {
|
||||
var enabled = !!(qparams.show || qparams.hide || qparams.showtypes || qparams.hidetypes);
|
||||
var enabled = !!(qparams.show || qparams.hide);
|
||||
return {
|
||||
enabled: enabled,
|
||||
show_groups: get_filter_from_qparams(qparams, 'show'),
|
||||
hide_groups: get_filter_from_qparams(qparams, 'hide'),
|
||||
show_types: get_filter_from_qparams(qparams, 'showtypes'),
|
||||
hide_types: get_filter_from_qparams(qparams, 'hidetypes'),
|
||||
show: get_filter_from_qparams(qparams, 'show'),
|
||||
hide: get_filter_from_qparams(qparams, 'hide')
|
||||
}
|
||||
}
|
||||
|
||||
function filtering_is_enabled (filter_params) {
|
||||
return filter_params['enabled'];
|
||||
function get_keywords(elt) {
|
||||
var keywords = $(elt).attr('data-filter-keywords');
|
||||
if (keywords) {
|
||||
return keywords.toLowerCase().split(',');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function get_area_items (area) {
|
||||
var types = [];
|
||||
var groups = [];
|
||||
var neg_groups = [];
|
||||
function get_item(elt) {
|
||||
return $(elt).attr('data-filter-item');
|
||||
}
|
||||
|
||||
$('.view.' + area).find('button').each(function (index, elt) {
|
||||
elt = $(elt) // jquerify
|
||||
var item = elt.text().trim().toLowerCase()
|
||||
if (elt.hasClass('picktype')) {
|
||||
types.push(item)
|
||||
} else if (elt.hasClass('pickview')) {
|
||||
groups.push(item);
|
||||
} else if (elt.hasClass('pickviewneg')) {
|
||||
neg_groups.push(item)
|
||||
// utility method - is there a match between two lists of keywords?
|
||||
function keyword_match(list1, list2) {
|
||||
for (var ii = 0; ii < list1.length; ii++) {
|
||||
if (list2.indexOf(list1[ii]) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the items corresponding to a keyword
|
||||
function get_items_with_keyword (keyword) {
|
||||
var items = [];
|
||||
|
||||
$('.view button.pickview').filter(function(index, elt) {
|
||||
return keyword_match(get_keywords(elt), [keyword]);
|
||||
}).each(function (index, elt) {
|
||||
items.push(get_item($(elt)));
|
||||
});
|
||||
return { 'groups': groups, 'neg_groups': neg_groups, 'types': types };
|
||||
return items;
|
||||
}
|
||||
|
||||
function filtering_is_enabled (filter_params) {
|
||||
return filter_params.enabled;
|
||||
}
|
||||
|
||||
// Update the filter / customization UI to match the current filter parameters
|
||||
function update_filter_ui (filter_params) {
|
||||
var area_group_buttons = $('.view .pickview, .pick-area');
|
||||
var non_area_header_button = $('button.pick-non-area');
|
||||
var non_area_type_buttons = $('.view.non-area .picktype');
|
||||
var non_area_group_buttons = $('.view.non-area button.pickviewneg');
|
||||
var buttons = $('.pickview');
|
||||
|
||||
if (!filtering_is_enabled(filter_params)) {
|
||||
// Not filtering - set everything to defaults and exit
|
||||
area_group_buttons.removeClass('active');
|
||||
non_area_header_button.removeClass('active');
|
||||
non_area_type_buttons.removeClass('active');
|
||||
non_area_group_buttons.removeClass('active');
|
||||
non_area_group_buttons.addClass('disabled');
|
||||
// Not filtering - set to default and exit
|
||||
buttons.removeClass('active');
|
||||
return;
|
||||
}
|
||||
|
||||
// show the customizer - it will stay visible even if filtering is disabled
|
||||
$('#customize').collapse('show')
|
||||
|
||||
// Group and area buttons - these are all positive selections
|
||||
area_group_buttons.each(function (index, elt) {
|
||||
// Update button state to match visibility
|
||||
buttons.each(function (index, elt) {
|
||||
elt = $(elt);
|
||||
var item = elt.text().trim().toLowerCase();
|
||||
var area = elt.attr('data-group-area');
|
||||
if ((filter_params['hide_groups'].indexOf(item) === -1) // not hidden...
|
||||
&& ((filter_params['show_groups'].indexOf(item) !== -1) // AND shown...
|
||||
|| (area && (filter_params['show_groups'].indexOf(area.trim().toLowerCase()) !== -1))) // OR area shown
|
||||
) {
|
||||
elt.addClass('active');
|
||||
} else {
|
||||
elt.removeClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Non-area buttons need special handling. Only have positive type and negative group buttons.
|
||||
// Assume non-area heading is disabled, then enable if one of the types is active
|
||||
non_area_header_button.removeClass('active');
|
||||
non_area_group_buttons.addClass('disabled');
|
||||
non_area_type_buttons.each(function (index, elt) {
|
||||
// Positive type selection buttons
|
||||
elt = $(elt);
|
||||
var item = elt.text().trim().toLowerCase();
|
||||
if ((filter_params['show_types'].indexOf(item) !== -1)
|
||||
&& (filter_params['hide_types'].indexOf(item) === -1)){
|
||||
elt.addClass('active');
|
||||
non_area_header_button.addClass('active');
|
||||
non_area_group_buttons.removeClass('disabled');
|
||||
} else {
|
||||
elt.removeClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
non_area_group_buttons.each(function (index, elt) {
|
||||
// Negative group selection buttons
|
||||
elt = $(elt);
|
||||
var item = elt.text().trim().toLowerCase();
|
||||
if (filter_params['hide_groups'].indexOf(item) === -1) {
|
||||
var keywords = get_keywords(elt);
|
||||
keywords.push(get_item(elt)); // treat item as one of its keywords
|
||||
var hidden = keyword_match(filter_params.hide, keywords);
|
||||
var shown = keyword_match(filter_params.show, keywords);
|
||||
if (shown && !hidden) {
|
||||
elt.addClass('active');
|
||||
} else {
|
||||
elt.removeClass('active');
|
||||
|
@ -163,7 +145,6 @@ var agenda_filter = function () {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* Trigger an update so the user will see the page appropriate for given filter_params
|
||||
*
|
||||
* Updates the URL to match filter_params, then updates the history / display to match
|
||||
|
@ -172,17 +153,11 @@ var agenda_filter = function () {
|
|||
function update_filters (filter_params) {
|
||||
var qparams = []
|
||||
var search = ''
|
||||
if (filter_params['show_groups'].length > 0) {
|
||||
qparams.push('show=' + filter_params['show_groups'].join())
|
||||
if (filter_params.show.length > 0) {
|
||||
qparams.push('show=' + filter_params.show.join())
|
||||
}
|
||||
if (filter_params['hide_groups'].length > 0) {
|
||||
qparams.push('hide=' + filter_params['hide_groups'].join())
|
||||
}
|
||||
if (filter_params['show_types'].length > 0) {
|
||||
qparams.push('showtypes=' + filter_params['show_types'].join())
|
||||
}
|
||||
if (filter_params['hide_types'].length > 0) {
|
||||
qparams.push('hidetypes=' + filter_params['hide_types'].join())
|
||||
if (filter_params.hide.length > 0) {
|
||||
qparams.push('hide=' + filter_params.hide.join())
|
||||
}
|
||||
if (qparams.length > 0) {
|
||||
search = '?' + qparams.join('&')
|
||||
|
@ -202,27 +177,40 @@ var agenda_filter = function () {
|
|||
|
||||
/* Helper for pick group/type button handlers - toggles the appropriate parameter entry
|
||||
* elt - the jquery element that was clicked
|
||||
* param_type - key of the filter param to update (show_groups, show_types, etc)
|
||||
*/
|
||||
function handle_pick_button (elt, param_type) {
|
||||
var area = elt.attr('data-group-area');
|
||||
var item = elt.text().trim().toLowerCase();
|
||||
function handle_pick_button (elt) {
|
||||
var fp = get_filter_params(parse_query_params(window.location.search));
|
||||
var neg_param_type = {
|
||||
show_groups: 'hide_groups',
|
||||
hide_groups: 'show_groups',
|
||||
show_types: 'hide_types',
|
||||
hide_types: 'show_types'
|
||||
}[param_type];
|
||||
var item = get_item(elt);
|
||||
|
||||
if (area && (fp[param_type].indexOf(area.trim().toLowerCase()) !== -1)) {
|
||||
// Area is shown - toggle hide list
|
||||
toggle_list_item(fp[neg_param_type], item);
|
||||
remove_list_item(fp[param_type], item);
|
||||
/* Normally toggle in and out of the 'show' list. If this item is active because
|
||||
* one of its keywords is active, invert the sense and toggle in and out of the
|
||||
* 'hide' list instead. */
|
||||
var inverted = keyword_match(fp.show, get_keywords(elt));
|
||||
var just_showed_item = false;
|
||||
if (inverted) {
|
||||
toggle_list_item(fp.hide, item);
|
||||
remove_list_item(fp.show, item);
|
||||
} else {
|
||||
toggle_list_item(fp[param_type], item);
|
||||
remove_list_item(fp[neg_param_type], item);
|
||||
just_showed_item = toggle_list_item(fp.show, item);
|
||||
remove_list_item(fp.hide, item);
|
||||
}
|
||||
|
||||
/* If we just showed an item, remove its children from the
|
||||
* show/hide lists to keep things consistent. This way, selecting
|
||||
* an area will enable all items in the row as one would expect. */
|
||||
if (just_showed_item) {
|
||||
var children = get_items_with_keyword(item);
|
||||
$.each(children, function(index, child) {
|
||||
remove_list_item(fp.show, child);
|
||||
remove_list_item(fp.hide, child);
|
||||
});
|
||||
}
|
||||
|
||||
// If the show list is empty, clear the hide list because there is nothing to hide
|
||||
if (fp.show.length === 0) {
|
||||
fp.hide = [];
|
||||
}
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
|
@ -230,88 +218,48 @@ var agenda_filter = function () {
|
|||
return elt.hasClass('disabled');
|
||||
}
|
||||
|
||||
// Various "pick" button handlers
|
||||
$('.pickview').click(function () {
|
||||
if (is_disabled($(this))) { return; }
|
||||
update_filters(handle_pick_button($(this), 'show_groups'))
|
||||
});
|
||||
|
||||
$('.pickviewneg').click(function () {
|
||||
if (is_disabled($(this))) { return; }
|
||||
update_filters(handle_pick_button($(this), 'hide_groups'))
|
||||
});
|
||||
|
||||
$('.picktype').click(function () {
|
||||
if (is_disabled($(this))) { return; }
|
||||
var fp = handle_pick_button($(this), 'show_types')
|
||||
// If we just disabled the last non-area type, clear out the hide groups list.
|
||||
var items = get_area_items('non-area')
|
||||
var any_left = false
|
||||
$.each(items.types, function (index, session_type) {
|
||||
if (fp['show_types'].indexOf(session_type) !== -1) {
|
||||
any_left = true
|
||||
}
|
||||
})
|
||||
if (!any_left) {
|
||||
fp['hide_groups'] = []
|
||||
}
|
||||
update_filters(fp);
|
||||
});
|
||||
|
||||
// Click handler for an area header button
|
||||
$('.pick-area').click(function() {
|
||||
if (is_disabled($(this))) { return; }
|
||||
var fp = handle_pick_button($(this), 'show_groups');
|
||||
var items = get_area_items($(this).text().trim().toLowerCase());
|
||||
|
||||
// Clear all the individual group show/hide options
|
||||
$.each(items.groups, function(index, group) {
|
||||
remove_list_item(fp['show_groups'], group);
|
||||
remove_list_item(fp['hide_groups'], group);
|
||||
function register_handlers() {
|
||||
$('.pickview').click(function () {
|
||||
if (is_disabled($(this))) { return; }
|
||||
var fp = handle_pick_button($(this));
|
||||
update_filters(fp);
|
||||
});
|
||||
update_filters(fp);
|
||||
});
|
||||
|
||||
// Click handler for the "Non-Area" header button
|
||||
$('.pick-non-area').click(function () {
|
||||
var items = get_area_items('non-area');
|
||||
|
||||
var fp = get_filter_params(parse_query_params(window.location.search))
|
||||
if ($(this).hasClass('active')) {
|
||||
// Were active - disable or hide everything
|
||||
$.each(items.types, function (index, session_type) {
|
||||
remove_list_item(fp['show_types'], session_type)
|
||||
})
|
||||
// When no types are shown, no need to hide groups. Empty hide_groups list.
|
||||
fp['hide_groups'] = []
|
||||
} else {
|
||||
// Were not active - enable or stop hiding everything
|
||||
$.each(items.types, function (index, session_type) {
|
||||
add_list_item(fp['show_types'], session_type)
|
||||
})
|
||||
$.each(items.neg_groups, function (index, group) {
|
||||
remove_list_item(fp['hide_groups'], group)
|
||||
})
|
||||
}
|
||||
update_filters(fp);
|
||||
});
|
||||
|
||||
// Entry point to filtering code when page loads
|
||||
}
|
||||
|
||||
/* Entry point to filtering code when page loads
|
||||
*
|
||||
* This must be called if you are using the HTML template to provide a customization
|
||||
* button UI. Do not call if you only want to use the parameter parsing routines.
|
||||
*/
|
||||
function enable () {
|
||||
$(document).ready(function () {
|
||||
update_view()
|
||||
register_handlers();
|
||||
update_view();
|
||||
})
|
||||
}
|
||||
|
||||
// utility method - filter a jquery set to those matching a keyword
|
||||
function rows_matching_filter_keyword(rows, kw) {
|
||||
return rows.filter(function(index, element) {
|
||||
var row_kws = get_keywords(element);
|
||||
return keyword_match(row_kws, [kw.toLowerCase()]);
|
||||
});
|
||||
}
|
||||
|
||||
// Make private functions available for unit testing
|
||||
agenda_filter_for_testing.toggle_list_item = toggle_list_item;
|
||||
agenda_filter_for_testing.parse_query_params = parse_query_params;
|
||||
agenda_filter_for_testing = {
|
||||
parse_query_params: parse_query_params,
|
||||
toggle_list_item: toggle_list_item
|
||||
};
|
||||
|
||||
// Public interface methods
|
||||
return {
|
||||
// Make public interface methods accessible
|
||||
agenda_filter = {
|
||||
enable: enable,
|
||||
filtering_is_enabled: filtering_is_enabled,
|
||||
include_non_area_selectors: function () {enable_non_area = true},
|
||||
get_filter_params: get_filter_params,
|
||||
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}
|
||||
}
|
||||
}();
|
||||
};
|
||||
})();
|
|
@ -62,18 +62,25 @@
|
|||
{% endif %}
|
||||
|
||||
|
||||
{% include "meeting/agenda_filter.html" with group_parents=group_parents non_area_filters=True customize_button_text="Customize the agenda view..." only %}
|
||||
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Customize the agenda view..." only %}
|
||||
|
||||
<h2>Download as .ics</h2>
|
||||
<p class="buttonlist">
|
||||
{% for p in group_parents %}
|
||||
<a class="btn btn-default" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{p.acronym|upper}}">{{p.acronym|upper}}</a>
|
||||
{% endfor %}
|
||||
<a class="btn btn-default" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?showtypes=plenary,other">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>
|
||||
{% for fc in filter_categories %}
|
||||
{% if not forloop.last %} {# skip the last group, it's the office hours/misc #}
|
||||
<div style="display:inline-block;margin-right:1em">
|
||||
{% for p in fc|dictsort:"label" %}
|
||||
<a class="btn btn-default" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{p.keyword}}">{{p.label}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% 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>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
|
||||
<h2>
|
||||
Schedule
|
||||
{% if schedule.meeting.agenda_warning_note %}
|
||||
|
@ -125,10 +132,7 @@
|
|||
{% 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-item-group="{% if item.session.historic_group %}{{ item.session.historic_group.acronym }}{% endif %}"
|
||||
data-item-area="{% if item.session.historic_group and item.session.historic_group.historic_parent %}{{ item.session.historic_group.historic_parent.acronym }}{% endif %}"
|
||||
data-timeslot-type="{{item.timeslot.type.slug}}">
|
||||
<tr id="row-{{ item.slug }}" data-filter-keywords="{{ item.filter_keywords|join:',' }}">
|
||||
<td class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
|
@ -187,11 +191,9 @@
|
|||
|
||||
{% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %}
|
||||
{% if item.session.historic_group %}
|
||||
<tr id="row-{{item.slug}}"
|
||||
data-item-group="{{ item.session.historic_group.acronym }}"
|
||||
data-item-area="{% if item.session.historic_group.historic_parent %}{{ item.session.historic_group.historic_parent.acronym }}{% endif %}"
|
||||
data-timeslot-type="{{item.timeslot.type.slug}}"
|
||||
data-ske="row-{{ item.slug }}" {% if item.timeslot.type.slug == 'plenary' %}class="{{item.timeslot.type.slug}}danger"{% endif %}>
|
||||
<tr id="row-{{item.slug}}"
|
||||
{% if item.timeslot.type.slug == 'plenary' %}class="{{item.timeslot.type.slug}}danger"{% endif %}
|
||||
data-filter-keywords="{{ item.filter_keywords|join:',' }}">
|
||||
{% if item.timeslot.type.slug == 'plenary' %}
|
||||
<th class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
|
@ -328,26 +330,18 @@
|
|||
}
|
||||
|
||||
// if groups were selected for filtering, hide all rows by default
|
||||
agenda_rows.hide();
|
||||
agenda_rows.filter(function(index, row) {
|
||||
return !!$(row).attr('data-filter-keywords');
|
||||
}).hide();
|
||||
|
||||
// loop through the has items and change the UI element and row visibilities accordingly
|
||||
$.each(filter_params['show_groups'], function (i, v) {
|
||||
$.each(filter_params.show, function (i, v) {
|
||||
// this is a regular item by wg: when present, show these rows
|
||||
agenda_rows.filter('[data-item-group="'+ v +'"]').show();
|
||||
agenda_rows.filter('[data-item-area="'+ v +'"]').show();
|
||||
agenda_filter.rows_matching_filter_keyword(agenda_rows, v).show();
|
||||
});
|
||||
$.each(filter_params['show_types'], function (i, v) {
|
||||
// this is a regular item by type: when present, show these rows
|
||||
agenda_rows.filter('[data-timeslot-type*="' + v + '"]').show();
|
||||
});
|
||||
$.each(filter_params['hide_groups'], function (i, v) {
|
||||
$.each(filter_params.hide, function (i, v) {
|
||||
// this is a "negative" item by wg: when present, hide these rows
|
||||
agenda_rows.filter('[data-item-group="'+ v +'"]').hide();
|
||||
agenda_rows.filter('[data-item-area="'+ v +'"]').hide();
|
||||
});
|
||||
$.each(filter_params['hide_types'], function (i, v) {
|
||||
// this is a "negative" item by type: when present, hide these rows
|
||||
agenda_rows.filter('[data-timeslot-type*="' + v + '"]').hide();
|
||||
agenda_filter.rows_matching_filter_keyword(agenda_rows, v).hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -381,7 +375,7 @@
|
|||
wv_window.history.replaceState({}, '', new_url);
|
||||
wv_window.draw_calendar()
|
||||
} else {
|
||||
// ho history.replaceState, page reload required
|
||||
// either have not yet loaded the iframe or we do not support history replacement
|
||||
wv_iframe.src = new_url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{% load agenda_filter_tags %}
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
@ -16,85 +17,60 @@
|
|||
To be able to return to the customized view later, bookmark the resulting URL.
|
||||
</p>
|
||||
|
||||
{% if group_parents|length %}
|
||||
{% if filter_categories|length %}
|
||||
<p>Groups displayed in <b><i>italics</i></b> are BOFs.</p>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for p in group_parents %}
|
||||
<th style="width:{% widthratio 1 group_parents|length|add:1 100 %}%">
|
||||
<button class="btn btn-default btn-block pick-area {{ p.acronym|lower }}">{{ p.acronym|upper }}</button>
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% if non_area_filters %}
|
||||
<th style="width:{% widthratio 1 group_parents|length|add:1 100 %}">
|
||||
<button class="btn btn-default btn-block pick-non-area">Non-Area</button>
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{% for p in group_parents %}
|
||||
<td class="view {{ p.acronym|lower }}">
|
||||
<div class="btn-group-vertical btn-block">
|
||||
{% for group in p.group_list %}
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickview {{ group.acronym }}"
|
||||
data-group-area="{{ p.acronym|lower }}">
|
||||
{% if group.is_bof %}
|
||||
<i>{{ group.acronym }}</i>
|
||||
{% else %}
|
||||
{{ group.acronym }}
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% if non_area_filters %}
|
||||
<!-- Non-Area buttons -->
|
||||
<td class="view non-area">
|
||||
<div class="btn-group-vertical btn-block">
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default picktype plenary">Plenary</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default picktype other">Other</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group-vertical btn-block">
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg edu"> EDU</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg hackathon"> Hackathon</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg iab"> IAB</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg iepg"> IEPG</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg iesg"> IESG</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg ietf"> IETF</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg secretariat"> Secretariat
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickviewneg tools"> Tools</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% with spacer_scale=5 %}
|
||||
{% with width_scale=filter_categories|agenda_width_scale:spacer_scale %}
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for fc in filter_categories %}
|
||||
{% if not forloop.first %}
|
||||
<th style="width:{% widthratio 1 width_scale 100 %}%"></th>
|
||||
{% endif %}
|
||||
{% for area in fc %}
|
||||
<th style="width:{% widthratio spacer_scale width_scale 100 %}%">
|
||||
{% if area.keyword %}
|
||||
<button class="btn btn-default btn-block pickview {{ area.keyword }}"
|
||||
data-filter-item="{{ area.keyword }}">
|
||||
{% firstof area.label area.keyword %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{% for fc in filter_categories %}
|
||||
{% if not forloop.first %} <td></td> {% endif %}
|
||||
{% for p in fc %}
|
||||
<td class="view {{ p.keyword }}">
|
||||
<div class="btn-group-vertical btn-block">
|
||||
{% for button in p.children|dictsort:"label" %}
|
||||
<div class="btn-group btn-group-xs btn-group-justified">
|
||||
<button class="btn btn-default pickview {{ button.keyword }}"
|
||||
{% if p.keyword or button.is_bof %}data-filter-keywords="{% if p.keyword %}{{ p.keyword }}{% if button.is_bof %},{% endif %}{% endif %}{% if button.is_bof %}bof{% endif %}"{% endif %}
|
||||
data-filter-item="{{ button.keyword }}">
|
||||
{% if button.is_bof %}
|
||||
<i>{{ button.label }}</i>
|
||||
{% else %}
|
||||
{{ button.label }}
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<blockquote><i>No WG / RG data available -- no WG / RG sessions have been scheduled yet.</i>
|
||||
</blockquote>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
|
||||
|
||||
{% include 'meeting/agenda_filter.html' with group_parents=group_parents customize_button_text="Customize the meeting list..." only%}
|
||||
{% include 'meeting/agenda_filter.html' with filter_categories=filter_categories customize_button_text="Customize the meeting list..." only%}
|
||||
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
|
@ -66,9 +66,8 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr class="entry"
|
||||
data-item-group="{% if entry.group %}{{ entry.group.acronym|lower }}{% endif %}"
|
||||
data-item-area="{% if entry.group and entry.group.parent %}{{ entry.group.parent.acronym|lower }}{% endif %}">
|
||||
<tr class="entry"
|
||||
{% if entry|classname == 'Session' %}data-filter-keywords="{{ entry.filter_keywords|join:',' }}"{% endif %}>
|
||||
{% if entry|classname == 'Meeting' %}
|
||||
{% with meeting=entry %}
|
||||
<td>{{ meeting.date }} - {{ meeting.end }}</td>
|
||||
|
@ -77,11 +76,11 @@
|
|||
<td></td>
|
||||
{% endwith %}
|
||||
{% elif entry|classname == 'Session' %}
|
||||
{% with session=entry meeting=entry.meeting%}
|
||||
{% with session=entry group=entry.group meeting=entry.meeting%}
|
||||
<td>{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i e" }}</td>
|
||||
<td><a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{ session.group.acronym }}</a></td>
|
||||
<td><a href="{% url 'ietf.group.views.group_home' acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td>
|
||||
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">{{ session.meeting.number }}</a>
|
||||
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=group.acronym %}"> {{ meeting.number }}</a>
|
||||
</td>
|
||||
{% if session.current_status == 'canceled' %}
|
||||
<td class='text-right'>
|
||||
|
@ -134,7 +133,7 @@
|
|||
{
|
||||
title: '{{session.official_timeslotassignment.timeslot.utc_start_time|date:"H:i"}}-{{session.official_timeslotassignment.timeslot.utc_end_time|date:"H:i"}}',
|
||||
group: '{% if session.group %}{{session.group.acronym}}{% endif %}',
|
||||
area: '{% if session.group and session.group.parent %}{{ session.group.parent.acronym }}{% endif %}',
|
||||
filter_keywords: ["{{ session.filter_keywords|join:'","' }}"],
|
||||
start: '{{session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}}',
|
||||
end: '{{session.official_timeslotassignment.timeslot.utc_end_time | date:"Y-m-d H:i"}}',
|
||||
url: '{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}'
|
||||
|
@ -148,29 +147,14 @@
|
|||
|
||||
// Test whether an event should be visible given a set of filter parameters
|
||||
function calendar_event_visible(filter_params, event) {
|
||||
// Visible if filtering is disabled or event has no group
|
||||
if (!agenda_filter.filtering_is_enabled(filter_params) || !event.group) {
|
||||
// Visible if filtering is disabled or event has no keywords
|
||||
if (!agenda_filter.filtering_is_enabled(filter_params) || !event.filter_keywords) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exclude if group or area is in the hide_groups list
|
||||
if (filter_params['hide_groups'].indexOf(event.group) !== -1) {
|
||||
return false;
|
||||
}
|
||||
if (event.area && (filter_params['hide_groups'].indexOf(event.area) !== -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Include if group or area is in the show_groups list
|
||||
if (filter_params['show_groups'].indexOf(event.group) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (event.area && (filter_params['show_groups'].indexOf(event.area) !== -1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not selected, exclude by default
|
||||
return false;
|
||||
// Visible if shown and not hidden
|
||||
return (!agenda_filter.keyword_match(filter_params.hide, event.filter_keywords)
|
||||
&& agenda_filter.keyword_match(filter_params.show, event.filter_keywords));
|
||||
}
|
||||
|
||||
// Apply filter_params to the event list and format data for the calendar
|
||||
|
@ -217,24 +201,22 @@
|
|||
}
|
||||
|
||||
function update_meeting_display(filter_params) {
|
||||
var meeting_rows = $("#upcoming-meeting-table tr.entry")
|
||||
var meeting_rows = $("#upcoming-meeting-table tr.entry");
|
||||
if (!agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
meeting_rows.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// hide everything that has a group
|
||||
meeting_rows.filter("[data-item-group!='']").hide();
|
||||
// hide everything that has keywords
|
||||
meeting_rows.filter(function(index, row){
|
||||
return !!$(row).attr('data-filter-keywords');
|
||||
}).hide();
|
||||
|
||||
$.each(filter_params['show_groups'], function (i, v) {
|
||||
// this is a regular item by wg: when present, show these rows
|
||||
meeting_rows.filter('[data-item-group="'+ v +'"]').show();
|
||||
meeting_rows.filter('[data-item-area="'+ v +'"]').show();
|
||||
$.each(filter_params['show'], function (i, v) {
|
||||
agenda_filter.rows_matching_filter_keyword(meeting_rows, v).show();
|
||||
});
|
||||
$.each(filter_params['hide_groups'], function (i, v) {
|
||||
// this is a "negative" item by wg: when present, hide these rows
|
||||
meeting_rows.filter('[data-item-group="'+ v +'"]').hide();
|
||||
meeting_rows.filter('[data-item-area="'+ v +'"]').hide();
|
||||
$.each(filter_params['hide'], function (i, v) {
|
||||
agenda_filter.rows_matching_filter_keyword(meeting_rows, v).hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
{% load origin %}{% origin %}
|
||||
{% load static %}
|
||||
<html> <head>
|
||||
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var all_items = {{ items|safe }};
|
||||
|
@ -106,62 +107,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========================================================================
|
||||
|
||||
function parse_query_params(qs) {
|
||||
var params = {};
|
||||
qs = qs.replace(/^\?/, '').toLowerCase();
|
||||
if (qs) {
|
||||
var param_strs = qs.split('&');
|
||||
for (var ii = 0; ii < param_strs.length; ii++) {
|
||||
var toks = param_strs[ii].split('=', 2)
|
||||
params[toks[0]] = toks[1] || true;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
||||
function get_filter_from_qparams(qparams, filt) {
|
||||
return qparams[filt] ? qparams[filt].split(',') : [];
|
||||
}
|
||||
|
||||
function get_filter_params(qparams) {
|
||||
return {
|
||||
show_groups: get_filter_from_qparams(qparams, 'show'),
|
||||
hide_groups: get_filter_from_qparams(qparams, 'hide'),
|
||||
show_types: get_filter_from_qparams(qparams, 'showtypes'),
|
||||
hide_types: get_filter_from_qparams(qparams, 'hidetypes'),
|
||||
};
|
||||
}
|
||||
//===========================================================================
|
||||
|
||||
function is_visible(query_params) {
|
||||
function is_visible(filter_params) {
|
||||
// Returns a method to filter objects for visibility
|
||||
// Accepts show, hide, showtypes, and hidetypes filters. Also accepts
|
||||
// Accepts show and hide filters. No longer accepts
|
||||
// '@<state>' to show sessions in a particular state (e.g., @bof).
|
||||
// Current types are:
|
||||
// Session, Other, Break, Plenary
|
||||
var fp = get_filter_params(query_params);
|
||||
|
||||
return function (item) {
|
||||
var item_group = (item.group || '').toLowerCase();
|
||||
var item_type = (item.type || '').toLowerCase();
|
||||
var item_area = (item.area || '').toLowerCase();
|
||||
var item_state = (item.state || '').toLowerCase();
|
||||
|
||||
if ((fp['hide_groups'].indexOf(item_group) >= 0) ||
|
||||
(fp['hide_groups'].indexOf(item_area) >= 0) ||
|
||||
(fp['hide_types'].indexOf(item_type) >= 0)) {
|
||||
return false;
|
||||
}
|
||||
return ((fp['show_groups'].indexOf(item_group) >= 0) ||
|
||||
(fp['show_groups'].indexOf(item_area) >= 0) ||
|
||||
(fp['show_types'].indexOf(item_type) >= 0) ||
|
||||
query_params['@'+item_state]);
|
||||
var filter_keywords = item.filter_keywords.split(',');
|
||||
return (!agenda_filter.keyword_match(filter_keywords, filter_params.hide)
|
||||
&& agenda_filter.keyword_match(filter_keywords, filter_params.show));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,9 +128,12 @@
|
|||
var height = document.body.clientHeight;
|
||||
|
||||
var visible_items = all_items;
|
||||
var qs = window.location.search;
|
||||
if (qs.length > 1) {
|
||||
visible_items = visible_items.filter(is_visible(parse_query_params(qs)));
|
||||
var filter_params = agenda_filter.get_filter_params(
|
||||
agenda_filter.parse_query_params(window.location.search)
|
||||
);
|
||||
|
||||
if (agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
visible_items = visible_items.filter(is_visible(filter_params));
|
||||
}
|
||||
|
||||
var start_day;
|
||||
|
@ -539,9 +498,13 @@
|
|||
|
||||
//===========================================================================
|
||||
// Set up events for drawing the calendar
|
||||
window.addEventListener("resize", draw_calendar, false);
|
||||
window.addEventListener("load", draw_calendar, false);
|
||||
window.addEventListener("hashchange", draw_calendar, false);
|
||||
function redraw_weekview() {
|
||||
draw_calendar();
|
||||
}
|
||||
|
||||
window.addEventListener("resize", redraw_weekview, false);
|
||||
window.addEventListener("load", redraw_weekview, false);
|
||||
window.addEventListener("hashchange", redraw_weekview, false);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -92,6 +92,39 @@ def reload_db_objects(*objects):
|
|||
else:
|
||||
return t
|
||||
|
||||
def assert_ical_response_is_valid(test_inst, response, expected_event_summaries=None,
|
||||
expected_event_uids=None, expected_event_count=None):
|
||||
"""Validate an HTTP response containing iCal data
|
||||
|
||||
Based on RFC2445, but not exhaustive by any means. Assumes a single iCalendar object. Checks that
|
||||
expected_event_summaries/_uids are found, but other events are allowed to be present. Specify the
|
||||
expected_event_count if you want to reject additional events. If any of these are None,
|
||||
the check for that property is skipped.
|
||||
"""
|
||||
test_inst.assertEqual(response.get('Content-Type'), "text/calendar")
|
||||
|
||||
# Validate iCalendar object
|
||||
test_inst.assertContains(response, 'BEGIN:VCALENDAR', count=1)
|
||||
test_inst.assertContains(response, 'END:VCALENDAR', count=1)
|
||||
test_inst.assertContains(response, 'PRODID:', count=1)
|
||||
test_inst.assertContains(response, 'VERSION', count=1)
|
||||
|
||||
# Validate event objects
|
||||
if expected_event_summaries is not None:
|
||||
for summary in expected_event_summaries:
|
||||
test_inst.assertContains(response, 'SUMMARY:' + summary)
|
||||
|
||||
if expected_event_uids is not None:
|
||||
for uid in expected_event_uids:
|
||||
test_inst.assertContains(response, 'UID:' + uid)
|
||||
|
||||
if expected_event_count is not None:
|
||||
test_inst.assertContains(response, 'BEGIN:VEVENT', count=expected_event_count)
|
||||
test_inst.assertContains(response, 'END:VEVENT', count=expected_event_count)
|
||||
test_inst.assertContains(response, 'UID', count=expected_event_count)
|
||||
|
||||
|
||||
|
||||
class ReverseLazyTest(django.test.TestCase):
|
||||
def test_redirect_with_lazy_reverse(self):
|
||||
response = self.client.get('/ipr/update/')
|
||||
|
|
Loading…
Reference in a new issue