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
This commit is contained in:
Robert Sparks 2024-10-21 16:45:36 -05:00 committed by GitHub
parent 9873439cdc
commit 8881010051
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 143 additions and 7 deletions

View file

@ -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

View file

@ -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<num>[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data),
# Meeting session materials

View file

@ -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
),
),
]

View file

@ -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

View file

@ -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',

View file

@ -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