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
This commit is contained in:
commit
a1c79a075d
5
PLAN
5
PLAN
|
@ -9,13 +9,10 @@ Planned work in rough order
|
||||||
|
|
||||||
* Revisit the review tool, work through the accumulated tickets.
|
* 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
|
* GroupFeatures cleanup. Move most to fields on GroupTypeName, and fix places
|
||||||
that still uses lists of group types to determine actions by instead
|
that still uses lists of group types to determine actions by instead
|
||||||
defining group type fields to hold the selector. (Setting up a new
|
defining group type fields to hold the selector. (Setting up a new
|
||||||
group type for things like PechaCucha and Hot RFC Lightning Tals probably
|
group type for things like PechaCucha and Hot RFC Lightning Talks probably
|
||||||
could have been done with only table edits if there hadn't been so much code
|
could have been done with only table edits if there hadn't been so much code
|
||||||
using type_id lists and features needing code changes).
|
using type_id lists and features needing code changes).
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
|
# Copyright The IETF Trust 2015-2018, All Rights Reserved
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.test import Client
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.test import Client
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from tastypie.test import ResourceTestCaseMixin
|
from tastypie.test import ResourceTestCaseMixin
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
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.meeting.test_data import make_meeting_test_data
|
||||||
|
from ietf.person.models import PersonalApiKey
|
||||||
|
from ietf.utils.test_utils import TestCase
|
||||||
|
|
||||||
OMITTED_APPS = (
|
OMITTED_APPS = (
|
||||||
'ietf.secr.meetings',
|
'ietf.secr.meetings',
|
||||||
|
@ -44,6 +51,75 @@ class CustomApiTestCase(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertContains(r, 'The datatracker API', status_code=200)
|
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)
|
||||||
|
video = 'https://foo.example.com/bar/beer/'
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': '1', 'group': group.acronym,
|
||||||
|
'item': '1', 'url': video, })
|
||||||
|
self.assertContains(r, "No sessions found for meeting", status_code=404)
|
||||||
|
|
||||||
|
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': 'bogous',
|
||||||
|
'item': '1', 'url': video, })
|
||||||
|
self.assertContains(r, "No sessions found in meeting '%s' for group 'bogous'"%meeting.number, status_code=404)
|
||||||
|
|
||||||
|
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
|
||||||
|
'item': '1', 'url': "foobar", })
|
||||||
|
self.assertContains(r, "Invalid url value: 'foobar'", status_code=400)
|
||||||
|
|
||||||
|
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
|
||||||
|
'item': '5', 'url': video, })
|
||||||
|
self.assertContains(r, "No item '5' found in list of sessions for group", status_code=400)
|
||||||
|
|
||||||
|
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):
|
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.apps = {}
|
self.apps = {}
|
||||||
|
|
|
@ -17,6 +17,7 @@ urlpatterns = [
|
||||||
url(r'^v1/?$', api_views.top_level),
|
url(r'^v1/?$', api_views.top_level),
|
||||||
# Custom API endpoints
|
# Custom API endpoints
|
||||||
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
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'^submit/?$', submit_views.api_submit),
|
||||||
url(r'^iesg/position', views_ballot.api_set_position),
|
url(r'^iesg/position', views_ballot.api_set_position),
|
||||||
]
|
]
|
||||||
|
|
|
@ -71,6 +71,7 @@ def has_role(user, role_names, *args, **kwargs):
|
||||||
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
|
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
|
||||||
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
|
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
|
||||||
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
|
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
|
||||||
|
"Recording Manager": Q(person=person,name="recman",group__type="ietf",group__state="active", ),
|
||||||
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
|
"Reviewer": Q(person=person, name="reviewer", group__state="active"),
|
||||||
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
|
"Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ),
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2007, All Rights Reserved
|
# Copyright The IETF Trust 2007-2018, All Rights Reserved
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -23,6 +23,8 @@ from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidde
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
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.urls import reverse,reverse_lazy
|
||||||
from django.db.models import Min, Max, Q
|
from django.db.models import Min, Max, Q
|
||||||
from django.forms.models import modelform_factory, inlineformset_factory
|
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.meeting.utils import finalize
|
||||||
from ietf.secr.proceedings.utils import handle_upload_file
|
from ietf.secr.proceedings.utils import handle_upload_file
|
||||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
|
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.mail import send_mail_message
|
||||||
from ietf.utils.pipe import pipe
|
from ietf.utils.pipe import pipe
|
||||||
from ietf.utils.pdf import pdf_pages
|
from ietf.utils.pdf import pdf_pages
|
||||||
|
@ -2180,6 +2183,62 @@ def api_import_recordings(request, number):
|
||||||
else:
|
else:
|
||||||
return HttpResponse(status=405)
|
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):
|
def important_dates(request, num=None):
|
||||||
assert num is None or num.isdigit()
|
assert num is None or num.isdigit()
|
||||||
preview_roles = ['Area Director', 'Secretariat', 'IETF Chair', 'IAD', ]
|
preview_roles = ['Area Director', 'Secretariat', 'IETF Chair', 'IAD', ]
|
||||||
|
|
|
@ -9538,6 +9538,16 @@
|
||||||
"model": "name.rolename",
|
"model": "name.rolename",
|
||||||
"pk": "pre-ad"
|
"pk": "pre-ad"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "",
|
||||||
|
"name": "Recording Manager",
|
||||||
|
"order": 13,
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "name.rolename",
|
||||||
|
"pk": "recman"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
@ -9578,6 +9588,16 @@
|
||||||
"model": "name.rolename",
|
"model": "name.rolename",
|
||||||
"pk": "trac-admin"
|
"pk": "trac-admin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "Provides log-in permission to restricted Trac instances",
|
||||||
|
"name": "Trac Editor",
|
||||||
|
"order": 0,
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "name.rolename",
|
||||||
|
"pk": "trac-editor"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "Audio streaming support",
|
"desc": "Audio streaming support",
|
||||||
|
@ -10002,7 +10022,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "xym",
|
"command": "xym",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-12-31T00:07:14.314",
|
"time": "2018-03-23T00:08:43.130",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "xym 0.4"
|
"version": "xym 0.4"
|
||||||
},
|
},
|
||||||
|
@ -10013,9 +10033,9 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "pyang",
|
"command": "pyang",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-12-31T00:07:15.241",
|
"time": "2018-03-23T00:08:44.177",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "pyang 1.7.3"
|
"version": "pyang 1.7.4"
|
||||||
},
|
},
|
||||||
"model": "utils.versioninfo",
|
"model": "utils.versioninfo",
|
||||||
"pk": 2
|
"pk": 2
|
||||||
|
@ -10024,11 +10044,22 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "yanglint",
|
"command": "yanglint",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-12-31T00:07:15.325",
|
"time": "2018-03-23T00:08:44.295",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "yanglint 0.14.53"
|
"version": "yanglint 0.14.73"
|
||||||
},
|
},
|
||||||
"model": "utils.versioninfo",
|
"model": "utils.versioninfo",
|
||||||
"pk": 3
|
"pk": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"command": "xml2rfc",
|
||||||
|
"switch": "--version",
|
||||||
|
"time": "2018-03-23T00:08:45.862",
|
||||||
|
"used": true,
|
||||||
|
"version": "xml2rfc 2.9.6"
|
||||||
|
},
|
||||||
|
"model": "utils.versioninfo",
|
||||||
|
"pk": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -290,6 +290,7 @@ def salt():
|
||||||
# Manual maintenance: List all endpoints that use @require_api_key here
|
# Manual maintenance: List all endpoints that use @require_api_key here
|
||||||
PERSON_API_KEY_ENDPOINTS = [
|
PERSON_API_KEY_ENDPOINTS = [
|
||||||
("/api/iesg/position", "/api/iesg/position"),
|
("/api/iesg/position", "/api/iesg/position"),
|
||||||
|
("/api/meeting/session/video/url", "/api/meeting/session/video/url"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class PersonalApiKey(models.Model):
|
class PersonalApiKey(models.Model):
|
||||||
|
@ -304,7 +305,10 @@ class PersonalApiKey(models.Model):
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_key(cls, s):
|
def validate_key(cls, s):
|
||||||
import struct, hashlib, base64
|
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)
|
id, salt, hash = struct.unpack(KEY_STRUCT, key)
|
||||||
k = cls.objects.filter(id=id)
|
k = cls.objects.filter(id=id)
|
||||||
if not k.exists():
|
if not k.exists():
|
||||||
|
|
|
@ -177,7 +177,7 @@ def get_or_create_recording_document(url,session):
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return create_recording(session,url)
|
return create_recording(session,url)
|
||||||
|
|
||||||
def create_recording(session,url):
|
def create_recording(session, url, title=None, user=None):
|
||||||
'''
|
'''
|
||||||
Creates the Document type=recording, setting external_url and creating
|
Creates the Document type=recording, setting external_url and creating
|
||||||
NewRevisionDocEvent
|
NewRevisionDocEvent
|
||||||
|
@ -185,10 +185,11 @@ def create_recording(session,url):
|
||||||
sequence = get_next_sequence(session.group,session.meeting,'recording')
|
sequence = get_next_sequence(session.group,session.meeting,'recording')
|
||||||
name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence)
|
name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence)
|
||||||
time = session.official_timeslotassignment().timeslot.time.strftime('%Y-%m-%d %H:%M')
|
time = session.official_timeslotassignment().timeslot.time.strftime('%Y-%m-%d %H:%M')
|
||||||
if url.endswith('mp3'):
|
if not title:
|
||||||
title = 'Audio recording for {}'.format(time)
|
if url.endswith('mp3'):
|
||||||
else:
|
title = 'Audio recording for {}'.format(time)
|
||||||
title = 'Video recording for {}'.format(time)
|
else:
|
||||||
|
title = 'Video recording for {}'.format(time)
|
||||||
|
|
||||||
doc = Document.objects.create(name=name,
|
doc = Document.objects.create(name=name,
|
||||||
title=title,
|
title=title,
|
||||||
|
@ -202,7 +203,7 @@ def create_recording(session,url):
|
||||||
|
|
||||||
# create DocEvent
|
# create DocEvent
|
||||||
NewRevisionDocEvent.objects.create(type='new_revision',
|
NewRevisionDocEvent.objects.create(type='new_revision',
|
||||||
by=Person.objects.get(name='(System)'),
|
by=user or Person.objects.get(name='(System)'),
|
||||||
doc=doc,
|
doc=doc,
|
||||||
rev=doc.rev,
|
rev=doc.rev,
|
||||||
desc='New revision available',
|
desc='New revision available',
|
||||||
|
|
|
@ -18,6 +18,7 @@ warnings.filterwarnings("ignore", message="on_delete will be a required arg for
|
||||||
warnings.filterwarnings("ignore", message="The load_template\(\) method is deprecated. Use get_template\(\) instead.")
|
warnings.filterwarnings("ignore", message="The load_template\(\) method is deprecated. Use get_template\(\) instead.")
|
||||||
warnings.filterwarnings("ignore", message="escape isn't the last filter in")
|
warnings.filterwarnings("ignore", message="escape isn't the last filter in")
|
||||||
warnings.filterwarnings("ignore", message="Deprecated allow_tags attribute used on field")
|
warnings.filterwarnings("ignore", message="Deprecated allow_tags attribute used on field")
|
||||||
|
warnings.filterwarnings("ignore", message="You passed a bytestring as `filenames`. This will not work on Python 3.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import syslog
|
import syslog
|
||||||
|
|
|
@ -226,6 +226,45 @@
|
||||||
Done
|
Done
|
||||||
</pre>
|
</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>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue