Use new querystring format for agenda_ical and add tests. Ensure querystring precedes URL fragment in agenda JS.
- Legacy-Id: 18413
This commit is contained in:
parent
ea3882034a
commit
b1e3c1fe92
|
@ -197,7 +197,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)
|
||||
|
@ -580,6 +580,28 @@ class MeetingTests(TestCase):
|
|||
post_date = meeting.importantdate_set.get(name=idn).date
|
||||
self.assertEqual(pre_date, post_date+datetime.timedelta(days=1))
|
||||
|
||||
def assert_ical_response_is_valid(self, 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.
|
||||
"""
|
||||
self.assertEqual(response.get('Content-Type'), "text/calendar")
|
||||
|
||||
# Validate iCalendar object
|
||||
self.assertContains(response, 'BEGIN:VCALENDAR', count=1)
|
||||
self.assertContains(response, 'END:VCALENDAR', count=1)
|
||||
self.assertContains(response, 'PRODID:', count=1)
|
||||
self.assertContains(response, 'VERSION', count=1)
|
||||
|
||||
# Validate event objects
|
||||
if expected_event_count is None:
|
||||
expected_event_count = len(expected_event_summaries)
|
||||
self.assertContains(response, 'BEGIN:VEVENT', count=expected_event_count)
|
||||
self.assertContains(response, 'END:VEVENT', count=expected_event_count)
|
||||
self.assertContains(response, 'UID', count=expected_event_count)
|
||||
for summary in expected_event_summaries:
|
||||
self.assertContains(response, 'SUMMARY:' + summary)
|
||||
|
||||
def test_group_ical(self):
|
||||
meeting = make_meeting_test_data()
|
||||
s1 = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
|
@ -592,23 +614,226 @@ 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')
|
||||
self.assert_ical_response_is_valid(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')
|
||||
self.assert_ical_response_is_valid(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_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)
|
||||
self.assert_ical_response_is_valid(r, expected_event_summaries=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_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=mars&hide=ames&showtypes=plenary,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,
|
||||
|
|
|
@ -27,7 +27,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from django import forms
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseBadRequest
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
@ -1337,6 +1337,23 @@ def ical_session_status(session_with_current_status):
|
|||
return "CONFIRMED"
|
||||
|
||||
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()
|
||||
|
@ -1344,38 +1361,49 @@ 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 = schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
|
||||
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)]
|
||||
if len(request.GET) > 0:
|
||||
# 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 request.GET.items():
|
||||
if key not in filt_params:
|
||||
return HttpResponseBadRequest('Unrecognized parameter "%s"' % key)
|
||||
if value is None:
|
||||
return HttpResponseBadRequest(
|
||||
'Parameter "%s" is not assigned a value (use "key=" for an empty value)' % key
|
||||
)
|
||||
filt_params[key] = set(unquote(value).lower().split(','))
|
||||
|
||||
def _should_include_assignment(assignment):
|
||||
"""Decide whether to include an assignment
|
||||
|
||||
Relies on filt_params from parent scope.
|
||||
"""
|
||||
historic_group = assignment.session.historic_group
|
||||
if historic_group:
|
||||
group_acronym = historic_group.acronym
|
||||
parent = historic_group.historic_parent
|
||||
parent_acronym = parent.acronym if parent else None
|
||||
else:
|
||||
group_acronym = None
|
||||
parent_acronym = None
|
||||
session_type = assignment.timeslot.type_id
|
||||
|
||||
# Hide if wg or type hide lists apply
|
||||
if (group_acronym in filt_params['hide']) or (session_type in filt_params['hidetypes']):
|
||||
return False
|
||||
|
||||
# Show if any of the show lists apply, including showing by parent group
|
||||
return ((group_acronym in filt_params['show']) or
|
||||
(parent_acronym in filt_params['show']) or
|
||||
(session_type in filt_params['showtypes']))
|
||||
|
||||
# Apply the filter
|
||||
assignments = [a for a in assignments if _should_include_assignment(a)]
|
||||
|
||||
if acronym:
|
||||
assignments = [ a for a in assignments if a.session.historic_group and a.session.historic_group.acronym == acronym ]
|
||||
|
|
|
@ -135,9 +135,9 @@
|
|||
<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>
|
||||
|
||||
|
@ -364,7 +364,7 @@
|
|||
qs = qs.replace(/^\?/, '');
|
||||
$.each(qs.split('&'), function(i, v) {
|
||||
var toks = v.split('=', 2)
|
||||
params[toks[0]] = toks[1];
|
||||
params[toks[0]] = toks[1].toLowerCase();
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
@ -397,9 +397,7 @@
|
|||
|
||||
// loop through the has items and change the UI element and row visibilities accordingly
|
||||
$.each(hide_groups, 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");
|
||||
|
@ -412,14 +410,14 @@
|
|||
});
|
||||
|
||||
// show the week view
|
||||
$("#weekview").attr("src", "week-view.html" + window.location.hash).removeClass("hidden");
|
||||
$("#weekview").attr("src", "week-view.html" + window.location.search).removeClass("hidden");
|
||||
|
||||
// show the custom .ics link
|
||||
$("#ical-link").attr("href",$("#ical-link").attr("href").split("?")[0]+window.location.search);
|
||||
$("#ical-link").removeClass("hidden");
|
||||
|
||||
} else {
|
||||
// if the hash is empty, show all and hide weekview
|
||||
// if the hash is empty, show all and hide weekview / custom ical link
|
||||
$('[id^="row-"]').show();
|
||||
$("#ical-link, #weekview").addClass("hidden");
|
||||
}
|
||||
|
@ -463,7 +461,8 @@
|
|||
search = '?' + qparams.join('&');
|
||||
}
|
||||
|
||||
var new_url = window.location.href.replace(/(\?.*)?$/, search);
|
||||
// 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);
|
||||
|
|
Loading…
Reference in a new issue