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:
parent
9873439cdc
commit
8881010051
|
@ -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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -222,6 +222,70 @@ class CustomApiTests(TestCase):
|
||||||
event = doc.latest_event()
|
event = doc.latest_event()
|
||||||
self.assertEqual(event.by, recman)
|
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):
|
def test_api_add_session_attendees_deprecated(self):
|
||||||
# Deprecated test - should be removed when we stop accepting a simple list of user PKs in
|
# Deprecated test - should be removed when we stop accepting a simple list of user PKs in
|
||||||
# the add_session_attendees() view
|
# the add_session_attendees() view
|
||||||
|
|
|
@ -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.conf import settings
|
||||||
from django.urls import include
|
from django.urls import include
|
||||||
|
@ -39,6 +39,8 @@ urlpatterns = [
|
||||||
url(r'^iesg/position', views_ballot.api_set_position),
|
url(r'^iesg/position', views_ballot.api_set_position),
|
||||||
# Let Meetecho set session video URLs
|
# Let Meetecho set session video URLs
|
||||||
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
|
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
|
# Meeting agenda + floorplan data
|
||||||
url(r'^meeting/(?P<num>[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data),
|
url(r'^meeting/(?P<num>[A-Za-z0-9._+-]+)/agenda-data$', meeting_views.api_get_agenda_data),
|
||||||
# Meeting session materials
|
# Meeting session materials
|
||||||
|
|
|
@ -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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1042,6 +1042,7 @@ class Session(models.Model):
|
||||||
on_agenda = models.BooleanField(default=True, help_text='Is this session visible on the meeting agenda?')
|
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?")
|
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')
|
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)
|
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
|
return None
|
||||||
|
|
||||||
def _session_recording_url_label(self):
|
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:
|
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:
|
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
|
return session_label
|
||||||
|
|
||||||
def session_recording_url(self):
|
def session_recording_url(self):
|
||||||
url_formatter = getattr(settings, "MEETECHO_SESSION_RECORDING_URL", "")
|
url_formatter = getattr(settings, "MEETECHO_SESSION_RECORDING_URL", "")
|
||||||
url = None
|
url = None
|
||||||
if url_formatter and self.video_stream_url:
|
name = self.meetecho_recording_name
|
||||||
url = url_formatter.format(session_label=self._session_recording_url_label())
|
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
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2021, All Rights Reserved
|
# Copyright The IETF Trust 2021-2024, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Tests of models in the Meeting application"""
|
"""Tests of models in the Meeting application"""
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -172,6 +172,10 @@ class SessionTests(TestCase):
|
||||||
settings.MEETECHO_SESSION_RECORDING_URL = "http://player.example.com?{session_label}"
|
settings.MEETECHO_SESSION_RECORDING_URL = "http://player.example.com?{session_label}"
|
||||||
self.assertEqual(session.session_recording_url(), "http://player.example.com?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):
|
def test_session_recording_url_label_ietf(self):
|
||||||
session = SessionFactory(
|
session = SessionFactory(
|
||||||
meeting__type_id='ietf',
|
meeting__type_id='ietf',
|
||||||
|
|
|
@ -4270,6 +4270,45 @@ class OldUploadRedirect(RedirectView):
|
||||||
def get_redirect_url(self, **kwargs):
|
def get_redirect_url(self, **kwargs):
|
||||||
return reverse_lazy('ietf.meeting.views.session_details',kwargs=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
|
@require_api_key
|
||||||
@role_required('Recording Manager')
|
@role_required('Recording Manager')
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
|
Loading…
Reference in a new issue