Added an API endpoint to let Meetech associate recording urls with sessions.
- Legacy-Id: 14967
This commit is contained in:
parent
0ef66ae9e2
commit
e8f999dc07
3
PLAN
3
PLAN
|
@ -9,9 +9,6 @@ Planned work in rough order
|
|||
|
||||
* Revisit the review tool, work through the accumulated tickets.
|
||||
|
||||
* Introduce an API for Meetecho to use to associate recordings with sessions
|
||||
(and perhaps automate making copies of those videos)
|
||||
|
||||
* GroupFeatures cleanup. Move most to fields on GroupTypeName, and fix places
|
||||
that still uses lists of group types to determine actions by instead
|
||||
defining group type fields to hold the selector. (Setting up a new
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
# Copyright The IETF Trust 2015-2018, All Rights Reserved
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
from importlib import import_module
|
||||
from mock import patch
|
||||
|
||||
from django.apps import apps
|
||||
from django.test import Client
|
||||
from django.conf import settings
|
||||
from django.test import Client
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.utils import timezone
|
||||
|
||||
from tastypie.test import ResourceTestCaseMixin
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.person.models import PersonalApiKey
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
||||
OMITTED_APPS = (
|
||||
'ietf.secr.meetings',
|
||||
|
@ -44,6 +51,59 @@ class CustomApiTestCase(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertContains(r, 'The datatracker API', status_code=200)
|
||||
|
||||
def test_api_set_session_video_url(self):
|
||||
url = urlreverse('ietf.meeting.views.api_set_session_video_url')
|
||||
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)
|
||||
group = session.group
|
||||
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
|
||||
|
||||
# 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 meeting parameter", status_code=400)
|
||||
|
||||
|
||||
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, } )
|
||||
self.assertContains(r, "Missing group parameter", status_code=400)
|
||||
|
||||
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym} )
|
||||
self.assertContains(r, "Missing item parameter", status_code=400)
|
||||
|
||||
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym, 'item': '1'} )
|
||||
self.assertContains(r, "Missing url parameter", status_code=400)
|
||||
|
||||
video = 'https://foo.example.com/bar/beer/'
|
||||
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
|
||||
'item': '1', 'url': video, })
|
||||
self.assertContains(r, "Done", status_code=200)
|
||||
recordings = session.recordings()
|
||||
self.assertEqual(len(recordings), 1)
|
||||
doc = recordings[0]
|
||||
self.assertEqual(doc.external_url, video)
|
||||
event = doc.latest_event()
|
||||
self.assertEqual(event.by, recman)
|
||||
|
||||
|
||||
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.apps = {}
|
||||
|
|
|
@ -17,6 +17,7 @@ urlpatterns = [
|
|||
url(r'^v1/?$', api_views.top_level),
|
||||
# Custom API endpoints
|
||||
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
||||
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
|
||||
url(r'^submit/?$', submit_views.api_submit),
|
||||
url(r'^iesg/position', views_ballot.api_set_position),
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
# Copyright The IETF Trust 2007-2018, All Rights Reserved
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
|
@ -23,6 +23,8 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidde
|
|||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import URLValidator
|
||||
from django.urls import reverse,reverse_lazy
|
||||
from django.db.models import Min, Max, Q
|
||||
from django.forms.models import modelform_factory, inlineformset_factory
|
||||
|
@ -59,7 +61,8 @@ from ietf.meeting.helpers import send_interim_announcement_request
|
|||
from ietf.meeting.utils import finalize
|
||||
from ietf.secr.proceedings.utils import handle_upload_file
|
||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
|
||||
import_youtube_video_urls)
|
||||
import_youtube_video_urls, create_recording)
|
||||
from ietf.utils.decorators import require_api_key
|
||||
from ietf.utils.mail import send_mail_message
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.pdf import pdf_pages
|
||||
|
@ -2178,6 +2181,62 @@ def api_import_recordings(request, number):
|
|||
else:
|
||||
return HttpResponse(status=405)
|
||||
|
||||
@require_api_key
|
||||
@role_required('Recording Manager')
|
||||
@csrf_exempt
|
||||
def api_set_session_video_url(request):
|
||||
def err(code, text):
|
||||
return HttpResponse(text, status=code, content_type='text/plain')
|
||||
if request.method == 'POST':
|
||||
# parameters:
|
||||
# apikey: the poster's personal API key
|
||||
# meeting: '101', or 'interim-2018-quic-02'
|
||||
# group: 'quic' or 'plenary'
|
||||
# item: '1', '2', '3' (the group's first, second, third etc.
|
||||
# session during the week)
|
||||
# url: The recording url (on YouTube, or whatever)
|
||||
user = request.user.person
|
||||
for item in ['meeting', 'group', 'item', 'url',]:
|
||||
value = request.POST.get(item)
|
||||
if not value:
|
||||
return err(400, "Missing %s parameter" % item)
|
||||
number = request.POST.get('meeting')
|
||||
sessions = Session.objects.filter(meeting__number=number)
|
||||
if not sessions.exists():
|
||||
return err(404, "No sessions found for meeting '%s'" % (number, ))
|
||||
acronym = request.POST.get('group')
|
||||
sessions = sessions.filter(group__acronym=acronym)
|
||||
if not sessions.exists():
|
||||
return err(404, "No sessions found in meeting '%s' for group '%s'" % (number, acronym))
|
||||
session_times = [ (s.official_timeslotassignment().timeslot.time, s) for s in sessions ]
|
||||
session_times.sort()
|
||||
item = request.POST.get('item')
|
||||
if not item.isdigit():
|
||||
return err(400, "Expected a numeric value for 'item', found '%s'" % (item, ))
|
||||
n = int(item)-1 # change 1-based to 0-based
|
||||
try:
|
||||
time, session = session_times[n]
|
||||
except IndexError:
|
||||
return err(400, "No item '%s' found in list of sessions for group" % (item, ))
|
||||
url = request.POST.get('url')
|
||||
try:
|
||||
URLValidator()(url)
|
||||
except ValidationError:
|
||||
return err(400, "Invalid url value: '%s'" % (url, ))
|
||||
recordings = [ (r.name, r.title, r) for r in session.recordings() if 'video' in r.title.lower() ]
|
||||
if recordings:
|
||||
r = recordings[-1][-1]
|
||||
r.external_url = url
|
||||
else:
|
||||
time = session.official_timeslotassignment().timeslot.time
|
||||
title = 'Video recording for %s on %s at %s' % (acronym, time.date(), time.time())
|
||||
create_recording(session, url, title=title, user=user)
|
||||
else:
|
||||
return err(405, "Method not allowed")
|
||||
|
||||
return HttpResponse("Done", status=200, content_type='text/plain')
|
||||
|
||||
|
||||
def important_dates(request, num=None):
|
||||
assert num is None or num.isdigit()
|
||||
preview_roles = ['Area Director', 'Secretariat', 'IETF Chair', 'IAD', ]
|
||||
|
|
|
@ -290,6 +290,7 @@ def salt():
|
|||
# Manual maintenance: List all endpoints that use @require_api_key here
|
||||
PERSON_API_KEY_ENDPOINTS = [
|
||||
("/api/iesg/position", "/api/iesg/position"),
|
||||
("/api/meeting/session/video/url", "/api/meeting/session/video/url"),
|
||||
]
|
||||
|
||||
class PersonalApiKey(models.Model):
|
||||
|
@ -304,7 +305,10 @@ class PersonalApiKey(models.Model):
|
|||
@classmethod
|
||||
def validate_key(cls, s):
|
||||
import struct, hashlib, base64
|
||||
key = base64.urlsafe_b64decode(six.binary_type(s))
|
||||
try:
|
||||
key = base64.urlsafe_b64decode(six.binary_type(s))
|
||||
except TypeError:
|
||||
return None
|
||||
id, salt, hash = struct.unpack(KEY_STRUCT, key)
|
||||
k = cls.objects.filter(id=id)
|
||||
if not k.exists():
|
||||
|
|
|
@ -226,6 +226,45 @@
|
|||
Done
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<h3 id="session-video-url-api">Set session video URL</h3>
|
||||
|
||||
<p>
|
||||
|
||||
This interface is intended for Meetecho, to provide a way to set the
|
||||
URL of a video recording for a given session. It is available at
|
||||
<code>{% url 'ietf.meeting.views.api_set_session_video_url' %}</code>.
|
||||
Access is limited to recording managers.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
The interface requires the use of a personal API key, which can be created at
|
||||
<a href="{% url 'ietf.ietfauth.views.apikey_index' %}">{% url 'ietf.ietfauth.views.apikey_index' %}</a>
|
||||
</p>
|
||||
<p>
|
||||
The ballot position API takes the following parameters:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>apikey</code> (required) which is the personal API key hash</li>
|
||||
<li><code>meeting</code> (required) which is the meeting number</li>
|
||||
<li><code>group</code> (required) which is the group acronym</li>
|
||||
<li><code>item</code> (required) which is the chronological sequence number of the session (1 for a group's first session, 2 for the second, etc.)</li>
|
||||
<li><code>url</code> (required) which is the url that points to the video recording</li>
|
||||
</ul>
|
||||
<p>
|
||||
It returns an appropriate http result code, and a brief explanatory text message.
|
||||
</p>
|
||||
<p>
|
||||
Here is an example:</li>
|
||||
</p>
|
||||
<pre>
|
||||
$ curl -S -F "apikey=DgAAAMLSi3coaE5TjrRs518xO8eBRlCmFF3eQcC8_SjUTtRGLGiJh7-1SYPT5WiS" -F "meeting=101" -F "group=mptcp" -F "item=1" -F "url=https://foo.example/beer/mptcp" https://datatracker.ietf.org/api/meeting/session/video/url
|
||||
Done
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue