diff --git a/ietf/api/tests.py b/ietf/api/tests.py index 4f2a7f7d3..97e928dc5 100644 --- a/ietf/api/tests.py +++ b/ietf/api/tests.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2015-2020, All Rights Reserved +# Copyright The IETF Trust 2015-2024, All Rights Reserved # -*- coding: utf-8 -*- import base64 import datetime @@ -222,6 +222,70 @@ class CustomApiTests(TestCase): event = doc.latest_event() self.assertEqual(event.by, recman) + def test_api_set_meetecho_recording_name(self): + url = urlreverse("ietf.meeting.views.api_set_meetecho_recording_name") + recmanrole = RoleFactory(group__type_id="ietf", name_id="recman") + recman = recmanrole.person + meeting = MeetingFactory(type_id="ietf") + session = SessionFactory(group__type_id="wg", meeting=meeting) + apikey = PersonalApiKey.objects.create(endpoint=url, person=recman) + name = "testname" + + # error cases + r = self.client.post(url, {}) + self.assertContains(r, "Missing apikey parameter", status_code=400) + + badrole = RoleFactory(group__type_id="ietf", name_id="ad") + badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person) + badrole.person.user.last_login = timezone.now() + badrole.person.user.save() + r = self.client.post(url, {"apikey": badapikey.hash()}) + self.assertContains(r, "Restricted to role: Recording Manager", status_code=403) + + r = self.client.post(url, {"apikey": apikey.hash()}) + self.assertContains(r, "Too long since last regular login", status_code=400) + recman.user.last_login = timezone.now() + recman.user.save() + + r = self.client.get(url, {"apikey": apikey.hash()}) + self.assertContains(r, "Method not allowed", status_code=405) + + r = self.client.post(url, {"apikey": apikey.hash()}) + self.assertContains(r, "Missing session_id parameter", status_code=400) + + r = self.client.post(url, {"apikey": apikey.hash(), "session_id": session.pk}) + self.assertContains(r, "Missing name parameter", status_code=400) + + bad_pk = int(Session.objects.order_by("-pk").first().pk) + 1 + r = self.client.post( + url, + { + "apikey": apikey.hash(), + "session_id": bad_pk, + "name": name, + }, + ) + self.assertContains(r, "Session not found", status_code=400) + + r = self.client.post( + url, + { + "apikey": apikey.hash(), + "session_id": "foo", + "name": name, + }, + ) + self.assertContains(r, "Invalid session_id", status_code=400) + + r = self.client.post( + url, {"apikey": apikey.hash(), "session_id": session.pk, "name": name} + ) + self.assertContains(r, "Done", status_code=200) + + session.refresh_from_db() + self.assertEqual(session.meetecho_recording_name, name) + + def test_api_add_session_attendees_deprecated(self): # Deprecated test - should be removed when we stop accepting a simple list of user PKs in # the add_session_attendees() view diff --git a/ietf/api/urls.py b/ietf/api/urls.py index 48525dfda..431ad5c5d 100644 --- a/ietf/api/urls.py +++ b/ietf/api/urls.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2017, All Rights Reserved +# Copyright The IETF Trust 2017-2024, All Rights Reserved from django.conf import settings from django.urls import include @@ -39,6 +39,8 @@ urlpatterns = [ url(r'^iesg/position', views_ballot.api_set_position), # Let Meetecho set session video URLs url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url), + # Let Meetecho tell us the name of its recordings + url(r'^meeting/session/recording-name$', meeting_views.api_set_meetecho_recording_name), # Meeting agenda + floorplan data url(r'^meeting/(?P[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data), # Meeting session materials diff --git a/ietf/meeting/migrations/0009_session_meetecho_recording_name.py b/ietf/meeting/migrations/0009_session_meetecho_recording_name.py new file mode 100644 index 000000000..79ca4919a --- /dev/null +++ b/ietf/meeting/migrations/0009_session_meetecho_recording_name.py @@ -0,0 +1,20 @@ +# Copyright The IETF Trust 2024, All Rights Reserved + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("meeting", "0008_remove_schedtimesessassignment_notes"), + ] + + operations = [ + migrations.AddField( + model_name="session", + name="meetecho_recording_name", + field=models.CharField( + blank=True, help_text="Name of the meetecho recording", max_length=64 + ), + ), + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index e16907780..8c6fb9741 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -1042,6 +1042,7 @@ class Session(models.Model): on_agenda = models.BooleanField(default=True, help_text='Is this session visible on the meeting agenda?') has_onsite_tool = models.BooleanField(default=False, help_text="Does this session use the officially supported onsite and remote tooling?") chat_room = models.CharField(blank=True, max_length=32, help_text='Name of Zulip stream, if different from group acronym') + meetecho_recording_name = models.CharField(blank=True, max_length=64, help_text="Name of the meetecho recording") 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) @@ -1332,17 +1333,23 @@ class Session(models.Model): return None def _session_recording_url_label(self): + otsa = self.official_timeslotassignment() + if otsa is None: + return None if self.meeting.type.slug == "ietf" and self.has_onsite_tool: - session_label = f"IETF{self.meeting.number}-{self.group.acronym.upper()}-{self.official_timeslotassignment().timeslot.time.strftime('%Y%m%d-%H%M')}" + session_label = f"IETF{self.meeting.number}-{self.group.acronym.upper()}-{otsa.timeslot.time.strftime('%Y%m%d-%H%M')}" else: - session_label = f"IETF-{self.group.acronym.upper()}-{self.official_timeslotassignment().timeslot.time.strftime('%Y%m%d-%H%M')}" + session_label = f"IETF-{self.group.acronym.upper()}-{otsa.timeslot.time.strftime('%Y%m%d-%H%M')}" return session_label def session_recording_url(self): url_formatter = getattr(settings, "MEETECHO_SESSION_RECORDING_URL", "") url = None - if url_formatter and self.video_stream_url: - url = url_formatter.format(session_label=self._session_recording_url_label()) + name = self.meetecho_recording_name + if name is None or name.strip() == "": + name = self._session_recording_url_label() + if url_formatter.strip() != "" and name is not None: + url = url_formatter.format(session_label=name) return url diff --git a/ietf/meeting/tests_models.py b/ietf/meeting/tests_models.py index 8457423c5..03b706e1d 100644 --- a/ietf/meeting/tests_models.py +++ b/ietf/meeting/tests_models.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2021, All Rights Reserved +# Copyright The IETF Trust 2021-2024, All Rights Reserved # -*- coding: utf-8 -*- """Tests of models in the Meeting application""" import datetime @@ -172,6 +172,10 @@ class SessionTests(TestCase): settings.MEETECHO_SESSION_RECORDING_URL = "http://player.example.com?{session_label}" self.assertEqual(session.session_recording_url(), "http://player.example.com?LABEL") + session.meetecho_recording_name="actualname" + session.save() + self.assertEqual(session.session_recording_url(), "http://player.example.com?actualname") + def test_session_recording_url_label_ietf(self): session = SessionFactory( meeting__type_id='ietf', diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 6e6c4dfe2..bce6ccd1b 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -4270,6 +4270,45 @@ class OldUploadRedirect(RedirectView): def get_redirect_url(self, **kwargs): return reverse_lazy('ietf.meeting.views.session_details',kwargs=self.kwargs) + +@require_api_key +@role_required("Recording Manager") +@csrf_exempt +def api_set_meetecho_recording_name(request): + """Set name for meetecho recording + + parameters: + apikey: the poster's personal API key + session_id: id of the session to update + name: the name to use for the recording at meetecho player + """ + def err(code, text): + return HttpResponse(text, status=code, content_type='text/plain') + + if request.method != "POST": + return HttpResponseNotAllowed( + content="Method not allowed", content_type="text/plain", permitted_methods=('POST',) + ) + + session_id = request.POST.get('session_id', None) + if session_id is None: + return err(400, 'Missing session_id parameter') + name = request.POST.get('name', None) + if name is None: + return err(400, 'Missing name parameter') + + try: + session = Session.objects.get(pk=session_id) + except Session.DoesNotExist: + return err(400, f"Session not found with session_id '{session_id}'") + except ValueError: + return err(400, "Invalid session_id: {session_id}") + + session.meetecho_recording_name = name + session.save() + + return HttpResponse("Done", status=200, content_type='text/plain') + @require_api_key @role_required('Recording Manager') @csrf_exempt