Skip to content

Commit bc9c74e

Browse files
fix: Render complete iCalendar event when show_location is False (ietf-tools#5394)
* test: More carefully test event syntax in ical file * test: Test agenda.ics view with all meeting sessions * fix: Render complete iCalendar event when show_location is False * chore: Fix confusing comment
1 parent f40681d commit bc9c74e

3 files changed

Lines changed: 47 additions & 13 deletions

File tree

ietf/meeting/tests_views.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ def test_meeting_agenda(self):
219219
slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule=meeting.schedule)
220220
slot.location.urlresource_set.create(name_id='meetecho_onsite', url='https://onsite.example.com')
221221
slot.location.urlresource_set.create(name_id='meetecho', url='https://meetecho.example.com')
222+
meeting.timeslot_set.filter(type_id="break").update(show_location=False)
222223
#
223224
self.write_materials_files(meeting, session)
224225
#
@@ -348,9 +349,17 @@ def test_meeting_agenda(self):
348349
self.assertContains(r, session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().uploaded_filename)
349350
self.assertNotContains(r, session.materials.filter(type='slides',states__type__slug='slides',states__slug='deleted').first().uploaded_filename)
350351

351-
# iCal
352-
r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs=dict(num=meeting.number))
353-
+ "?show=" + session.group.parent.acronym.upper())
352+
# iCal, no session filtering
353+
ical_url = urlreverse("ietf.meeting.views.agenda_ical", kwargs=dict(num=meeting.number))
354+
r = self.client.get(ical_url)
355+
with open('./ical-output.ics', 'w') as f:
356+
f.write(r.content.decode())
357+
assert_ical_response_is_valid(self, r)
358+
self.assertContains(r, "BEGIN:VTIMEZONE")
359+
self.assertContains(r, "END:VTIMEZONE")
360+
361+
# iCal, single group
362+
r = self.client.get(ical_url + "?show=" + session.group.parent.acronym.upper())
354363
assert_ical_response_is_valid(self, r)
355364
self.assertContains(r, session.group.acronym)
356365
self.assertContains(r, session.group.name)

ietf/templates/meeting/agenda.ics

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ PRODID:-//IETF//datatracker.ietf.org ical agenda//EN
66
UID:ietf-{{schedule.meeting.number}}-{{item.timeslot.pk}}-{{item.session.group.acronym}}
77
SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{{item.session.group_at_the_time.acronym|lower}} - {{item.session.group_at_the_time.name}}{%endif%}{% if item.session.agenda_note %} ({{item.session.agenda_note}}){% endif %}
88
{% if item.timeslot.show_location %}LOCATION:{{item.timeslot.get_location}}
9-
STATUS:{{item.session.ical_status}}
9+
{% endif %}STATUS:{{item.session.ical_status}}
1010
CLASS:PUBLIC
1111
DTSTART{% ics_date_time item.timeslot.local_start_time schedule.meeting.time_zone %}
1212
DTEND{% ics_date_time item.timeslot.local_end_time schedule.meeting.time_zone %}
@@ -29,4 +29,4 @@ DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
2929
\n{# link agenda for ietf meetings #}
3030
See in schedule: {% absurl 'agenda' num=schedule.meeting.number %}#row-{{ item.slug }}\n{% endif %}
3131
END:VEVENT
32-
{% endif %}{% endfor %}END:VCALENDAR{% endcache %}{% endtimezone %}{% endautoescape %}
32+
{% endfor %}END:VCALENDAR{% endcache %}{% endtimezone %}{% endautoescape %}

ietf/utils/test_utils.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def assert_ical_response_is_valid(test_inst, response, expected_event_summaries=
118118
expected_event_uids=None, expected_event_count=None):
119119
"""Validate an HTTP response containing iCal data
120120
121-
Based on RFC2445, but not exhaustive by any means. Assumes a single iCalendar object. Checks that
121+
Based on RFC5545, but not exhaustive by any means. Assumes a single iCalendar object. Checks that
122122
expected_event_summaries/_uids are found, but other events are allowed to be present. Specify the
123123
expected_event_count if you want to reject additional events. If any of these are None,
124124
the check for that property is skipped.
@@ -132,18 +132,43 @@ def assert_ical_response_is_valid(test_inst, response, expected_event_summaries=
132132
test_inst.assertContains(response, 'VERSION', count=1)
133133

134134
# Validate event objects
135+
event_count = 0
136+
uids_found = set()
137+
summaries_found = set()
138+
got_begin = False
139+
cur_event_props = set()
140+
for line_num, line in enumerate(response.content.decode().split("\n")):
141+
line = line.rstrip()
142+
if line == 'BEGIN:VEVENT':
143+
test_inst.assertFalse(got_begin, f"Nested BEGIN:VEVENT found on line {line_num + 1}")
144+
got_begin = True
145+
elif line == 'END:VEVENT':
146+
test_inst.assertTrue(got_begin, f"Unexpected END:VEVENT on line {line_num + 1}")
147+
test_inst.assertIn("uid", cur_event_props, f"Found END:VEVENT without UID on line {line_num + 1}")
148+
got_begin = False
149+
cur_event_props.clear()
150+
event_count += 1
151+
elif got_begin:
152+
# properties in an event
153+
if line.startswith("UID:"):
154+
# mandatory, not more than once
155+
test_inst.assertNotIn("uid", cur_event_props, f"Two UID properties in single event on line {line_num + 1}")
156+
cur_event_props.add("uid")
157+
uids_found.add(line.split(":", 1)[1])
158+
elif line.startswith("SUMMARY:"):
159+
# optional, not more than once
160+
test_inst.assertNotIn("summary", cur_event_props, f"Two SUMMARY properties in single event on line {line_num + 1}")
161+
cur_event_props.add("summary")
162+
summaries_found.add(line.split(":", 1)[1])
163+
135164
if expected_event_summaries is not None:
136-
for summary in expected_event_summaries:
137-
test_inst.assertContains(response, 'SUMMARY:' + summary)
165+
test_inst.assertCountEqual(summaries_found, set(expected_event_summaries))
138166

139167
if expected_event_uids is not None:
140-
for uid in expected_event_uids:
141-
test_inst.assertContains(response, 'UID:' + uid)
168+
test_inst.assertCountEqual(uids_found, set(expected_event_uids))
142169

143170
if expected_event_count is not None:
144-
test_inst.assertContains(response, 'BEGIN:VEVENT', count=expected_event_count)
145-
test_inst.assertContains(response, 'END:VEVENT', count=expected_event_count)
146-
test_inst.assertContains(response, 'UID', count=expected_event_count)
171+
test_inst.assertEqual(event_count, expected_event_count)
147172

148173
# make sure no doubled colons after timestamp properties
149174
test_inst.assertNotContains(response, 'DTSTART::')

0 commit comments

Comments
 (0)