Skip to content

Commit 09ff9c6

Browse files
refactor: Tie Meetecho resources to Session pk (ietf-tools#5281)
* feat: Use session.id to specify session for api_set_session_video_url * feat: Use session.id to specify session for api_upload_bluesheet * refactor: Add audio/video stream and onsite tool URLs to Session model * refactor: Get onsite tool/stream URLs for agenda from Session * refactor: Use Session methods for onsite tool/stream a few more places * refactor: Move hard-coded meetecho URLs into settings.py * feat: Add has_onsite_flag to Session * chore: Set has_onsite_tool for sessions that had meetecho UrlResources * fix: Only show onsite tool URLs when Session.has_onsite_tool is True * test: Update test_api_upload_bluesheet to test deprecated version * fix: Fix test failure in api_upload_bluesheet view * test: Add test of new api_upload_bluesheet view * style: Apply Black style to test_api_upload_bluesheet * fix: Fix test failures in api_upload_bluesheet() * test: Update test_api_set_session_video_url to test deprecated version * fix: Fix test failure in api_set_session_video_url view * test: Add test of new api_set_session_video_url view * style: Apply Black styling to new test * fix: Fix test failures in api_set_session_video_url view * test: Fix test_meeting_agenda; set has_onsite_tool in SessionFactory * feat: Add has_onsite_tool to Session list in admin * feat: Add has_onsite_tool flag to SessionDetailsForm * feat: Add has_onsite_tool flag to sreq * feat: Show has_onsite_tool flag on secr view for a submitted request * feat: Only prompt for has_onsite_tool in sreq for non-wg type groups * fix: Clean up styling of sreq view a bit * chore: Renumber migrations
1 parent 8c98abb commit 09ff9c6

20 files changed

Lines changed: 488 additions & 66 deletions

ietf/api/tests.py

Lines changed: 220 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_api_openid_issuer(self):
5555
r = self.client.get(url)
5656
self.assertContains(r, 'OpenID Connect Issuer', status_code=200)
5757

58-
def test_api_set_session_video_url(self):
58+
def test_deprecated_api_set_session_video_url(self):
5959
url = urlreverse('ietf.meeting.views.api_set_session_video_url')
6060
recmanrole = RoleFactory(group__type_id='ietf', name_id='recman')
6161
recman = recmanrole.person
@@ -84,7 +84,7 @@ def test_api_set_session_video_url(self):
8484
r = self.client.get(url, {'apikey': apikey.hash()} )
8585
self.assertContains(r, "Method not allowed", status_code=405)
8686

87-
r = self.client.post(url, {'apikey': apikey.hash()} )
87+
r = self.client.post(url, {'apikey': apikey.hash(), 'group': group.acronym} )
8888
self.assertContains(r, "Missing meeting parameter", status_code=400)
8989

9090

@@ -136,6 +136,83 @@ def test_api_set_session_video_url(self):
136136
event = doc.latest_event()
137137
self.assertEqual(event.by, recman)
138138

139+
def test_api_set_session_video_url(self):
140+
url = urlreverse("ietf.meeting.views.api_set_session_video_url")
141+
recmanrole = RoleFactory(group__type_id="ietf", name_id="recman")
142+
recman = recmanrole.person
143+
meeting = MeetingFactory(type_id="ietf")
144+
session = SessionFactory(group__type_id="wg", meeting=meeting)
145+
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
146+
video = "https://foo.example.com/bar/beer/"
147+
148+
# error cases
149+
r = self.client.post(url, {})
150+
self.assertContains(r, "Missing apikey parameter", status_code=400)
151+
152+
badrole = RoleFactory(group__type_id="ietf", name_id="ad")
153+
badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person)
154+
badrole.person.user.last_login = timezone.now()
155+
badrole.person.user.save()
156+
r = self.client.post(url, {"apikey": badapikey.hash()})
157+
self.assertContains(r, "Restricted to role: Recording Manager", status_code=403)
158+
159+
r = self.client.post(url, {"apikey": apikey.hash()})
160+
self.assertContains(r, "Too long since last regular login", status_code=400)
161+
recman.user.last_login = timezone.now()
162+
recman.user.save()
163+
164+
r = self.client.get(url, {"apikey": apikey.hash()})
165+
self.assertContains(r, "Method not allowed", status_code=405)
166+
167+
r = self.client.post(url, {"apikey": apikey.hash()})
168+
self.assertContains(r, "Missing session_id parameter", status_code=400)
169+
170+
r = self.client.post(url, {"apikey": apikey.hash(), "session_id": session.pk})
171+
self.assertContains(r, "Missing url parameter", status_code=400)
172+
173+
bad_pk = int(Session.objects.order_by("-pk").first().pk) + 1
174+
r = self.client.post(
175+
url,
176+
{
177+
"apikey": apikey.hash(),
178+
"session_id": bad_pk,
179+
"url": video,
180+
},
181+
)
182+
self.assertContains(r, "Session not found", status_code=400)
183+
184+
r = self.client.post(
185+
url,
186+
{
187+
"apikey": apikey.hash(),
188+
"session_id": "foo",
189+
"url": video,
190+
},
191+
)
192+
self.assertContains(r, "Invalid session_id", status_code=400)
193+
194+
r = self.client.post(
195+
url,
196+
{
197+
"apikey": apikey.hash(),
198+
"session_id": session.pk,
199+
"url": "foobar",
200+
},
201+
)
202+
self.assertContains(r, "Invalid url value: 'foobar'", status_code=400)
203+
204+
r = self.client.post(
205+
url, {"apikey": apikey.hash(), "session_id": session.pk, "url": video}
206+
)
207+
self.assertContains(r, "Done", status_code=200)
208+
209+
recordings = session.recordings()
210+
self.assertEqual(len(recordings), 1)
211+
doc = recordings[0]
212+
self.assertEqual(doc.external_url, video)
213+
event = doc.latest_event()
214+
self.assertEqual(event.by, recman)
215+
139216
def test_api_add_session_attendees(self):
140217
url = urlreverse('ietf.meeting.views.api_add_session_attendees')
141218
otherperson = PersonFactory()
@@ -289,20 +366,20 @@ def test_api_upload_polls_and_chatlog(self):
289366
newdoccontent = get_unicode_document_content(newdoc.name, Path(session.meeting.get_materials_path()) / type_id / newdoc.uploaded_filename)
290367
self.assertEqual(json.loads(content), json.loads(newdoccontent))
291368

292-
def test_api_upload_bluesheet(self):
369+
def test_deprecated_api_upload_bluesheet(self):
293370
url = urlreverse('ietf.meeting.views.api_upload_bluesheet')
294371
recmanrole = RoleFactory(group__type_id='ietf', name_id='recman')
295372
recman = recmanrole.person
296373
meeting = MeetingFactory(type_id='ietf')
297374
session = SessionFactory(group__type_id='wg', meeting=meeting)
298375
group = session.group
299376
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
300-
377+
301378
people = [
302-
{"name":"Andrea Andreotti", "affiliation": "Azienda"},
303-
{"name":"Bosse Bernadotte", "affiliation": "Bolag"},
304-
{"name":"Charles Charlemagne", "affiliation": "Compagnie"},
305-
]
379+
{"name": "Andrea Andreotti", "affiliation": "Azienda"},
380+
{"name": "Bosse Bernadotte", "affiliation": "Bolag"},
381+
{"name": "Charles Charlemagne", "affiliation": "Compagnie"},
382+
]
306383
for i in range(3):
307384
faker = random_faker()
308385
people.append(dict(name=faker.name(), affiliation=faker.company()))
@@ -312,63 +389,63 @@ def test_api_upload_bluesheet(self):
312389
r = self.client.post(url, {})
313390
self.assertContains(r, "Missing apikey parameter", status_code=400)
314391

315-
badrole = RoleFactory(group__type_id='ietf', name_id='ad')
392+
badrole = RoleFactory(group__type_id='ietf', name_id='ad')
316393
badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person)
317394
badrole.person.user.last_login = timezone.now()
318395
badrole.person.user.save()
319-
r = self.client.post(url, {'apikey': badapikey.hash()} )
396+
r = self.client.post(url, {'apikey': badapikey.hash()})
320397
self.assertContains(r, "Restricted to roles: Recording Manager, Secretariat", status_code=403)
321398

322-
r = self.client.post(url, {'apikey': apikey.hash()} )
399+
r = self.client.post(url, {'apikey': apikey.hash()})
323400
self.assertContains(r, "Too long since last regular login", status_code=400)
324401
recman.user.last_login = timezone.now()
325402
recman.user.save()
326403

327-
r = self.client.get(url, {'apikey': apikey.hash()} )
404+
r = self.client.get(url, {'apikey': apikey.hash()})
328405
self.assertContains(r, "Method not allowed", status_code=405)
329406

330-
r = self.client.post(url, {'apikey': apikey.hash()} )
407+
r = self.client.post(url, {'apikey': apikey.hash(), 'group': group.acronym})
331408
self.assertContains(r, "Missing meeting parameter", status_code=400)
332409

333-
334-
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, } )
410+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, })
335411
self.assertContains(r, "Missing group parameter", status_code=400)
336412

337-
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym} )
413+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym})
338414
self.assertContains(r, "Missing item parameter", status_code=400)
339415

340-
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym, 'item': '1'} )
416+
r = self.client.post(url,
417+
{'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym, 'item': '1'})
341418
self.assertContains(r, "Missing bluesheet parameter", status_code=400)
342419

343420
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': '1', 'group': group.acronym,
344-
'item': '1', 'bluesheet': bluesheet, })
421+
'item': '1', 'bluesheet': bluesheet, })
345422
self.assertContains(r, "No sessions found for meeting", status_code=400)
346423

347424
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': 'bogous',
348-
'item': '1', 'bluesheet': bluesheet, })
349-
self.assertContains(r, "No sessions found in meeting '%s' for group 'bogous'"%meeting.number, status_code=400)
425+
'item': '1', 'bluesheet': bluesheet, })
426+
self.assertContains(r, "No sessions found in meeting '%s' for group 'bogous'" % meeting.number, status_code=400)
350427

351428
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
352-
'item': '1', 'bluesheet': "foobar", })
429+
'item': '1', 'bluesheet': "foobar", })
353430
self.assertContains(r, "Invalid json value: 'foobar'", status_code=400)
354431

355432
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
356-
'item': '5', 'bluesheet': bluesheet, })
433+
'item': '5', 'bluesheet': bluesheet, })
357434
self.assertContains(r, "No item '5' found in list of sessions for group", status_code=400)
358435

359436
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
360-
'item': 'foo', 'bluesheet': bluesheet, })
437+
'item': 'foo', 'bluesheet': bluesheet, })
361438
self.assertContains(r, "Expected a numeric value for 'item', found 'foo'", status_code=400)
362439

363440
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
364-
'item': '1', 'bluesheet': bluesheet, })
441+
'item': '1', 'bluesheet': bluesheet, })
365442
self.assertContains(r, "Done", status_code=200)
366443

367444
# Submit again, with slightly different content, as an updated version
368445
people[1]['affiliation'] = 'Bolaget AB'
369446
bluesheet = json.dumps(people)
370447
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
371-
'item': '1', 'bluesheet': bluesheet, })
448+
'item': '1', 'bluesheet': bluesheet, })
372449
self.assertContains(r, "Done", status_code=200)
373450

374451
bluesheet = session.sessionpresentation_set.filter(document__type__slug='bluesheets').first().document
@@ -381,6 +458,124 @@ def test_api_upload_bluesheet(self):
381458
self.assertIn(p['name'], html.unescape(text))
382459
self.assertIn(p['affiliation'], html.unescape(text))
383460

461+
def test_api_upload_bluesheet(self):
462+
url = urlreverse("ietf.meeting.views.api_upload_bluesheet")
463+
recmanrole = RoleFactory(group__type_id="ietf", name_id="recman")
464+
recman = recmanrole.person
465+
meeting = MeetingFactory(type_id="ietf")
466+
session = SessionFactory(group__type_id="wg", meeting=meeting)
467+
group = session.group
468+
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
469+
470+
people = [
471+
{"name": "Andrea Andreotti", "affiliation": "Azienda"},
472+
{"name": "Bosse Bernadotte", "affiliation": "Bolag"},
473+
{"name": "Charles Charlemagne", "affiliation": "Compagnie"},
474+
]
475+
for i in range(3):
476+
faker = random_faker()
477+
people.append(dict(name=faker.name(), affiliation=faker.company()))
478+
bluesheet = json.dumps(people)
479+
480+
# error cases
481+
r = self.client.post(url, {})
482+
self.assertContains(r, "Missing apikey parameter", status_code=400)
483+
484+
badrole = RoleFactory(group__type_id="ietf", name_id="ad")
485+
badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person)
486+
badrole.person.user.last_login = timezone.now()
487+
badrole.person.user.save()
488+
r = self.client.post(url, {"apikey": badapikey.hash()})
489+
self.assertContains(
490+
r, "Restricted to roles: Recording Manager, Secretariat", status_code=403
491+
)
492+
493+
r = self.client.post(url, {"apikey": apikey.hash()})
494+
self.assertContains(r, "Too long since last regular login", status_code=400)
495+
recman.user.last_login = timezone.now()
496+
recman.user.save()
497+
498+
r = self.client.get(url, {"apikey": apikey.hash()})
499+
self.assertContains(r, "Method not allowed", status_code=405)
500+
501+
r = self.client.post(url, {"apikey": apikey.hash()})
502+
self.assertContains(r, "Missing session_id parameter", status_code=400)
503+
504+
r = self.client.post(url, {"apikey": apikey.hash(), "session_id": session.pk})
505+
self.assertContains(r, "Missing bluesheet parameter", status_code=400)
506+
507+
r = self.client.post(
508+
url,
509+
{
510+
"apikey": apikey.hash(),
511+
"meeting": meeting.number,
512+
"group": group.acronym,
513+
"item": "1",
514+
"bluesheet": "foobar",
515+
},
516+
)
517+
self.assertContains(r, "Invalid json value: 'foobar'", status_code=400)
518+
519+
bad_session_pk = int(Session.objects.order_by("-pk").first().pk) + 1
520+
r = self.client.post(
521+
url,
522+
{
523+
"apikey": apikey.hash(),
524+
"session_id": bad_session_pk,
525+
"bluesheet": bluesheet,
526+
},
527+
)
528+
self.assertContains(r, "Session not found", status_code=400)
529+
530+
r = self.client.post(
531+
url,
532+
{
533+
"apikey": apikey.hash(),
534+
"session_id": "foo",
535+
"bluesheet": bluesheet,
536+
},
537+
)
538+
self.assertContains(r, "Invalid session_id", status_code=400)
539+
540+
r = self.client.post(
541+
url,
542+
{
543+
"apikey": apikey.hash(),
544+
"session_id": session.pk,
545+
"bluesheet": bluesheet,
546+
},
547+
)
548+
self.assertContains(r, "Done", status_code=200)
549+
550+
# Submit again, with slightly different content, as an updated version
551+
people[1]["affiliation"] = "Bolaget AB"
552+
bluesheet = json.dumps(people)
553+
r = self.client.post(
554+
url,
555+
{
556+
"apikey": apikey.hash(),
557+
"meeting": meeting.number,
558+
"group": group.acronym,
559+
"item": "1",
560+
"bluesheet": bluesheet,
561+
},
562+
)
563+
self.assertContains(r, "Done", status_code=200)
564+
565+
bluesheet = (
566+
session.sessionpresentation_set.filter(document__type__slug="bluesheets")
567+
.first()
568+
.document
569+
)
570+
# We've submitted an update; check that the rev is right
571+
self.assertEqual(bluesheet.rev, "01")
572+
# Check the content
573+
with open(bluesheet.get_file_name()) as file:
574+
text = file.read()
575+
for p in people:
576+
self.assertIn(p["name"], html.unescape(text))
577+
self.assertIn(p["affiliation"], html.unescape(text))
578+
384579
def test_person_export(self):
385580
person = PersonFactory()
386581
url = urlreverse('ietf.api.views.PersonalInformationExportView')

ietf/meeting/admin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ class SchedulingEventInline(admin.TabularInline):
9696
raw_id_fields = ["by"]
9797

9898
class SessionAdmin(admin.ModelAdmin):
99-
list_display = ["meeting", "name", "group_acronym", "purpose", "attendees", "requested", "current_status"]
99+
list_display = [
100+
"meeting", "name", "group_acronym", "purpose", "attendees", "has_onsite_tool", "requested", "current_status"
101+
]
100102
list_filter = ["purpose", "meeting", ]
101103
raw_id_fields = ["meeting", "group", "materials", "joint_with_groups", "tombstone_for"]
102104
search_fields = ["meeting__number", "name", "group__name", "group__acronym", "purpose__name"]

ietf/meeting/factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class Meta:
110110
group = factory.SubFactory(GroupFactory)
111111
requested_duration = datetime.timedelta(hours=1)
112112
on_agenda = factory.lazy_attribute(lambda obj: SessionPurposeName.objects.get(pk=obj.purpose_id).on_agenda)
113+
has_onsite_tool = factory.lazy_attribute(lambda obj: obj.purpose_id == 'regular')
113114

114115
@factory.post_generation
115116
def status_id(obj, create, extracted, **kwargs):

ietf/meeting/forms.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ def __init__(self, group, *args, **kwargs):
727727
'purpose',
728728
session_purposes[0] if len(session_purposes) > 0 else None,
729729
)
730+
kwargs['initial'].setdefault('has_onsite_tool', group.features.acts_like_wg)
730731
super().__init__(*args, **kwargs)
731732

732733
self.fields['type'].widget.attrs.update({
@@ -743,8 +744,8 @@ class Meta:
743744
model = Session
744745
fields = (
745746
'purpose', 'name', 'short', 'type', 'requested_duration',
746-
'on_agenda', 'agenda_note', 'remote_instructions', 'attendees',
747-
'comments',
747+
'on_agenda', 'agenda_note', 'has_onsite_tool', 'remote_instructions',
748+
'attendees', 'comments',
748749
)
749750
labels = {'requested_duration': 'Length'}
750751

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.28 on 2023-03-07 16:54
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('meeting', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='session',
15+
name='has_onsite_tool',
16+
field=models.BooleanField(default=False, help_text='Does this session use the officially supported onsite and remote tooling?'),
17+
),
18+
]

0 commit comments

Comments
 (0)