Skip to content

Commit a1c79a0

Browse files
committed
Merged in ^/personal/henrik/6.76.1-meetecho-api/ which provides new API endpoint to let video urls be associated with sessions. Also added some more checks to the test_api_set_session_video_url() test.
- Legacy-Id: 14969
2 parents 8187339 + 2b5315f commit a1c79a0

10 files changed

Lines changed: 231 additions & 21 deletions

File tree

PLAN

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ Planned work in rough order
99

1010
* Revisit the review tool, work through the accumulated tickets.
1111

12-
* Introduce an API for Meetecho to use to associate recordings with sessions
13-
(and perhaps automate making copies of those videos)
14-
1512
* GroupFeatures cleanup. Move most to fields on GroupTypeName, and fix places
1613
that still uses lists of group types to determine actions by instead
1714
defining group type fields to hold the selector. (Setting up a new
18-
group type for things like PechaCucha and Hot RFC Lightning Tals probably
15+
group type for things like PechaCucha and Hot RFC Lightning Talks probably
1916
could have been done with only table edits if there hadn't been so much code
2017
using type_id lists and features needing code changes).
2118

ietf/api/tests.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
# Copyright The IETF Trust 2015-2018, All Rights Reserved
2+
3+
import json
14
import os
25
import sys
3-
import json
6+
47
from importlib import import_module
58
from mock import patch
69

710
from django.apps import apps
8-
from django.test import Client
911
from django.conf import settings
12+
from django.test import Client
1013
from django.urls import reverse as urlreverse
14+
from django.utils import timezone
1115

1216
from tastypie.test import ResourceTestCaseMixin
1317

1418
import debug # pyflakes:ignore
1519

16-
from ietf.utils.test_utils import TestCase
20+
from ietf.group.factories import RoleFactory
21+
from ietf.meeting.factories import MeetingFactory, SessionFactory
1722
from ietf.meeting.test_data import make_meeting_test_data
23+
from ietf.person.models import PersonalApiKey
24+
from ietf.utils.test_utils import TestCase
1825

1926
OMITTED_APPS = (
2027
'ietf.secr.meetings',
@@ -44,6 +51,75 @@ def test_api_help_page(self):
4451
r = self.client.get(url)
4552
self.assertContains(r, 'The datatracker API', status_code=200)
4653

54+
def test_api_set_session_video_url(self):
55+
url = urlreverse('ietf.meeting.views.api_set_session_video_url')
56+
recmanrole = RoleFactory(group__type_id='ietf', name_id='recman')
57+
recman = recmanrole.person
58+
meeting = MeetingFactory(type_id='ietf')
59+
session = SessionFactory(group__type_id='wg', meeting=meeting)
60+
group = session.group
61+
apikey = PersonalApiKey.objects.create(endpoint=url, person=recman)
62+
video = 'https://foo.example.com/bar/beer/'
63+
64+
# error cases
65+
r = self.client.post(url, {})
66+
self.assertContains(r, "Missing apikey parameter", status_code=400)
67+
68+
badrole = RoleFactory(group__type_id='ietf', name_id='ad')
69+
badapikey = PersonalApiKey.objects.create(endpoint=url, person=badrole.person)
70+
badrole.person.user.last_login = timezone.now()
71+
badrole.person.user.save()
72+
r = self.client.post(url, {'apikey': badapikey.hash()} )
73+
self.assertContains(r, "Restricted to role Recording Manager", status_code=403)
74+
75+
r = self.client.post(url, {'apikey': apikey.hash()} )
76+
self.assertContains(r, "Too long since last regular login", status_code=400)
77+
recman.user.last_login = timezone.now()
78+
recman.user.save()
79+
80+
r = self.client.get(url, {'apikey': apikey.hash()} )
81+
self.assertContains(r, "Method not allowed", status_code=405)
82+
83+
r = self.client.post(url, {'apikey': apikey.hash()} )
84+
self.assertContains(r, "Missing meeting parameter", status_code=400)
85+
86+
87+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, } )
88+
self.assertContains(r, "Missing group parameter", status_code=400)
89+
90+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym} )
91+
self.assertContains(r, "Missing item parameter", status_code=400)
92+
93+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym, 'item': '1'} )
94+
self.assertContains(r, "Missing url parameter", status_code=400)
95+
96+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': '1', 'group': group.acronym,
97+
'item': '1', 'url': video, })
98+
self.assertContains(r, "No sessions found for meeting", status_code=404)
99+
100+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': 'bogous',
101+
'item': '1', 'url': video, })
102+
self.assertContains(r, "No sessions found in meeting '%s' for group 'bogous'"%meeting.number, status_code=404)
103+
104+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
105+
'item': '1', 'url': "foobar", })
106+
self.assertContains(r, "Invalid url value: 'foobar'", status_code=400)
107+
108+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
109+
'item': '5', 'url': video, })
110+
self.assertContains(r, "No item '5' found in list of sessions for group", status_code=400)
111+
112+
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
113+
'item': '1', 'url': video, })
114+
self.assertContains(r, "Done", status_code=200)
115+
recordings = session.recordings()
116+
self.assertEqual(len(recordings), 1)
117+
doc = recordings[0]
118+
self.assertEqual(doc.external_url, video)
119+
event = doc.latest_event()
120+
self.assertEqual(event.by, recman)
121+
122+
47123
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
48124
def __init__(self, *args, **kwargs):
49125
self.apps = {}

ietf/api/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
url(r'^v1/?$', api_views.top_level),
1818
# Custom API endpoints
1919
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
20+
url(r'^meeting/session/video/url$', meeting_views.api_set_session_video_url),
2021
url(r'^submit/?$', submit_views.api_submit),
2122
url(r'^iesg/position', views_ballot.api_set_position),
2223
]

ietf/ietfauth/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def has_role(user, role_names, *args, **kwargs):
7171
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
7272
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
7373
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
74+
"Recording Manager": Q(person=person,name="recman",group__type="ietf",group__state="active", ),
7475
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
7576
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
7677

ietf/meeting/views.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2007, All Rights Reserved
1+
# Copyright The IETF Trust 2007-2018, All Rights Reserved
22

33
import csv
44
import datetime
@@ -23,6 +23,8 @@
2323
from django.conf import settings
2424
from django.contrib import messages
2525
from django.contrib.auth.decorators import login_required
26+
from django.core.exceptions import ValidationError
27+
from django.core.validators import URLValidator
2628
from django.urls import reverse,reverse_lazy
2729
from django.db.models import Min, Max, Q
2830
from django.forms.models import modelform_factory, inlineformset_factory
@@ -59,7 +61,8 @@
5961
from ietf.meeting.utils import finalize
6062
from ietf.secr.proceedings.utils import handle_upload_file
6163
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
62-
import_youtube_video_urls)
64+
import_youtube_video_urls, create_recording)
65+
from ietf.utils.decorators import require_api_key
6366
from ietf.utils.mail import send_mail_message
6467
from ietf.utils.pipe import pipe
6568
from ietf.utils.pdf import pdf_pages
@@ -2180,6 +2183,62 @@ def api_import_recordings(request, number):
21802183
else:
21812184
return HttpResponse(status=405)
21822185

2186+
@require_api_key
2187+
@role_required('Recording Manager')
2188+
@csrf_exempt
2189+
def api_set_session_video_url(request):
2190+
def err(code, text):
2191+
return HttpResponse(text, status=code, content_type='text/plain')
2192+
if request.method == 'POST':
2193+
# parameters:
2194+
# apikey: the poster's personal API key
2195+
# meeting: '101', or 'interim-2018-quic-02'
2196+
# group: 'quic' or 'plenary'
2197+
# item: '1', '2', '3' (the group's first, second, third etc.
2198+
# session during the week)
2199+
# url: The recording url (on YouTube, or whatever)
2200+
user = request.user.person
2201+
for item in ['meeting', 'group', 'item', 'url',]:
2202+
value = request.POST.get(item)
2203+
if not value:
2204+
return err(400, "Missing %s parameter" % item)
2205+
number = request.POST.get('meeting')
2206+
sessions = Session.objects.filter(meeting__number=number)
2207+
if not sessions.exists():
2208+
return err(404, "No sessions found for meeting '%s'" % (number, ))
2209+
acronym = request.POST.get('group')
2210+
sessions = sessions.filter(group__acronym=acronym)
2211+
if not sessions.exists():
2212+
return err(404, "No sessions found in meeting '%s' for group '%s'" % (number, acronym))
2213+
session_times = [ (s.official_timeslotassignment().timeslot.time, s) for s in sessions ]
2214+
session_times.sort()
2215+
item = request.POST.get('item')
2216+
if not item.isdigit():
2217+
return err(400, "Expected a numeric value for 'item', found '%s'" % (item, ))
2218+
n = int(item)-1 # change 1-based to 0-based
2219+
try:
2220+
time, session = session_times[n]
2221+
except IndexError:
2222+
return err(400, "No item '%s' found in list of sessions for group" % (item, ))
2223+
url = request.POST.get('url')
2224+
try:
2225+
URLValidator()(url)
2226+
except ValidationError:
2227+
return err(400, "Invalid url value: '%s'" % (url, ))
2228+
recordings = [ (r.name, r.title, r) for r in session.recordings() if 'video' in r.title.lower() ]
2229+
if recordings:
2230+
r = recordings[-1][-1]
2231+
r.external_url = url
2232+
else:
2233+
time = session.official_timeslotassignment().timeslot.time
2234+
title = 'Video recording for %s on %s at %s' % (acronym, time.date(), time.time())
2235+
create_recording(session, url, title=title, user=user)
2236+
else:
2237+
return err(405, "Method not allowed")
2238+
2239+
return HttpResponse("Done", status=200, content_type='text/plain')
2240+
2241+
21832242
def important_dates(request, num=None):
21842243
assert num is None or num.isdigit()
21852244
preview_roles = ['Area Director', 'Secretariat', 'IETF Chair', 'IAD', ]

ietf/name/fixtures/names.json

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9538,6 +9538,16 @@
95389538
"model": "name.rolename",
95399539
"pk": "pre-ad"
95409540
},
9541+
{
9542+
"fields": {
9543+
"desc": "",
9544+
"name": "Recording Manager",
9545+
"order": 13,
9546+
"used": true
9547+
},
9548+
"model": "name.rolename",
9549+
"pk": "recman"
9550+
},
95419551
{
95429552
"fields": {
95439553
"desc": "",
@@ -9578,6 +9588,16 @@
95789588
"model": "name.rolename",
95799589
"pk": "trac-admin"
95809590
},
9591+
{
9592+
"fields": {
9593+
"desc": "Provides log-in permission to restricted Trac instances",
9594+
"name": "Trac Editor",
9595+
"order": 0,
9596+
"used": true
9597+
},
9598+
"model": "name.rolename",
9599+
"pk": "trac-editor"
9600+
},
95819601
{
95829602
"fields": {
95839603
"desc": "Audio streaming support",
@@ -10002,7 +10022,7 @@
1000210022
"fields": {
1000310023
"command": "xym",
1000410024
"switch": "--version",
10005-
"time": "2017-12-31T00:07:14.314",
10025+
"time": "2018-03-23T00:08:43.130",
1000610026
"used": true,
1000710027
"version": "xym 0.4"
1000810028
},
@@ -10013,9 +10033,9 @@
1001310033
"fields": {
1001410034
"command": "pyang",
1001510035
"switch": "--version",
10016-
"time": "2017-12-31T00:07:15.241",
10036+
"time": "2018-03-23T00:08:44.177",
1001710037
"used": true,
10018-
"version": "pyang 1.7.3"
10038+
"version": "pyang 1.7.4"
1001910039
},
1002010040
"model": "utils.versioninfo",
1002110041
"pk": 2
@@ -10024,11 +10044,22 @@
1002410044
"fields": {
1002510045
"command": "yanglint",
1002610046
"switch": "--version",
10027-
"time": "2017-12-31T00:07:15.325",
10047+
"time": "2018-03-23T00:08:44.295",
1002810048
"used": true,
10029-
"version": "yanglint 0.14.53"
10049+
"version": "yanglint 0.14.73"
1003010050
},
1003110051
"model": "utils.versioninfo",
1003210052
"pk": 3
10053+
},
10054+
{
10055+
"fields": {
10056+
"command": "xml2rfc",
10057+
"switch": "--version",
10058+
"time": "2018-03-23T00:08:45.862",
10059+
"used": true,
10060+
"version": "xml2rfc 2.9.6"
10061+
},
10062+
"model": "utils.versioninfo",
10063+
"pk": 4
1003310064
}
1003410065
]

ietf/person/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ def salt():
290290
# Manual maintenance: List all endpoints that use @require_api_key here
291291
PERSON_API_KEY_ENDPOINTS = [
292292
("/api/iesg/position", "/api/iesg/position"),
293+
("/api/meeting/session/video/url", "/api/meeting/session/video/url"),
293294
]
294295

295296
class PersonalApiKey(models.Model):
@@ -304,7 +305,10 @@ class PersonalApiKey(models.Model):
304305
@classmethod
305306
def validate_key(cls, s):
306307
import struct, hashlib, base64
307-
key = base64.urlsafe_b64decode(six.binary_type(s))
308+
try:
309+
key = base64.urlsafe_b64decode(six.binary_type(s))
310+
except TypeError:
311+
return None
308312
id, salt, hash = struct.unpack(KEY_STRUCT, key)
309313
k = cls.objects.filter(id=id)
310314
if not k.exists():

ietf/secr/proceedings/proc_utils.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,19 @@ def get_or_create_recording_document(url,session):
177177
except ObjectDoesNotExist:
178178
return create_recording(session,url)
179179

180-
def create_recording(session,url):
180+
def create_recording(session, url, title=None, user=None):
181181
'''
182182
Creates the Document type=recording, setting external_url and creating
183183
NewRevisionDocEvent
184184
'''
185185
sequence = get_next_sequence(session.group,session.meeting,'recording')
186186
name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence)
187187
time = session.official_timeslotassignment().timeslot.time.strftime('%Y-%m-%d %H:%M')
188-
if url.endswith('mp3'):
189-
title = 'Audio recording for {}'.format(time)
190-
else:
191-
title = 'Video recording for {}'.format(time)
188+
if not title:
189+
if url.endswith('mp3'):
190+
title = 'Audio recording for {}'.format(time)
191+
else:
192+
title = 'Video recording for {}'.format(time)
192193

193194
doc = Document.objects.create(name=name,
194195
title=title,
@@ -202,7 +203,7 @@ def create_recording(session,url):
202203

203204
# create DocEvent
204205
NewRevisionDocEvent.objects.create(type='new_revision',
205-
by=Person.objects.get(name='(System)'),
206+
by=user or Person.objects.get(name='(System)'),
206207
doc=doc,
207208
rev=doc.rev,
208209
desc='New revision available',

ietf/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
warnings.filterwarnings("ignore", message="The load_template\(\) method is deprecated. Use get_template\(\) instead.")
1919
warnings.filterwarnings("ignore", message="escape isn't the last filter in")
2020
warnings.filterwarnings("ignore", message="Deprecated allow_tags attribute used on field")
21+
warnings.filterwarnings("ignore", message="You passed a bytestring as `filenames`. This will not work on Python 3.")
2122

2223
try:
2324
import syslog

0 commit comments

Comments
 (0)