Skip to content

Commit 8881010

Browse files
authored
feat: explicit names for meetecho recordings (#8062)
* feat: explicit names for meetecho recordings * fix: better regex and kebab-styled-endpoint-name * fix: spaces around comparison operator
1 parent 9873439 commit 8881010

File tree

6 files changed

+143
-7
lines changed

6 files changed

+143
-7
lines changed

ietf/api/tests.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2015-2020, All Rights Reserved
1+
# Copyright The IETF Trust 2015-2024, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
import base64
44
import datetime
@@ -222,6 +222,70 @@ def test_api_set_session_video_url(self):
222222
event = doc.latest_event()
223223
self.assertEqual(event.by, recman)
224224

225+
def test_api_set_meetecho_recording_name(self):
226+
url = urlreverse("ietf.meeting.views.api_set_meetecho_recording_name")
227+
recmanrole = RoleFactory(group__type_id="ietf", name_id="recman")
228+
recman = recmanrole.person
229+
meeting = MeetingFactory(type_id="ietf")
230+
session = SessionFactory(group__type_id="wg", meeting=meeting)
231+
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
232+
name = "testname"
233+
234+
# error cases
235+
r = self.client.post(url, {})
236+
self.assertContains(r, "Missing apikey parameter", status_code=400)
237+
238+
badrole = RoleFactory(group__type_id="ietf", name_id="ad")
239+
badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person)
240+
badrole.person.user.last_login = timezone.now()
241+
badrole.person.user.save()
242+
r = self.client.post(url, {"apikey": badapikey.hash()})
243+
self.assertContains(r, "Restricted to role: Recording Manager", status_code=403)
244+
245+
r = self.client.post(url, {"apikey": apikey.hash()})
246+
self.assertContains(r, "Too long since last regular login", status_code=400)
247+
recman.user.last_login = timezone.now()
248+
recman.user.save()
249+
250+
r = self.client.get(url, {"apikey": apikey.hash()})
251+
self.assertContains(r, "Method not allowed", status_code=405)
252+
253+
r = self.client.post(url, {"apikey": apikey.hash()})
254+
self.assertContains(r, "Missing session_id parameter", status_code=400)
255+
256+
r = self.client.post(url, {"apikey": apikey.hash(), "session_id": session.pk})
257+
self.assertContains(r, "Missing name parameter", status_code=400)
258+
259+
bad_pk = int(Session.objects.order_by("-pk").first().pk) + 1
260+
r = self.client.post(
261+
url,
262+
{
263+
"apikey": apikey.hash(),
264+
"session_id": bad_pk,
265+
"name": name,
266+
},
267+
)
268+
self.assertContains(r, "Session not found", status_code=400)
269+
270+
r = self.client.post(
271+
url,
272+
{
273+
"apikey": apikey.hash(),
274+
"session_id": "foo",
275+
"name": name,
276+
},
277+
)
278+
self.assertContains(r, "Invalid session_id", status_code=400)
279+
280+
r = self.client.post(
281+
url, {"apikey": apikey.hash(), "session_id": session.pk, "name": name}
282+
)
283+
self.assertContains(r, "Done", status_code=200)
284+
285+
session.refresh_from_db()
286+
self.assertEqual(session.meetecho_recording_name, name)
287+
288+
225289
def test_api_add_session_attendees_deprecated(self):
226290
# Deprecated test - should be removed when we stop accepting a simple list of user PKs in
227291
# the add_session_attendees() view

ietf/api/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2017, All Rights Reserved
1+
# Copyright The IETF Trust 2017-2024, All Rights Reserved
22

33
from django.conf import settings
44
from django.urls import include
@@ -39,6 +39,8 @@
3939
url(r'^iesg/position', views_ballot.api_set_position),
4040
# Let Meetecho set session video URLs
4141
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
42+
# Let Meetecho tell us the name of its recordings
43+
url(r'^meeting/session/recording-name$', meeting_views.api_set_meetecho_recording_name),
4244
# Meeting agenda + floorplan data
4345
url(r'^meeting/(?P<num>[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data),
4446
# Meeting session materials
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright The IETF Trust 2024, All Rights Reserved
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("meeting", "0008_remove_schedtimesessassignment_notes"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="session",
15+
name="meetecho_recording_name",
16+
field=models.CharField(
17+
blank=True, help_text="Name of the meetecho recording", max_length=64
18+
),
19+
),
20+
]

ietf/meeting/models.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,7 @@ class Session(models.Model):
10421042
on_agenda = models.BooleanField(default=True, help_text='Is this session visible on the meeting agenda?')
10431043
has_onsite_tool = models.BooleanField(default=False, help_text="Does this session use the officially supported onsite and remote tooling?")
10441044
chat_room = models.CharField(blank=True, max_length=32, help_text='Name of Zulip stream, if different from group acronym')
1045+
meetecho_recording_name = models.CharField(blank=True, max_length=64, help_text="Name of the meetecho recording")
10451046

10461047
tombstone_for = models.ForeignKey('Session', blank=True, null=True, help_text="This session is the tombstone for a session that was rescheduled", on_delete=models.CASCADE)
10471048

@@ -1332,17 +1333,23 @@ def onsite_tool_url(self):
13321333
return None
13331334

13341335
def _session_recording_url_label(self):
1336+
otsa = self.official_timeslotassignment()
1337+
if otsa is None:
1338+
return None
13351339
if self.meeting.type.slug == "ietf" and self.has_onsite_tool:
1336-
session_label = f"IETF{self.meeting.number}-{self.group.acronym.upper()}-{self.official_timeslotassignment().timeslot.time.strftime('%Y%m%d-%H%M')}"
1340+
session_label = f"IETF{self.meeting.number}-{self.group.acronym.upper()}-{otsa.timeslot.time.strftime('%Y%m%d-%H%M')}"
13371341
else:
1338-
session_label = f"IETF-{self.group.acronym.upper()}-{self.official_timeslotassignment().timeslot.time.strftime('%Y%m%d-%H%M')}"
1342+
session_label = f"IETF-{self.group.acronym.upper()}-{otsa.timeslot.time.strftime('%Y%m%d-%H%M')}"
13391343
return session_label
13401344

13411345
def session_recording_url(self):
13421346
url_formatter = getattr(settings, "MEETECHO_SESSION_RECORDING_URL", "")
13431347
url = None
1344-
if url_formatter and self.video_stream_url:
1345-
url = url_formatter.format(session_label=self._session_recording_url_label())
1348+
name = self.meetecho_recording_name
1349+
if name is None or name.strip() == "":
1350+
name = self._session_recording_url_label()
1351+
if url_formatter.strip() != "" and name is not None:
1352+
url = url_formatter.format(session_label=name)
13461353
return url
13471354

13481355

ietf/meeting/tests_models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2021, All Rights Reserved
1+
# Copyright The IETF Trust 2021-2024, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
"""Tests of models in the Meeting application"""
44
import datetime
@@ -172,6 +172,10 @@ def test_session_recording_url(self, mock):
172172
settings.MEETECHO_SESSION_RECORDING_URL = "http://player.example.com?{session_label}"
173173
self.assertEqual(session.session_recording_url(), "http://player.example.com?LABEL")
174174

175+
session.meetecho_recording_name="actualname"
176+
session.save()
177+
self.assertEqual(session.session_recording_url(), "http://player.example.com?actualname")
178+
175179
def test_session_recording_url_label_ietf(self):
176180
session = SessionFactory(
177181
meeting__type_id='ietf',

ietf/meeting/views.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4270,6 +4270,45 @@ class OldUploadRedirect(RedirectView):
42704270
def get_redirect_url(self, **kwargs):
42714271
return reverse_lazy('ietf.meeting.views.session_details',kwargs=self.kwargs)
42724272

4273+
4274+
@require_api_key
4275+
@role_required("Recording Manager")
4276+
@csrf_exempt
4277+
def api_set_meetecho_recording_name(request):
4278+
"""Set name for meetecho recording
4279+
4280+
parameters:
4281+
apikey: the poster's personal API key
4282+
session_id: id of the session to update
4283+
name: the name to use for the recording at meetecho player
4284+
"""
4285+
def err(code, text):
4286+
return HttpResponse(text, status=code, content_type='text/plain')
4287+
4288+
if request.method != "POST":
4289+
return HttpResponseNotAllowed(
4290+
content="Method not allowed", content_type="text/plain", permitted_methods=('POST',)
4291+
)
4292+
4293+
session_id = request.POST.get('session_id', None)
4294+
if session_id is None:
4295+
return err(400, 'Missing session_id parameter')
4296+
name = request.POST.get('name', None)
4297+
if name is None:
4298+
return err(400, 'Missing name parameter')
4299+
4300+
try:
4301+
session = Session.objects.get(pk=session_id)
4302+
except Session.DoesNotExist:
4303+
return err(400, f"Session not found with session_id '{session_id}'")
4304+
except ValueError:
4305+
return err(400, "Invalid session_id: {session_id}")
4306+
4307+
session.meetecho_recording_name = name
4308+
session.save()
4309+
4310+
return HttpResponse("Done", status=200, content_type='text/plain')
4311+
42734312
@require_api_key
42744313
@role_required('Recording Manager')
42754314
@csrf_exempt

0 commit comments

Comments
 (0)