Added an API endpoint to let Meetech associate recording urls with sessions.

- Legacy-Id: 14967
This commit is contained in:
Henrik Levkowetz 2018-03-29 11:04:47 +00:00
parent 0ef66ae9e2
commit e8f999dc07
6 changed files with 169 additions and 9 deletions

3
PLAN
View file

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

View file

@ -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 = {}

View file

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

View file

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

View file

@ -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():

View file

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