Rebase agenda customization from jennifer/7.13.1.dev0 to 7.17.1.dev0
- Legacy-Id: 18517
This commit is contained in:
commit
97dd600a9b
|
@ -1,6 +1,7 @@
|
|||
# Copyright The IETF Trust 2017, All Rights Reserved
|
||||
|
||||
from django.conf.urls import include
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from ietf import api
|
||||
from ietf.api import views as api_views
|
||||
|
@ -33,6 +34,7 @@ urlpatterns = [
|
|||
# Let the registration system notify us about registrations
|
||||
url(r'^notify/meeting/registration/?', api_views.api_new_meeting_registration),
|
||||
# OpenID authentication provider
|
||||
url(r'^openid/$', TemplateView.as_view(template_name='api/openid-issuer.html'), name='ietf.api.urls.oidc_issuer'),
|
||||
url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
|
||||
# Draft submission API
|
||||
url(r'^submit/?$', submit_views.api_submit),
|
||||
|
|
|
@ -116,6 +116,9 @@ def make_meeting_test_data(meeting=None, create_interims=False):
|
|||
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
|
||||
duration=datetime.timedelta(minutes=90),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
|
||||
plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
|
||||
duration=datetime.timedelta(minutes=60),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(11,0)))
|
||||
# mars WG
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars_session = Session.objects.create(meeting=meeting, group=mars,
|
||||
|
@ -159,6 +162,14 @@ def make_meeting_test_data(meeting=None, create_interims=False):
|
|||
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=base_schedule)
|
||||
|
||||
# IETF Plenary
|
||||
plenary_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ietf"),
|
||||
name="IETF Plenary", attendees=250,
|
||||
requested_duration=datetime.timedelta(minutes=60),
|
||||
type_id="plenary")
|
||||
SchedulingEvent.objects.create(session=plenary_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=plenary_slot, session=plenary_session, schedule=schedule)
|
||||
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
|
||||
|
@ -209,7 +220,7 @@ def make_interim_test_data():
|
|||
ad = Person.objects.get(user__username='ad')
|
||||
RoleFactory(group=area,person=ad,name_id='ad')
|
||||
mars = GroupFactory(acronym='mars',parent=area,name='Martian Special Interest Group')
|
||||
ames = GroupFactory(acronym='ames',parent=area)
|
||||
ames = GroupFactory(acronym='ames',parent=area,name='Asteroid Mining Equipment Standardization Group')
|
||||
RoleFactory(group=mars,person__user__username='marschairman',name_id='chair')
|
||||
RoleFactory(group=ames,person__user__username='ameschairman',name_id='chair')
|
||||
|
||||
|
|
|
@ -477,8 +477,7 @@ class TimeSlotEditingApiTests(TestCase):
|
|||
|
||||
def test_manipulate_timeslot(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.timeslot_set.all()[0]
|
||||
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular')
|
||||
slot = meeting.timeslot_set.filter(type_id='regular')[0]
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
|
||||
kwargs=dict(num=meeting.number, slotid=slot.pk))
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
import time
|
||||
import datetime
|
||||
import shutil
|
||||
import os
|
||||
from unittest import skipIf
|
||||
|
||||
import django
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.utils.text import slugify
|
||||
from django.db.models import F
|
||||
#from django.test.utils import override_settings
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
@ -15,10 +19,13 @@ import debug # pyflakes:ignore
|
|||
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.meeting.factories import SessionFactory
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.meeting.models import Schedule, SchedTimeSessAssignment, Session, Room, TimeSlot, Constraint, ConstraintName
|
||||
from ietf.meeting.models import SchedulingEvent, SessionStatusName
|
||||
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 import settings
|
||||
|
@ -31,6 +38,7 @@ try:
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
except ImportError as e:
|
||||
skip_selenium = True
|
||||
skip_message = "Skipping selenium tests: %s" % e
|
||||
|
@ -51,29 +59,37 @@ def start_web_driver():
|
|||
options.add_argument("no-sandbox") # docker needs this
|
||||
return webdriver.Chrome(options=options, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class EditMeetingScheduleTests(IetfLiveServerTestCase):
|
||||
class MeetingTestCase(IetfLiveServerTestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MeetingTestCase, self).__init__(*args, **kwargs)
|
||||
self.login_view = 'ietf.ietfauth.views.login'
|
||||
|
||||
def setUp(self):
|
||||
super(MeetingTestCase, self).setUp()
|
||||
self.driver = start_web_driver()
|
||||
self.driver.set_window_size(1024,768)
|
||||
|
||||
def tearDown(self):
|
||||
self.driver.close()
|
||||
|
||||
def absreverse(self,*args,**kwargs):
|
||||
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
|
||||
|
||||
def login(self, username='plain'):
|
||||
url = self.absreverse(self.login_view)
|
||||
password = '%s+password' % username
|
||||
self.driver.get(url)
|
||||
self.driver.find_element_by_name('username').send_keys(username)
|
||||
self.driver.find_element_by_name('password').send_keys(password)
|
||||
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
|
||||
|
||||
def debug_snapshot(self,filename='debug_this.png'):
|
||||
self.driver.execute_script("document.body.bgColor = 'white';")
|
||||
self.driver.save_screenshot(filename)
|
||||
|
||||
def absreverse(self,*args,**kwargs):
|
||||
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
|
||||
|
||||
def login(self):
|
||||
url = self.absreverse('ietf.ietfauth.views.login')
|
||||
self.driver.get(url)
|
||||
self.driver.find_element_by_name('username').send_keys('plain')
|
||||
self.driver.find_element_by_name('password').send_keys('plain+password')
|
||||
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class EditMeetingScheduleTests(MeetingTestCase):
|
||||
def test_edit_meeting_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
|
@ -258,28 +274,7 @@ class EditMeetingScheduleTests(IetfLiveServerTestCase):
|
|||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
@skipIf(django.VERSION[0]==2, "Skipping test with race conditions under Django 2")
|
||||
class ScheduleEditTests(IetfLiveServerTestCase):
|
||||
def setUp(self):
|
||||
self.driver = start_web_driver()
|
||||
self.driver.set_window_size(1024,768)
|
||||
|
||||
def tearDown(self):
|
||||
self.driver.close()
|
||||
|
||||
def debug_snapshot(self,filename='debug_this.png'):
|
||||
self.driver.execute_script("document.body.bgColor = 'white';")
|
||||
self.driver.save_screenshot(filename)
|
||||
|
||||
def absreverse(self,*args,**kwargs):
|
||||
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
|
||||
|
||||
def login(self):
|
||||
url = self.absreverse('ietf.ietfauth.views.login')
|
||||
self.driver.get(url)
|
||||
self.driver.find_element_by_name('username').send_keys('plain')
|
||||
self.driver.find_element_by_name('password').send_keys('plain+password')
|
||||
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
|
||||
|
||||
class ScheduleEditTests(MeetingTestCase):
|
||||
def testUnschedule(self):
|
||||
|
||||
meeting = make_meeting_test_data()
|
||||
|
@ -317,27 +312,16 @@ class ScheduleEditTests(IetfLiveServerTestCase):
|
|||
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),0)
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class SlideReorderTests(IetfLiveServerTestCase):
|
||||
class SlideReorderTests(MeetingTestCase):
|
||||
def setUp(self):
|
||||
self.driver = start_web_driver()
|
||||
self.driver.set_window_size(1024,768)
|
||||
super(SlideReorderTests, self).setUp()
|
||||
self.session = SessionFactory(meeting__type_id='ietf', status_id='sched')
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1)
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2)
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3)
|
||||
|
||||
def tearDown(self):
|
||||
self.driver.close()
|
||||
|
||||
def absreverse(self,*args,**kwargs):
|
||||
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
|
||||
|
||||
def secr_login(self):
|
||||
url = '%s%s'%(self.live_server_url, urlreverse('ietf.ietfauth.views.login'))
|
||||
self.driver.get(url)
|
||||
self.driver.find_element_by_name('username').send_keys('secretary')
|
||||
self.driver.find_element_by_name('password').send_keys('secretary+password')
|
||||
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
|
||||
self.login('secretary')
|
||||
|
||||
#@override_settings(DEBUG=True)
|
||||
def testReorderSlides(self):
|
||||
|
@ -357,6 +341,477 @@ class SlideReorderTests(IetfLiveServerTestCase):
|
|||
names=self.session.sessionpresentation_set.values_list('document__name',flat=True)
|
||||
self.assertEqual(list(names),['one','three','two'])
|
||||
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class AgendaTests(MeetingTestCase):
|
||||
def setUp(self):
|
||||
super(AgendaTests, self).setUp()
|
||||
self.meeting = make_meeting_test_data()
|
||||
|
||||
def row_id_for_item(self, item):
|
||||
return 'row-%s' % item.slug()
|
||||
|
||||
def get_expected_items(self):
|
||||
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"""
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
|
||||
|
||||
parse_query_params = 'return agenda_filter_for_testing.parse_query_params'
|
||||
|
||||
# Only 'show' param
|
||||
result = self.driver.execute_script(
|
||||
parse_query_params + '("?show=group1,group2,group3");'
|
||||
)
|
||||
self.assertEqual(result, dict(show='group1,group2,group3'))
|
||||
|
||||
# Only 'hide' param
|
||||
result = self.driver.execute_script(
|
||||
parse_query_params + '("?hide=group4,group5,group6");'
|
||||
)
|
||||
self.assertEqual(result, dict(hide='group4,group5,group6'))
|
||||
|
||||
# Both 'show' and 'hide'
|
||||
result = self.driver.execute_script(
|
||||
parse_query_params + '("?show=group1,group2,group3&hide=group4,group5,group6");'
|
||||
)
|
||||
self.assertEqual(result, dict(show='group1,group2,group3', hide='group4,group5,group6'))
|
||||
|
||||
# Encoded
|
||||
result = self.driver.execute_script(
|
||||
parse_query_params + '("?show=%20group1,%20group2,%20group3&hide=group4,group5,group6");'
|
||||
)
|
||||
self.assertEqual(result, dict(show=' group1, group2, group3', hide='group4,group5,group6'))
|
||||
|
||||
def test_agenda_view_js_func_toggle_list_item(self):
|
||||
"""Test toggle_list_item() function"""
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
|
||||
|
||||
result = self.driver.execute_script(
|
||||
"""
|
||||
// start empty, add item
|
||||
var list0=[];
|
||||
%(toggle_list_item)s(list0, 'item');
|
||||
|
||||
// one item, remove it
|
||||
var list1=['item'];
|
||||
%(toggle_list_item)s(list1, 'item');
|
||||
|
||||
// one item, add another
|
||||
var list2=['item1'];
|
||||
%(toggle_list_item)s(list2, 'item2');
|
||||
|
||||
// multiple items, remove first
|
||||
var list3=['item1', 'item2', 'item3'];
|
||||
%(toggle_list_item)s(list3, 'item1');
|
||||
|
||||
// multiple items, remove middle
|
||||
var list4=['item1', 'item2', 'item3'];
|
||||
%(toggle_list_item)s(list4, 'item2');
|
||||
|
||||
// multiple items, remove last
|
||||
var list5=['item1', 'item2', 'item3'];
|
||||
%(toggle_list_item)s(list5, 'item3');
|
||||
|
||||
return [list0, list1, list2, list3, list4, list5];
|
||||
""" % {'toggle_list_item': 'agenda_filter_for_testing.toggle_list_item'}
|
||||
)
|
||||
self.assertEqual(result[0], ['item'], 'Adding item to empty list failed')
|
||||
self.assertEqual(result[1], [], 'Removing only item in a list failed')
|
||||
self.assertEqual(result[2], ['item1', 'item2'], 'Adding second item to list failed')
|
||||
self.assertEqual(result[3], ['item2', 'item3'], 'Removing first item from list failed')
|
||||
self.assertEqual(result[4], ['item1', 'item3'], 'Removing middle item from list failed')
|
||||
self.assertEqual(result[5], ['item1', 'item2'], 'Removing last item from list failed')
|
||||
|
||||
def do_agenda_view_filter_test(self, querystring, visible_groups=()):
|
||||
self.login()
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + querystring)
|
||||
self.assert_agenda_item_visibility(visible_groups)
|
||||
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')
|
||||
else:
|
||||
self.assertTrue(weekview_iframe.is_displayed(), 'Weekview should be visible when filters on')
|
||||
self.driver.switch_to.frame(weekview_iframe)
|
||||
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_show_one(self):
|
||||
"""Filtered agenda view should display only matching rows (one group selected)"""
|
||||
self.do_agenda_view_filter_test('?show=mars', ['mars'])
|
||||
|
||||
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_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_hide(self):
|
||||
self.do_agenda_view_filter_test('?hide=ietf', [])
|
||||
|
||||
def test_agenda_view_filter_hide_area(self):
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
area = mars.parent
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=%s' % area.acronym, [])
|
||||
|
||||
def test_agenda_view_filter_show_and_hide(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=ietf', ['mars'])
|
||||
|
||||
def test_agenda_view_filter_show_and_hide_same_group(self):
|
||||
self.do_agenda_view_filter_test('?show=mars&hide=mars', [])
|
||||
|
||||
def test_agenda_view_filter_showtypes(self):
|
||||
self.do_agenda_view_filter_test('?showtypes=plenary', ['ietf']) # ietf has a plenary session
|
||||
|
||||
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
|
||||
|
||||
If visible_groups is None (the default), expects all items to be visible.
|
||||
"""
|
||||
for item in self.get_expected_items():
|
||||
row_id = self.row_id_for_item(item)
|
||||
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)
|
||||
if visible_groups is None or item.session.group.acronym in visible_groups:
|
||||
self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed but should be' % row_id)
|
||||
else:
|
||||
self.assertFalse(item_row.is_displayed(), 'Row for schedule item "%s" is displayed but should not be' % row_id)
|
||||
|
||||
def assert_weekview_item_visibility(self, visible_groups=None):
|
||||
for item in self.get_expected_items():
|
||||
if item.session.name:
|
||||
label = item.session.name
|
||||
elif item.timeslot.type_id == 'break':
|
||||
label = item.timeslot.name
|
||||
elif item.session.group:
|
||||
label = item.session.group.name
|
||||
else:
|
||||
label = 'Free Slot'
|
||||
|
||||
try:
|
||||
item_div = self.driver.find_element_by_xpath('//div/span[contains(text(),"%s")]/..' % label)
|
||||
except NoSuchElementException:
|
||||
item_div = None
|
||||
|
||||
if visible_groups is None or item.session.group.acronym in visible_groups:
|
||||
self.assertIsNotNone(item_div, 'No weekview entry for "%s" (%s)' % (label, item.slug()))
|
||||
self.assertTrue(item_div.is_displayed(), 'Entry for "%s (%s)" is not displayed but should be' % (label, item.slug()))
|
||||
else:
|
||||
self.assertIsNone(item_div, 'Unexpected weekview entry for "%s" (%s)' % (label, item.slug()))
|
||||
|
||||
def test_agenda_view_group_filter_toggle(self):
|
||||
"""Clicking a group toggle enables/disables agenda filtering"""
|
||||
group_acronym = 'mars'
|
||||
|
||||
self.login()
|
||||
url = self.absreverse('ietf.meeting.views.agenda')
|
||||
self.driver.get(url)
|
||||
|
||||
# Click the 'customize' anchor to reveal the group buttons
|
||||
customize_anchor = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
|
||||
)
|
||||
)
|
||||
customize_anchor.click()
|
||||
|
||||
# Click the group button
|
||||
group_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
|
||||
)
|
||||
)
|
||||
group_button.click()
|
||||
|
||||
# Check visibility
|
||||
self.assert_agenda_item_visibility([group_acronym])
|
||||
|
||||
# Click the group button again
|
||||
group_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
|
||||
)
|
||||
)
|
||||
group_button.click()
|
||||
|
||||
# Check visibility
|
||||
self.assert_agenda_item_visibility()
|
||||
|
||||
def test_agenda_view_group_filter_toggle_without_replace_state(self):
|
||||
"""Toggle should function for browsers without window.history.replaceState"""
|
||||
group_acronym = 'mars'
|
||||
|
||||
self.login()
|
||||
url = self.absreverse('ietf.meeting.views.agenda')
|
||||
self.driver.get(url)
|
||||
|
||||
# Rather than digging up an ancient browser, simulate absence of history.replaceState
|
||||
self.driver.execute_script('window.history.replaceState = undefined;')
|
||||
|
||||
|
||||
# Click the 'customize' anchor to reveal the group buttons
|
||||
customize_anchor = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
|
||||
)
|
||||
)
|
||||
customize_anchor.click()
|
||||
|
||||
|
||||
# Get ready to click the group button
|
||||
group_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
|
||||
)
|
||||
)
|
||||
|
||||
# Be sure we're at the URL we think we're at before we click
|
||||
self.assertEqual(self.driver.current_url, url)
|
||||
group_button.click() # click!
|
||||
|
||||
expected_url = '%s?show=%s' % (url, group_acronym)
|
||||
WebDriverWait(self.driver, 2).until(expected_conditions.url_to_be(expected_url))
|
||||
# no assertion here - if WebDriverWait raises an exception, the test will fail.
|
||||
# We separately test whether this URL will filter correctly.
|
||||
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class InterimTests(MeetingTestCase):
|
||||
def setUp(self):
|
||||
super(InterimTests, self).setUp()
|
||||
self.materials_dir = self.tempdir('materials')
|
||||
self.saved_agenda_path = settings.AGENDA_PATH
|
||||
settings.AGENDA_PATH = self.materials_dir
|
||||
self.meeting = make_meeting_test_data(create_interims=True)
|
||||
|
||||
def tearDown(self):
|
||||
settings.AGENDA_PATH = self.saved_agenda_path
|
||||
shutil.rmtree(self.materials_dir)
|
||||
super(InterimTests, self).tearDown()
|
||||
|
||||
def tempdir(self, label):
|
||||
# Borrowed from test_utils.TestCase
|
||||
slug = slugify(self.__class__.__name__.replace('.','-'))
|
||||
dirname = "tmp-{label}-{slug}-dir".format(**locals())
|
||||
if 'VIRTUAL_ENV' in os.environ:
|
||||
dirname = os.path.join(os.environ['VIRTUAL_ENV'], dirname)
|
||||
path = os.path.abspath(dirname)
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
return path
|
||||
|
||||
def displayed_interims(self, groups=None):
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting__type_id='interim',
|
||||
timeslotassignments__schedule=F('meeting__schedule'),
|
||||
timeslotassignments__timeslot__time__gte=datetime.datetime.today()
|
||||
)
|
||||
).filter(current_status__in=('sched','canceled'))
|
||||
meetings = []
|
||||
for s in sessions:
|
||||
if groups is None or s.group.acronym in groups:
|
||||
s.meeting.calendar_label = s.group.acronym # annotate with group
|
||||
meetings.append(s.meeting)
|
||||
return meetings
|
||||
|
||||
def all_ietf_meetings(self):
|
||||
meetings = Meeting.objects.filter(
|
||||
type_id='ietf',
|
||||
date__gte=datetime.datetime.today()-datetime.timedelta(days=7)
|
||||
)
|
||||
for m in meetings:
|
||||
m.calendar_label = 'IETF %s' % m.number
|
||||
return meetings
|
||||
|
||||
def assert_upcoming_meeting_visibility(self, visible_meetings=None):
|
||||
"""Assert that correct items are visible in current browser window
|
||||
|
||||
If visible_meetings is None (the default), expects all items to be visible.
|
||||
"""
|
||||
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'
|
||||
)
|
||||
for entry in entries:
|
||||
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
|
||||
if entry.is_displayed():
|
||||
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.")
|
||||
self.assertEqual(unexpected, set(), "Unexpected row visible")
|
||||
|
||||
def assert_upcoming_meeting_calendar(self, visible_meetings=None):
|
||||
"""Assert that correct items are sent to the calendar"""
|
||||
def advance_month():
|
||||
button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, 'div#calendar button.fc-next-button')))
|
||||
button.click()
|
||||
|
||||
seen = set()
|
||||
not_visible = set()
|
||||
unexpected = set()
|
||||
|
||||
# Test that we see all the expected meetings when we scroll through the
|
||||
# entire year. We only check the group names / IETF numbers. This should
|
||||
# be good enough to catch filtering errors but does not validate the
|
||||
# details of what's shown on the calendar. Need 13 iterations instead of
|
||||
# 12 in order to check the starting month of the following year, which
|
||||
# will usually contain the day 1 year from the start date.
|
||||
for _ in range(13):
|
||||
entries = self.driver.find_elements_by_css_selector(
|
||||
'div#calendar div.fc-content'
|
||||
)
|
||||
for entry in entries:
|
||||
meetings = [m for m in visible_meetings if m.calendar_label in entry.text]
|
||||
if len(meetings) > 0:
|
||||
seen.add(meetings[0])
|
||||
if not entry.is_displayed():
|
||||
not_visible.add(meetings[0])
|
||||
continue
|
||||
# Found an unexpected row - this is ok as long as it's hidden
|
||||
if entry.is_displayed():
|
||||
unexpected.add(entry.text)
|
||||
advance_month()
|
||||
|
||||
self.assertEqual(seen, visible_meetings, "Expected calendar entries not shown.")
|
||||
self.assertEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
|
||||
self.assertEqual(unexpected, set(), "Unexpected calendar entries visible")
|
||||
|
||||
def do_upcoming_view_filter_test(self, querystring, visible_meetings=()):
|
||||
self.login()
|
||||
self.driver.get(self.absreverse('ietf.meeting.views.upcoming') + querystring)
|
||||
self.assert_upcoming_meeting_visibility(visible_meetings)
|
||||
self.assert_upcoming_meeting_calendar(visible_meetings)
|
||||
|
||||
# Check the ical links
|
||||
simplified_querystring = querystring.replace(' ', '%20') # encode spaces'
|
||||
ics_link = self.driver.find_element_by_link_text('Download as .ics')
|
||||
self.assertIn(simplified_querystring, ics_link.get_attribute('href'))
|
||||
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')
|
||||
self.assertIn(simplified_querystring, webcal_link.get_attribute('href'))
|
||||
|
||||
def test_upcoming_view_displays_all_interims(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)
|
||||
|
||||
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_one(self):
|
||||
meetings = set(self.all_ietf_meetings())
|
||||
meetings.update(self.displayed_interims(groups=['mars']))
|
||||
self.do_upcoming_view_filter_test('?show=mars', meetings)
|
||||
|
||||
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)
|
||||
|
||||
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_whitespace(self):
|
||||
"""Whitespace in filter lists should be ignored"""
|
||||
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)
|
||||
|
||||
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
|
||||
|
|
|
@ -22,6 +22,7 @@ from django.conf import settings
|
|||
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
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -37,7 +38,7 @@ from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignm
|
|||
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting, make_interim_test_data
|
||||
from ietf.meeting.utils import finalize, condition_slide_order
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.meeting.views import session_draft_list
|
||||
from ietf.meeting.views import session_draft_list, parse_agenda_filter_params
|
||||
from ietf.name.models import SessionStatusName, ImportantDateName, RoleName
|
||||
from ietf.utils.decorators import skip_coverage
|
||||
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
||||
|
@ -61,6 +62,33 @@ else:
|
|||
"location indicated in settings.py.")
|
||||
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')
|
||||
|
@ -201,7 +229,7 @@ class MeetingTests(TestCase):
|
|||
|
||||
# iCal
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs=dict(num=meeting.number))
|
||||
+ "?" + session.group.parent.acronym.upper())
|
||||
+ "?show=" + session.group.parent.acronym.upper())
|
||||
self.assertContains(r, session.group.acronym)
|
||||
self.assertContains(r, session.group.name)
|
||||
self.assertContains(r, slot.location.name)
|
||||
|
@ -607,23 +635,282 @@ class MeetingTests(TestCase):
|
|||
#
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'acronym':s1.group.acronym, })
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.get('Content-Type'), "text/calendar")
|
||||
self.assertContains(r, 'BEGIN:VEVENT')
|
||||
self.assertEqual(r.content.count(b'UID'), 2)
|
||||
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
|
||||
assert_ical_response_is_valid(self,
|
||||
r,
|
||||
expected_event_summaries=['mars - Martian Special Interest Group'],
|
||||
expected_event_count=2)
|
||||
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, 'END:VEVENT')
|
||||
#
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, })
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.get('Content-Type'), "text/calendar")
|
||||
self.assertContains(r, 'BEGIN:VEVENT')
|
||||
self.assertEqual(r.content.count(b'UID'), 1)
|
||||
self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=['mars - Martian Special Interest Group'],
|
||||
expected_event_count=1)
|
||||
self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S'))
|
||||
self.assertContains(r, 'END:VEVENT')
|
||||
|
||||
def test_meeting_agenda_has_static_ical_links(self):
|
||||
"""Links to the agenda_ical view must appear on the agenda page
|
||||
|
||||
Confirms that these have the correct querystrings. Does not test the JS-based
|
||||
'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)
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
||||
def test_parse_agenda_filter_params(self):
|
||||
def _r(show=(), hide=(), showtypes=(), hidetypes=()):
|
||||
"""Helper to create expected result dict"""
|
||||
return dict(show=set(show), hide=set(hide), showtypes=set(showtypes), hidetypes=set(hidetypes))
|
||||
|
||||
self.assertIsNone(parse_agenda_filter_params(QueryDict('')))
|
||||
|
||||
self.assertRaises(ValueError, parse_agenda_filter_params, QueryDict('unknown')) # unknown param
|
||||
self.assertRaises(ValueError, parse_agenda_filter_params, QueryDict('unknown=x')) # unknown param
|
||||
|
||||
# test valid combos (not exhaustive)
|
||||
for qstr, expected in (
|
||||
('show=', _r()), ('hide=', _r()), ('showtypes=', _r()), ('hidetypes=', _r()),
|
||||
('show=x', _r(show=['x'])), ('hide=x', _r(hide=['x'])),
|
||||
('showtypes=x', _r(showtypes=['x'])), ('hidetypes=x', _r(hidetypes=['x'])),
|
||||
('show=x,y,z', _r(show=['x','y','z'])),
|
||||
('hide=x,y,z', _r(hide=['x','y','z'])),
|
||||
('showtypes=x,y,z', _r(showtypes=['x','y','z'])),
|
||||
('hidetypes=x,y,z', _r(hidetypes=['x','y','z'])),
|
||||
('show=a&hide=a', _r(show=['a'], hide=['a'])),
|
||||
('show=a&hide=b', _r(show=['a'], hide=['b'])),
|
||||
('show=a&hide=b&showtypes=c&hidetypes=d', _r(show=['a'], hide=['b'], showtypes=['c'], hidetypes=['d'])),
|
||||
):
|
||||
self.assertEqual(
|
||||
parse_agenda_filter_params(QueryDict(qstr)),
|
||||
expected,
|
||||
'Parsed "%s" incorrectly' % qstr,
|
||||
)
|
||||
|
||||
def test_ical_filter_invalid_syntaxes(self):
|
||||
meeting = make_meeting_test_data()
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number})
|
||||
|
||||
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 do_ical_filter_test(self, meeting, querystring, expected_session_summaries):
|
||||
url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number})
|
||||
r = self.client.get(url + querystring)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
assert_ical_response_is_valid(self,
|
||||
r,
|
||||
expected_event_summaries=expected_session_summaries,
|
||||
expected_event_count=len(expected_session_summaries))
|
||||
|
||||
def test_ical_filter_default(self):
|
||||
meeting = make_meeting_test_data()
|
||||
self.do_ical_filter_test(
|
||||
meeting,
|
||||
querystring='',
|
||||
expected_session_summaries=[
|
||||
'Morning Break',
|
||||
'Registration',
|
||||
'IETF Plenary',
|
||||
'ames - Asteroid Mining Equipment Standardization Group',
|
||||
'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,
|
||||
expected_session_summaries=[
|
||||
'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',
|
||||
]
|
||||
)
|
||||
|
||||
def build_session_setup(self):
|
||||
# This setup is intentionally unusual - the session has one draft attached as a session presentation,
|
||||
|
@ -1927,45 +2214,210 @@ class InterimTests(TestCase):
|
|||
#self.assertIn('CANCELLED', q('[id*="'+id+'"]').text())
|
||||
self.assertIn('CANCELLED', q('tr>td>a>span').text())
|
||||
|
||||
def test_upcoming(self):
|
||||
make_meeting_test_data(create_interims=True)
|
||||
def do_upcoming_test(self, querystring=None, create_meeting=True):
|
||||
if create_meeting:
|
||||
make_meeting_test_data(create_interims=True)
|
||||
url = urlreverse("ietf.meeting.views.upcoming")
|
||||
if querystring is not None:
|
||||
url += '?' + querystring
|
||||
|
||||
today = datetime.date.today()
|
||||
mars_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
ames_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='ames')).filter(current_status='canceled').first().meeting
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r, mars_interim.number)
|
||||
self.assertContains(r, ames_interim.number)
|
||||
interims = dict(
|
||||
mars=add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='mars')).filter(current_status='sched').first().meeting,
|
||||
ames=add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='ames')).filter(current_status='canceled').first().meeting,
|
||||
)
|
||||
self.check_interim_tabs(url)
|
||||
return self.client.get(url), interims
|
||||
|
||||
def test_upcoming(self):
|
||||
r, interims = self.do_upcoming_test()
|
||||
self.assertContains(r, interims['mars'].number)
|
||||
self.assertContains(r, interims['ames'].number)
|
||||
self.assertContains(r, 'IETF 72')
|
||||
# cancelled session
|
||||
q = PyQuery(r.content)
|
||||
self.assertIn('CANCELLED', q('tr>td.text-right>span').text())
|
||||
self.check_interim_tabs(url)
|
||||
|
||||
def test_upcoming_filters_ignored(self):
|
||||
"""The upcoming view should ignore filter querystrings"""
|
||||
r, interims = self.do_upcoming_test()
|
||||
self.assertContains(r, interims['mars'].number)
|
||||
self.assertContains(r, interims['ames'].number)
|
||||
self.assertContains(r, 'IETF 72')
|
||||
|
||||
r, interims = self.do_upcoming_test('show=ames', create_meeting=False)
|
||||
self.assertContains(r, interims['mars'].number)
|
||||
self.assertContains(r, interims['ames'].number)
|
||||
self.assertContains(r, 'IETF 72')
|
||||
|
||||
r, interims = self.do_upcoming_test('show=ames&hide=ames,mars', create_meeting=False)
|
||||
self.assertContains(r, interims['mars'].number)
|
||||
self.assertContains(r, interims['ames'].number)
|
||||
self.assertContains(r, 'IETF 72')
|
||||
|
||||
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)
|
||||
|
||||
url = urlreverse("ietf.meeting.views.upcoming_ical")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# 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())
|
||||
|
||||
today = datetime.date.today()
|
||||
mars_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
ames_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='ames')).filter(current_status='canceled').first().meeting
|
||||
self.assertContains(r, mars_interim.number)
|
||||
self.assertContains(r, ames_interim.number)
|
||||
self.assertContains(r, 'IETF 72')
|
||||
self.assertEqual(r.get('Content-Type'), "text/calendar")
|
||||
self.assertEqual(r.content.count(b'UID'), 3 + 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)
|
||||
|
||||
# check filtered output
|
||||
url = url + '?filters=mars'
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.get('Content-Type'), "text/calendar")
|
||||
self.assertEqual(r.content.count(b'UID'), 2 + meeting.importantdate_set.count())
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_upcoming_ical_filter_hide_and_showtypes(self):
|
||||
r = self.do_upcoming_ical_test('hide=mars&showtypes=regular')
|
||||
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_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)
|
||||
|
||||
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_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')
|
||||
assert_ical_response_is_valid(self, r,
|
||||
expected_event_summaries=[
|
||||
'mars - Martian Special Interest Group',
|
||||
'IETF 72',
|
||||
],
|
||||
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_json(self):
|
||||
make_meeting_test_data(create_interims=True)
|
||||
|
|
|
@ -25,8 +25,9 @@ from wsgiref.handlers import format_date_time
|
|||
|
||||
from django import forms
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import ( HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseNotFound,
|
||||
Http404, JsonResponse)
|
||||
from django.http import (HttpResponse, HttpResponseRedirect, HttpResponseForbidden,
|
||||
HttpResponseNotFound, Http404, HttpResponseBadRequest,
|
||||
JsonResponse)
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
@ -1773,7 +1774,78 @@ def ical_session_status(assignment):
|
|||
else:
|
||||
return "CONFIRMED"
|
||||
|
||||
def parse_agenda_filter_params(querydict):
|
||||
"""Parse agenda filter parameters from a request"""
|
||||
if len(querydict) == 0:
|
||||
return None
|
||||
|
||||
# Parse group filters from GET parameters. The keys in this dict define the
|
||||
# allowed querystring parameters.
|
||||
filt_params = {'show': set(), 'hide': set(), 'showtypes': set(), 'hidetypes': set()}
|
||||
|
||||
for key, value in querydict.items():
|
||||
if key not in filt_params:
|
||||
raise ValueError('Unrecognized parameter "%s"' % key)
|
||||
if value is None:
|
||||
return ValueError(
|
||||
'Parameter "%s" is not assigned a value (use "key=" for an empty value)' % key
|
||||
)
|
||||
vals = unquote(value).lower().split(',')
|
||||
filt_params[key] = set([v for v in vals if len(v) > 0]) # remove empty strings
|
||||
|
||||
return filt_params
|
||||
|
||||
|
||||
def should_include_assignment(filter_params, assignment):
|
||||
"""Decide whether to include an 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']))
|
||||
|
||||
|
||||
def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
|
||||
"""Agenda ical view
|
||||
|
||||
By default, all agenda items will be shown. A filter can be specified in
|
||||
the querystring. It has the format
|
||||
|
||||
?show=...&hide=...&showtypes=...&hidetypes=...
|
||||
|
||||
where any of the parameters can be omitted. The right-hand side of each
|
||||
'=' is a comma separated list, which can be empty. If none of the filter
|
||||
parameters are specified, no filtering will be applied, even if the query
|
||||
string is not empty.
|
||||
|
||||
The show and hide parameters each take a list of working group (wg) acronyms.
|
||||
The showtypes and hidetypes parameters take a list of session types.
|
||||
|
||||
Hiding (by wg or type) takes priority over showing.
|
||||
"""
|
||||
meeting = get_meeting(num, type_in=None)
|
||||
schedule = get_schedule(meeting, name)
|
||||
updated = meeting.updated()
|
||||
|
@ -1781,41 +1853,20 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None):
|
|||
if schedule is None and acronym is None and session_id is None:
|
||||
raise Http404
|
||||
|
||||
q = request.META.get('QUERY_STRING','') or ""
|
||||
filter = set(unquote(q).lower().split(','))
|
||||
include = [ i for i in filter if not (i.startswith('-') or i.startswith('~')) ]
|
||||
include_types = set(["plenary","other"])
|
||||
exclude = []
|
||||
|
||||
# Process the special flags.
|
||||
# "-wgname" will remove a working group from the output.
|
||||
# "~Type" will add that type to the output.
|
||||
# "-~Type" will remove that type from the output
|
||||
# Current types are:
|
||||
# Session, Other (default on), Break, Plenary (default on)
|
||||
# Non-Working Group "wg names" include:
|
||||
# edu, ietf, tools, iesg, iab
|
||||
|
||||
for item in filter:
|
||||
if len(item) > 2 and item[0] == '-' and item[1] == '~':
|
||||
include_types -= set([item[2:]])
|
||||
elif len(item) > 1 and item[0] == '-':
|
||||
exclude.append(item[1:])
|
||||
elif len(item) > 1 and item[0] == '~':
|
||||
include_types |= set([item[1:]])
|
||||
|
||||
assignments = SchedTimeSessAssignment.objects.filter(
|
||||
schedule__in=[schedule, schedule.base],
|
||||
timeslot__type__private=False,
|
||||
)
|
||||
assignments = preprocess_assignments_for_agenda(assignments, meeting)
|
||||
|
||||
if q:
|
||||
assignments = [a for a in assignments if
|
||||
(a.timeslot.type_id in include_types
|
||||
or (a.session.historic_group and a.session.historic_group.acronym in include)
|
||||
or (a.session.historic_group and a.session.historic_group.historic_parent and a.session.historic_group.historic_parent.acronym in include))
|
||||
and (not a.session.historic_group or a.session.historic_group.acronym not in exclude)]
|
||||
try:
|
||||
filt_params = parse_agenda_filter_params(request.GET)
|
||||
except ValueError as e:
|
||||
return HttpResponseBadRequest(str(e))
|
||||
|
||||
if filt_params is not None:
|
||||
# Apply the filter
|
||||
assignments = [a for a in assignments if should_include_assignment(filt_params, a)]
|
||||
|
||||
if acronym:
|
||||
assignments = [ a for a in assignments if a.session.historic_group and a.session.historic_group.acronym == acronym ]
|
||||
|
@ -3182,24 +3233,40 @@ def past(request):
|
|||
})
|
||||
|
||||
def upcoming(request):
|
||||
'''List of upcoming meetings'''
|
||||
"""List of upcoming meetings"""
|
||||
today = datetime.date.today()
|
||||
|
||||
# Get ietf meetings starting 7 days ago, and interim meetings starting today
|
||||
ietf_meetings = Meeting.objects.filter(type_id='ietf', date__gte=today-datetime.timedelta(days=7))
|
||||
for m in ietf_meetings:
|
||||
m.end = m.date+datetime.timedelta(days=m.days)
|
||||
entries = list(ietf_meetings)
|
||||
|
||||
interim_sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting__type_id='interim',
|
||||
meeting__type_id='interim',
|
||||
timeslotassignments__schedule=F('meeting__schedule'),
|
||||
timeslotassignments__timeslot__time__gte=today
|
||||
)
|
||||
).filter(current_status__in=('sched','canceled'))
|
||||
|
||||
# get groups for group UI display - same algorithm as in agenda(), but
|
||||
# using group / parent instead of historic_group / historic_parent
|
||||
groups = [s.group for s in interim_sessions
|
||||
if s.group
|
||||
and s.group.type_id in ('wg', 'rg', 'ag', 'rag', 'iab', 'program')
|
||||
and s.group.parent]
|
||||
group_parents = {g.parent for g in groups if g.parent}
|
||||
seen = set()
|
||||
for p in group_parents:
|
||||
p.group_list = []
|
||||
for g in groups:
|
||||
if g.acronym not in seen and g.parent.acronym == p.acronym:
|
||||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
for session in interim_sessions:
|
||||
session.historic_group = session.group
|
||||
|
||||
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())
|
||||
|
||||
|
@ -3210,15 +3277,25 @@ def upcoming(request):
|
|||
# add menu actions
|
||||
actions = []
|
||||
if can_request_interim_meeting(request.user):
|
||||
actions.append(('Request new interim meeting',
|
||||
reverse('ietf.meeting.views.interim_request')))
|
||||
actions.append(('Download as .ics',
|
||||
reverse('ietf.meeting.views.upcoming_ical')))
|
||||
actions.append(('Subscribe with webcal',
|
||||
'webcal://'+request.get_host()+reverse('ietf.meeting.views.upcoming_ical')))
|
||||
actions.append(dict(
|
||||
label='Request new interim meeting',
|
||||
url=reverse('ietf.meeting.views.interim_request'),
|
||||
append_filter=False)
|
||||
)
|
||||
actions.append(dict(
|
||||
label='Download as .ics',
|
||||
url=reverse('ietf.meeting.views.upcoming_ical'),
|
||||
append_filter=True)
|
||||
)
|
||||
actions.append(dict(
|
||||
label='Subscribe with webcal',
|
||||
url='webcal://'+request.get_host()+reverse('ietf.meeting.views.upcoming_ical'),
|
||||
append_filter=True)
|
||||
)
|
||||
|
||||
return render(request, 'meeting/upcoming.html', {
|
||||
'entries': entries,
|
||||
'group_parents': group_parents,
|
||||
'menu_actions': actions,
|
||||
'menu_entries': menu_entries,
|
||||
'selected_menu_entry': selected_menu_entry,
|
||||
|
@ -3228,8 +3305,11 @@ def upcoming(request):
|
|||
|
||||
|
||||
def upcoming_ical(request):
|
||||
'''Return Upcoming meetings in iCalendar file'''
|
||||
filters = request.GET.getlist('filters')
|
||||
"""Return Upcoming meetings in iCalendar file
|
||||
|
||||
Filters by wg name and session type.
|
||||
"""
|
||||
filter_params = parse_agenda_filter_params(request.GET)
|
||||
today = datetime.date.today()
|
||||
|
||||
# get meetings starting 7 days ago -- we'll filter out sessions in the past further down
|
||||
|
@ -3246,13 +3326,8 @@ def upcoming_ical(request):
|
|||
).distinct())
|
||||
|
||||
# apply filters
|
||||
if filters:
|
||||
assignments = [a for a in assignments if
|
||||
a.session.group and (
|
||||
a.session.group.acronym in filters or (
|
||||
a.session.group.parent and a.session.group.parent.acronym in filters
|
||||
)
|
||||
) ]
|
||||
if filter_params is not None:
|
||||
assignments = [a for a in assignments if should_include_assignment(filter_params, a)]
|
||||
|
||||
# we already collected sessions with current_status, so reuse those
|
||||
sessions = {s.pk: s for m in meetings for s in m.sessions}
|
||||
|
|
317
ietf/static/ietf/js/agenda/agenda_filter.js
Normal file
317
ietf/static/ietf/js/agenda/agenda_filter.js
Normal file
|
@ -0,0 +1,317 @@
|
|||
var agenda_filter_for_testing = {}; // methods to be accessed for automated testing
|
||||
var agenda_filter = 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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove from list, if present */
|
||||
function remove_list_item (list, item) {
|
||||
var item_index = list.indexOf(item);
|
||||
if (item_index !== -1) {
|
||||
list.splice(item_index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to list if not present, remove if present */
|
||||
function toggle_list_item (list, item) {
|
||||
var item_index = list.indexOf(item);
|
||||
if (item_index === -1) {
|
||||
list.push(item)
|
||||
} else {
|
||||
list.splice(item_index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function parse_query_params (qs) {
|
||||
var params = {}
|
||||
qs = decodeURI(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
|
||||
}
|
||||
|
||||
/* filt = 'show' or 'hide' */
|
||||
function get_filter_from_qparams (qparams, filt) {
|
||||
if (!qparams[filt] || (qparams[filt] === true)) {
|
||||
return [];
|
||||
}
|
||||
return $.map(qparams[filt].split(','), function(s){return s.trim();});
|
||||
}
|
||||
|
||||
function get_filter_params (qparams) {
|
||||
var enabled = !!(qparams.show || qparams.hide || qparams.showtypes || qparams.hidetypes);
|
||||
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'),
|
||||
}
|
||||
}
|
||||
|
||||
function filtering_is_enabled (filter_params) {
|
||||
return filter_params['enabled'];
|
||||
}
|
||||
|
||||
function get_area_items (area) {
|
||||
var types = [];
|
||||
var groups = [];
|
||||
var neg_groups = [];
|
||||
|
||||
$('.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)
|
||||
}
|
||||
});
|
||||
return { 'groups': groups, 'neg_groups': neg_groups, 'types': types };
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
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');
|
||||
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) {
|
||||
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) {
|
||||
elt.addClass('active');
|
||||
} else {
|
||||
elt.removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Update state of the view to match the filters
|
||||
*
|
||||
* Calling the individual update_* functions outside of this method will likely cause
|
||||
* various parts of the page to get out of sync.
|
||||
*/
|
||||
function update_view () {
|
||||
var filter_params = get_filter_params(parse_query_params(window.location.search))
|
||||
update_filter_ui(filter_params)
|
||||
if (update_callback) {
|
||||
update_callback(filter_params)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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
|
||||
* (if supported) or loads the new URL.
|
||||
*/
|
||||
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['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 (qparams.length > 0) {
|
||||
search = '?' + qparams.join('&')
|
||||
}
|
||||
|
||||
// strip out the search / hash, then add back
|
||||
var new_url = window.location.href.replace(/(\?.*)?(#.*)?$/, search + window.location.hash)
|
||||
if (window.history && window.history.replaceState) {
|
||||
// Keep current origin, replace search string, no page reload
|
||||
history.replaceState({}, document.title, new_url)
|
||||
update_view()
|
||||
} else {
|
||||
// No window.history.replaceState support, page reload required
|
||||
window.location = new_url
|
||||
}
|
||||
}
|
||||
|
||||
/* 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();
|
||||
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];
|
||||
|
||||
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);
|
||||
} else {
|
||||
toggle_list_item(fp[param_type], item);
|
||||
remove_list_item(fp[neg_param_type], item);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
function is_disabled(elt) {
|
||||
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);
|
||||
});
|
||||
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
|
||||
function enable () {
|
||||
$(document).ready(function () {
|
||||
update_view()
|
||||
})
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Public interface methods
|
||||
return {
|
||||
enable: enable,
|
||||
filtering_is_enabled: filtering_is_enabled,
|
||||
include_non_area_selectors: function () {enable_non_area = true},
|
||||
set_update_callback: function (cb) {update_callback = cb}
|
||||
}
|
||||
}();
|
|
@ -1,74 +0,0 @@
|
|||
|
||||
function toggle_visibility() {
|
||||
var h = window.location.hash;
|
||||
h = h.replace(/^#?,?/, '');
|
||||
|
||||
// reset UI elements to default state
|
||||
$(".pickview").removeClass("active disabled");
|
||||
$(".pickviewneg").addClass("active");
|
||||
|
||||
if (h) {
|
||||
// if there are items in the hash, hide all rows
|
||||
$('[id^="row-"]').hide();
|
||||
|
||||
// show the customizer
|
||||
$("#customize").collapse("show");
|
||||
|
||||
// loop through the has items and change the UI element and row visibilities accordingly
|
||||
var query_array = [];
|
||||
$.each(h.split(","), function (i, v) {
|
||||
if (v.indexOf("-") == 0) {
|
||||
// this is a "negative" item: when present, hide these rows
|
||||
v = v.replace(/^-/, '');
|
||||
$('[id^="row-"]').filter('[id*="-' + v + '"]').hide();
|
||||
$(".view." + v).find("button").removeClass("active disabled");
|
||||
$("button.pickviewneg." + v).removeClass("active");
|
||||
} else {
|
||||
// this is a regular item: when present, show these rows
|
||||
$('[id^="row-"]').filter('[id*="-' + v + '"]').show();
|
||||
$(".view." + v).find("button").addClass("active disabled");
|
||||
$("button.pickview." + v).addClass("active");
|
||||
query_array.push("filters=" + v)
|
||||
}
|
||||
});
|
||||
|
||||
// adjust the custom .ics link
|
||||
var link = $('a[href*="upcoming.ics"]');
|
||||
var new_href = link.attr("href").split("?")[0]+"?"+query_array.join("&");
|
||||
link.attr("href",new_href);
|
||||
|
||||
} else {
|
||||
// if the hash is empty, show all
|
||||
$('[id^="row-"]').show();
|
||||
// adjust the custom .ics link
|
||||
var link = $('a[href*="upcoming.ics"]');
|
||||
link.attr("href",link.attr("href").split("?")[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$(".pickview, .pickviewneg").click(function () {
|
||||
var h = window.location.hash;
|
||||
var item = $(this).text().trim().toLowerCase();
|
||||
if ($(this).hasClass("pickviewneg")) {
|
||||
item = "-" + item;
|
||||
}
|
||||
|
||||
re = new RegExp('(^|#|,)' + item + "(,|$)");
|
||||
if (h.match(re) == null) {
|
||||
if (h.replace("#", "").length == 0) {
|
||||
h = item;
|
||||
} else {
|
||||
h += "," + item;
|
||||
}
|
||||
h = h.replace(/^#?,/, '');
|
||||
} else {
|
||||
h = h.replace(re, "$2").replace(/^#?,/, '');
|
||||
}
|
||||
window.location.hash = h.replace(/^#$/, '');
|
||||
toggle_visibility();
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
toggle_visibility();
|
||||
});
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<div class="panel panel-default" id="futuremeets">
|
||||
<div class="panel-heading">
|
||||
Future Meetings
|
||||
<a class="regular pull-right" title="icalendar entry for all scheduled future {{group.acronym}} meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}?filters={{group.acronym}}"><span class="fa fa-calendar"></span></a>
|
||||
<a class="regular pull-right" title="icalendar entry for all scheduled future {{group.acronym}} meetings" href="{% url 'ietf.meeting.views.upcoming_ical' %}?show={{group.acronym}}"><span class="fa fa-calendar"></span></a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% with sessions=future show_request=True show_ical=True can_edit_materials=can_edit %}
|
||||
|
|
|
@ -62,82 +62,14 @@
|
|||
{% endif %}
|
||||
|
||||
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#accordion" href="#customize">
|
||||
<span class="fa fa-caret-down"></span> Customize the agenda view...
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="customize" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
|
||||
<p>
|
||||
You can customize the agenda view to show only selected sessions,
|
||||
by clicking on groups and areas in the table below.
|
||||
To be able to return to the customized view later, bookmark the resulting URL.
|
||||
</p>
|
||||
|
||||
{% if group_parents|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 100 %}%">
|
||||
<button class="btn btn-default btn-block pickview {{p.acronym|lower}}">{{p.acronym|upper}}</button>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</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}}">
|
||||
{% if group.is_bof %}
|
||||
<i>{{group.acronym}}</i>
|
||||
{% else %}
|
||||
{{group.acronym}}
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<blockquote><i>No WG / RG data available -- no WG / RG sessions have been scheduled yet.</i></blockquote>
|
||||
{% endif %}
|
||||
<p>Also show special sessions of these groups:</p>
|
||||
<div class="btn-group btn-group-justified">
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active iepg"> IEPG</button></div>
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active tools"> Tools</button></div>
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active edu"> EDU</button></div>
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active ietf"> IETF</button></div>
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active iesg"> IESG</button></div>
|
||||
<div class="btn-group"><button class="btn btn-default pickviewneg active iab"> IAB</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "meeting/agenda_filter.html" with group_parents=group_parents non_area_filters=True 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 %}?{{p.acronym|upper}},-~Other,-~Plenary">{{p.acronym|upper}}</a>
|
||||
<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 %}?~Plenary,~Other">Non-area events</a>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
|
@ -193,7 +125,10 @@
|
|||
{% endif %}
|
||||
|
||||
{% if item.timeslot.type.slug == 'break' or item.timeslot.type.slug == 'reg' or item.timeslot.type.slug == 'other' %}
|
||||
<tr id="row-{{ item.slug }}" timeslot-type="{{item.timeslot.type.slug}}">
|
||||
<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}}">
|
||||
<td class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
{% include "meeting/timeslot_start_end.html" %}
|
||||
|
@ -252,7 +187,11 @@
|
|||
|
||||
{% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %}
|
||||
{% if item.session.historic_group %}
|
||||
<tr id="row-{{item.slug}}" 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}}"
|
||||
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 %}>
|
||||
{% if item.timeslot.type.slug == 'plenary' %}
|
||||
<th class="text-nowrap text-right">
|
||||
<span class="hidden-xs">
|
||||
|
@ -375,82 +314,86 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
|
||||
<script>
|
||||
function toggle_visibility() {
|
||||
var h = window.location.hash;
|
||||
h = h.replace(/^#?,?/, '');
|
||||
// Update the agenda display with specified filters
|
||||
function update_agenda_display(filter_params) {
|
||||
var agenda_rows=$('[id^="row-"]')
|
||||
|
||||
// reset UI elements to default state
|
||||
$(".pickview").removeClass("active disabled");
|
||||
$(".pickviewneg").addClass("active");
|
||||
if (!agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
// When filtering is not enabled, show all sessions
|
||||
agenda_rows.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (h) {
|
||||
// if there are items in the hash, hide all rows that are
|
||||
// hidden by default, show all rows that are shown by default
|
||||
$('[id^="row-"]').hide();
|
||||
$.each($(".pickviewneg").text().trim().split(/ +/), function (i, v) {
|
||||
v = v.trim().toLowerCase();
|
||||
$('[id^="row-"]').filter('[id*="-' + v + '"]').show();
|
||||
});
|
||||
// if groups were selected for filtering, hide all rows by default
|
||||
agenda_rows.hide();
|
||||
|
||||
// show the customizer
|
||||
$("#customize").collapse("show");
|
||||
|
||||
// loop through the has items and change the UI element and row visibilities accordingly
|
||||
$.each(h.split(","), function (i, v) {
|
||||
if (v.indexOf("-") == 0) {
|
||||
// this is a "negative" item: when present, hide these rows
|
||||
v = v.replace(/^-/, '');
|
||||
$('[id^="row-"]').filter('[id*="-' + v + '"]').hide();
|
||||
$(".view." + v).find("button").removeClass("active disabled");
|
||||
$("button.pickviewneg." + v).removeClass("active");
|
||||
} else {
|
||||
// this is a regular item: when present, show these rows
|
||||
$('[id^="row-"]').filter('[id*="-' + v + '"]').show();
|
||||
$(".view." + v).find("button").addClass("active disabled");
|
||||
$("button.pickview." + v).addClass("active");
|
||||
}
|
||||
});
|
||||
|
||||
// show the week view
|
||||
$("#weekview").attr("src", "week-view.html" + window.location.hash).removeClass("hidden");
|
||||
|
||||
// show the custom .ics link
|
||||
$("#ical-link").attr("href",$("#ical-link").attr("href").split("?")[0]+"?"+h);
|
||||
$("#ical-link").removeClass("hidden");
|
||||
// loop through the has items and change the UI element and row visibilities accordingly
|
||||
$.each(filter_params['show_groups'], 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();
|
||||
});
|
||||
$.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) {
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
||||
function update_ical_links(filter_params) {
|
||||
var ical_link = $("#ical-link");
|
||||
if (agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
// Replace the query string in the ical link
|
||||
var orig_link_href = ical_link.attr("href").split("?")[0];
|
||||
ical_link.attr("href", orig_link_href+window.location.search);
|
||||
ical_link.removeClass("hidden");
|
||||
} else {
|
||||
// if the hash is empty, show all and hide weekview
|
||||
$('[id^="row-"]').show();
|
||||
$("#ical-link, #weekview").addClass("hidden");
|
||||
ical_link.addClass("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
$(".pickview, .pickviewneg").click(function () {
|
||||
var h = window.location.hash;
|
||||
var item = $(this).text().trim().toLowerCase();
|
||||
if ($(this).hasClass("pickviewneg")) {
|
||||
item = "-" + item;
|
||||
}
|
||||
function update_weekview(filter_params) {
|
||||
var weekview = $("#weekview");
|
||||
|
||||
re = new RegExp('(^|#|,)' + item + "(,|$)");
|
||||
if (h.match(re) == null) {
|
||||
if (h.replace("#", "").length == 0) {
|
||||
h = item;
|
||||
} else {
|
||||
h += "," + item;
|
||||
}
|
||||
h = h.replace(/^#?,/, '');
|
||||
} else {
|
||||
h = h.replace(re, "$2").replace(/^#?,/, '');
|
||||
}
|
||||
window.location.hash = h.replace(/^#$/, '');
|
||||
toggle_visibility();
|
||||
});
|
||||
if (!agenda_filter.filtering_is_enabled(filter_params)) {
|
||||
weekview.addClass("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
toggle_visibility();
|
||||
});
|
||||
// Filtering is enabled
|
||||
weekview.removeClass("hidden");
|
||||
|
||||
var wv_iframe = document.getElementById('weekview');
|
||||
var wv_window = wv_iframe.contentWindow;
|
||||
var new_url = 'week-view.html' + window.location.search;
|
||||
if (wv_iframe.src && wv_window.history && wv_window.history.replaceState) {
|
||||
wv_window.history.replaceState({}, '', new_url);
|
||||
wv_window.draw_calendar()
|
||||
} else {
|
||||
// ho history.replaceState, page reload required
|
||||
wv_iframe.src = new_url;
|
||||
}
|
||||
}
|
||||
|
||||
function update_view(filter_params) {
|
||||
update_agenda_display(filter_params);
|
||||
update_weekview(filter_params)
|
||||
update_ical_links(filter_params)
|
||||
}
|
||||
|
||||
agenda_filter.set_update_callback(update_view);
|
||||
agenda_filter.enable();
|
||||
|
||||
$(".modal").on("show.bs.modal", function () {
|
||||
var i = $(this).find(".frame");
|
||||
|
|
105
ietf/templates/meeting/agenda_filter.html
Normal file
105
ietf/templates/meeting/agenda_filter.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#accordion" href="#customize">
|
||||
<span class="fa fa-caret-down"></span> {% firstof customize_button_text "Customize..."%}
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="customize" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
|
||||
<p>
|
||||
You can customize the agenda view to show only selected sessions,
|
||||
by clicking on groups and areas in the table below.
|
||||
To be able to return to the customized view later, bookmark the resulting URL.
|
||||
</p>
|
||||
|
||||
{% if group_parents|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>
|
||||
{% else %}
|
||||
<blockquote><i>No WG / RG data available -- no WG / RG sessions have been scheduled yet.</i>
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -71,5 +71,4 @@
|
|||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
<script src="{% static 'ietf/js/toggle-visibility.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{# Copyright The IETF Trust 2015, 2020, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load cache %}
|
||||
{% load ietf_filters static classname %}
|
||||
|
@ -31,7 +31,9 @@
|
|||
|
||||
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
|
||||
|
||||
{% if menu_entries %}
|
||||
{% include 'meeting/agenda_filter.html' with group_parents=group_parents customize_button_text="Customize the meeting list..." only%}
|
||||
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for name, url in menu_entries %}
|
||||
<li {% if selected_menu_entry == name.lower %}class="active"{% endif %}>
|
||||
|
@ -42,16 +44,18 @@
|
|||
{% endif %}
|
||||
|
||||
{% if menu_actions %}
|
||||
<div class="buttonlist">
|
||||
{% for name, url in menu_actions %}
|
||||
<a class="btn btn-default" href="{{ url }}">{{ name }}</a>
|
||||
<div id="menu-actions" class="buttonlist">
|
||||
{% for action in menu_actions %}
|
||||
<a class="btn btn-default"
|
||||
data-append-filter="{{ action.append_filter }}"
|
||||
href="{{ action.url }}">{{ action.label }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% cache 600 upcoming-meetings entries.count %}
|
||||
{% if entries %}
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<table id="upcoming-meeting-table" class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
|
@ -62,7 +66,9 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<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 %}">
|
||||
{% if entry|classname == 'Meeting' %}
|
||||
{% with meeting=entry %}
|
||||
<td>{{ meeting.date }} - {{ meeting.end }}</td>
|
||||
|
@ -110,47 +116,152 @@
|
|||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
<script src="{% static 'fullcalendar/core/main.js' %}"></script>
|
||||
<script src="{% static 'fullcalendar/daygrid/main.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
var glue = calendarEl.clientWidth > 720 ? ' ' : '\n';
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
plugins: [ 'dayGrid' ],
|
||||
displayEventTime: false,
|
||||
events: [
|
||||
{% for entry in entries %}
|
||||
{% if entry|classname == 'Meeting' %}
|
||||
// List of all events with meta-info needed for filtering
|
||||
var all_event_list = [{% for entry in entries %}
|
||||
{% if entry|classname == 'Meeting' %}
|
||||
{% with meeting=entry %}
|
||||
{
|
||||
title: 'IETF {{ meeting.number }}',
|
||||
start: '{{meeting.date}}',
|
||||
end: '{{meeting.end}}',
|
||||
url: '{% url 'ietf.meeting.views.agenda' num=meeting.number %}'
|
||||
}{% if not forloop.last %}, {% endif %}
|
||||
{
|
||||
title: 'IETF {{ meeting.number }}',
|
||||
start: '{{meeting.date}}',
|
||||
end: '{{meeting.end}}',
|
||||
url: '{% url 'ietf.meeting.views.agenda' num=meeting.number %}'
|
||||
}{% if not forloop.last %}, {% endif %}
|
||||
{% endwith %}
|
||||
{% else %} {# if it's not a Meeting, it's a Session #}
|
||||
{% else %} {# if it's not a Meeting, it's a Session #}
|
||||
{% with session=entry %}
|
||||
{
|
||||
title: '{{session.official_timeslotassignment.timeslot.utc_start_time|date:"H:i"}}-{{session.official_timeslotassignment.timeslot.utc_end_time|date:"H:i"}}'+glue+'{{session.group.acronym}}',
|
||||
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 %}'
|
||||
}
|
||||
{
|
||||
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 %}',
|
||||
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 %}'
|
||||
}
|
||||
{% endwith %}
|
||||
{% if not forloop.last %}, {% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
],
|
||||
eventRender: function(info) {
|
||||
$(info.el).tooltip({title: info.event.title});
|
||||
},
|
||||
timeFormat: 'H:mm',
|
||||
});
|
||||
{% endif %}
|
||||
{% endfor %}];
|
||||
var filtered_event_list = []; // currently visible list
|
||||
var event_calendar; // handle on the calendar object
|
||||
|
||||
calendar.render();
|
||||
});
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Apply filter_params to the event list and format data for the calendar
|
||||
function filter_calendar_events(filter_params, event_list) {
|
||||
var calendarEl = document.getElementById('calendar');
|
||||
var glue = calendarEl.clientWidth > 720 ? ' ' : '\n';
|
||||
var filtered_output = [];
|
||||
for (var ii = 0; ii < event_list.length; ii++) {
|
||||
var this_event = event_list[ii];
|
||||
if (calendar_event_visible(filter_params, this_event)) {
|
||||
filtered_output.push({
|
||||
title: this_event.title + (this_event.group ? (glue + this_event.group) : ''),
|
||||
start: this_event.start,
|
||||
end: this_event.end,
|
||||
url: this_event.url
|
||||
})
|
||||
}
|
||||
}
|
||||
return filtered_output;
|
||||
}
|
||||
|
||||
// Initialize or update the calendar, updating the filtered event list
|
||||
function update_calendar(filter_params) {
|
||||
filtered_event_list = filter_calendar_events(filter_params, all_event_list);
|
||||
if (event_calendar) {
|
||||
event_calendar.refetchEvents()
|
||||
} else {
|
||||
/* Initialize the calendar object.
|
||||
* The event source is a function that simply returns the current global list of
|
||||
* filtered events.
|
||||
*/
|
||||
var calendarEl = document.getElementById('calendar')
|
||||
event_calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
plugins: ['dayGrid'],
|
||||
displayEventTime: false,
|
||||
events: function (fInfo, success) {success(filtered_event_list)},
|
||||
eventRender: function (info) {
|
||||
$(info.el).tooltip({ title: info.event.title })
|
||||
},
|
||||
timeFormat: 'H:mm',
|
||||
})
|
||||
event_calendar.render()
|
||||
}
|
||||
}
|
||||
|
||||
function update_meeting_display(filter_params) {
|
||||
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();
|
||||
|
||||
$.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['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();
|
||||
});
|
||||
}
|
||||
|
||||
function update_links(filter_params) {
|
||||
var filtered_links = $("#menu-actions [data-append-filter='True']");
|
||||
var filtering_enabled = agenda_filter.filtering_is_enabled(filter_params);
|
||||
filtered_links.each(function(index, elt) {
|
||||
var orig_link_href = $(elt).attr("href").split("?")[0];
|
||||
if (filtering_enabled) {
|
||||
// append new querystring
|
||||
$(elt).attr("href", orig_link_href+window.location.search);
|
||||
} else {
|
||||
// remove querystring
|
||||
$(elt).attr("href", orig_link_href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function update_view(filter_params) {
|
||||
update_meeting_display(filter_params);
|
||||
update_links(filter_params);
|
||||
update_calendar(filter_params);
|
||||
}
|
||||
|
||||
// Set up the filtering - the callback will be called when the page loads and on any filter changes
|
||||
agenda_filter.set_update_callback(update_view);
|
||||
agenda_filter.enable();
|
||||
|
||||
$(".modal").on("show.bs.modal", function () {
|
||||
var i = $(this).find(".frame");
|
||||
|
|
|
@ -107,33 +107,61 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
|
||||
function is_visible(include) {
|
||||
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) {
|
||||
// Returns a method to filter objects for visibility
|
||||
// Accepts show, hide, showtypes, and hidetypes filters. Also 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) {
|
||||
// "-wgname" will remove a working group from the output.
|
||||
// "~Type" will add that type to the output.
|
||||
// "-~Type" will remove that type from the output
|
||||
// "@bof" will include all BOFs
|
||||
// Current types are:
|
||||
// Session, Other, Break, Plenary
|
||||
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 ("group" in item) {
|
||||
if (include[(item.group).toLowerCase()]) { return true; }
|
||||
if (include["-"+(item.group).toLowerCase()]) { return false; }
|
||||
if ((fp['hide_groups'].indexOf(item_group) >= 0) ||
|
||||
(fp['hide_groups'].indexOf(item_area) >= 0) ||
|
||||
(fp['hide_types'].indexOf(item_type) >= 0)) {
|
||||
return false;
|
||||
}
|
||||
if ("state" in item) {
|
||||
if (include["@"+(item.state).toLowerCase()]) { return true; }
|
||||
}
|
||||
if (include["~"+(item.type).toLowerCase()]) { return true; }
|
||||
if (include["-~"+(item.type).toLowerCase()]) { return false; }
|
||||
if ("area" in item) {
|
||||
if (include[(item.area).toLowerCase()]) { return true; }
|
||||
}
|
||||
if (item.type === "Plenary") { return true; }
|
||||
if (item.type === "Other") { return true; }
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,16 +171,23 @@
|
|||
var width = document.body.clientWidth;
|
||||
var height = document.body.clientHeight;
|
||||
|
||||
var include = {};
|
||||
window.location.hash.replace("#",'').split(',').forEach(function(key){
|
||||
include[(key + "").toLowerCase()] = true;
|
||||
});
|
||||
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 visible_items = all_items.filter(is_visible(include));
|
||||
|
||||
var start_day = visible_items[0].day;
|
||||
var start_day;
|
||||
var day_start;
|
||||
if (visible_items.length > 0) {
|
||||
start_day = visible_items[0].day;
|
||||
day_start = visible_items[0].start_time;
|
||||
} else {
|
||||
// fallback in case all items were filtered
|
||||
start_day = all_items[0].day;
|
||||
day_start = all_items[0].start_time;
|
||||
}
|
||||
var end_day = start_day;
|
||||
var day_start = visible_items[0].start_time;
|
||||
var day_end = 0;
|
||||
|
||||
compute_swimlanes(visible_items);
|
||||
|
@ -324,6 +359,11 @@
|
|||
|
||||
document.body.appendChild(e);
|
||||
});
|
||||
|
||||
// Div to indicate rendering has occurred, for testing purposes.
|
||||
var elt = document.createElement('div');
|
||||
elt.id = 'wv-end';
|
||||
document.body.appendChild(elt);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
@ -428,7 +428,6 @@ def make_test_data():
|
|||
|
||||
return draft
|
||||
|
||||
return draft
|
||||
|
||||
def make_review_data(doc):
|
||||
team1 = create_group(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
|
||||
|
|
|
@ -789,6 +789,8 @@ class IetfLiveServerTestCase(StaticLiveServerTestCase):
|
|||
set_coverage_checking(False)
|
||||
super(IetfLiveServerTestCase, cls).setUpClass()
|
||||
|
||||
def setUp(self):
|
||||
super(IetfLiveServerTestCase, self).setUp()
|
||||
# LiveServerTestCase uses TransactionTestCase which seems to
|
||||
# somehow interfere with the fixture loading process in
|
||||
# IetfTestRunner when running multiple tests (the first test
|
||||
|
|
Loading…
Reference in a new issue