Added an API endpoint for bluesheet information upload. Factored out code in common with manual upload_session_bluesheets(). Tweaked ietf.secr.proceedings.utils.handle_upload_file() to handle temp files in addition to http file objects, to support the API endpoint.

- Legacy-Id: 18067
This commit is contained in:
Henrik Levkowetz 2020-06-26 14:49:13 +00:00
parent 729b52fb36
commit 3a76e4a935
4 changed files with 207 additions and 42 deletions

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import json
import os
import sys
@ -21,7 +22,7 @@ import debug # pyflakes:ignore
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.factories import PersonFactory
from ietf.person.factories import PersonFactory, random_faker
from ietf.person.models import PersonalApiKey
from ietf.stats.models import MeetingRegistration
from ietf.utils.mail import outbox, get_payload_text
@ -135,6 +136,93 @@ class CustomApiTests(TestCase):
event = doc.latest_event()
self.assertEqual(event.by, recman)
def test_api_upload_bluesheet(self):
url = urlreverse('ietf.meeting.views.api_upload_bluesheet')
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)
people = [
{"name":"Andrea Andreotti", "affiliation": "Azienda"},
{"name":"Bosse Bernadotte", "affiliation": "Bolag"},
{"name":"Charles Charlemagne", "affiliation": "Compagnie"},
]
for i in range(3):
faker = random_faker()
people.append(dict(name=faker.name(), affiliation=faker.company()))
bluesheet = json.dumps(people)
# 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 roles Recording Manager, Secretariat", 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 bluesheet parameter", status_code=400)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': '1', 'group': group.acronym,
'item': '1', 'bluesheet': bluesheet, })
self.assertContains(r, "No sessions found for meeting", status_code=400)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': 'bogous',
'item': '1', 'bluesheet': bluesheet, })
self.assertContains(r, "No sessions found in meeting '%s' for group 'bogous'"%meeting.number, status_code=400)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
'item': '1', 'bluesheet': "foobar", })
self.assertContains(r, "Invalid json value: 'foobar'", status_code=400)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
'item': '5', 'bluesheet': bluesheet, })
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': 'foo', 'bluesheet': bluesheet, })
self.assertContains(r, "Expected a numeric value for 'item', found 'foo'", status_code=400)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
'item': '1', 'bluesheet': bluesheet, })
self.assertContains(r, "Done", status_code=200)
r = self.client.post(url, {'apikey': apikey.hash(), 'meeting': meeting.number, 'group': group.acronym,
'item': '1', 'bluesheet': bluesheet, })
debug.show('bluesheet')
self.assertContains(r, "Done", status_code=200)
bluesheet = session.sessionpresentation_set.filter(document__type__slug='bluesheets').first().document
with open(bluesheet.get_file_name()) as file:
text = file.read()
for p in people:
self.assertIn(p['name'], text)
self.assertIn(p['affiliation'], text)
def test_person_export(self):
person = PersonFactory()
url = urlreverse('ietf.api.views.PersonalInformationExportView')

View file

@ -29,7 +29,9 @@ urlpatterns = [
# Let Meetecho trigger recording imports
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
# Let the registration system notify us about registrations
url(r'^notify/meeting/registration', api_views.api_new_meeting_registration),
url(r'^notify/meeting/bluesheet/?$', meeting_views.api_upload_bluesheet),
# Let the registration system notify us about registrations
url(r'^notify/meeting/registration/?', api_views.api_new_meeting_registration),
# OpenID authentication provider
url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
# For meetecho access

View file

@ -13,6 +13,7 @@ import os
import pytz
import re
import tarfile
import tempfile
import markdown2
@ -1702,55 +1703,27 @@ def upload_session_bluesheets(request, session_id, num):
if len(sessions) > 1:
session_number = 1 + sessions.index(session)
bluesheet_sp = session.sessionpresentation_set.filter(document__type='bluesheets').first()
if request.method == 'POST':
form = UploadBlueSheetForm(request.POST,request.FILES)
if form.is_valid():
file = request.FILES['file']
_, ext = os.path.splitext(file.name)
if bluesheet_sp:
doc = bluesheet_sp.document
doc.rev = '%02d' % (int(doc.rev)+1)
bluesheet_sp.rev = doc.rev
bluesheet_sp.save()
else:
ota = session.official_timeslotassignment()
sess_time = ota and ota.timeslot.time
if not sess_time:
return HttpResponse("Cannot receive uploads for an unscheduled session. Please check the session ID.", status=410, content_type="text/plain")
if session.meeting.type_id=='ietf':
name = 'bluesheets-%s-%s-%s' % (session.meeting.number,
session.group.acronym,
sess_time.strftime("%Y%m%d%H%M"))
title = 'Bluesheets IETF%s: %s : %s' % (session.meeting.number,
session.group.acronym,
sess_time.strftime("%a %H:%M"))
else:
name = 'bluesheets-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M"))
title = 'Bluesheets %s: %s' % (session.meeting.number, sess_time.strftime("%a %H:%M"))
doc = Document.objects.create(
name = name,
type_id = 'bluesheets',
title = title,
group = session.group,
rev = '00',
)
doc.states.add(State.objects.get(type_id='bluesheets',slug='active'))
DocAlias.objects.create(name=doc.name).docs.add(doc)
session.sessionpresentation_set.create(document=doc,rev='00')
filename = '%s-%s%s'% ( doc.name, doc.rev, ext)
doc.uploaded_filename = filename
e = NewRevisionDocEvent.objects.create(doc=doc, rev=doc.rev, by=request.user.person, type='new_revision', desc='New revision available: %s'%doc.rev)
save_error = handle_upload_file(file, filename, session.meeting, 'bluesheets', request=request, encoding=form.file_encoding[file.name])
ota = session.official_timeslotassignment()
sess_time = ota and ota.timeslot.time
if not sess_time:
return HttpResponse("Cannot receive uploads for an unscheduled session. Please check the session ID.", status=410, content_type="text/plain")
save_error = save_bluesheet(request, session, file, encoding=form.file_encoding[file.name])
if save_error:
form.add_error(None, save_error)
else:
doc.save_with_history([e])
return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym)
else:
form = UploadBlueSheetForm()
bluesheet_sp = session.sessionpresentation_set.filter(document__type='bluesheets').first()
return render(request, "meeting/upload_session_bluesheets.html",
{'session': session,
'session_number': session_number,
@ -1759,6 +1732,47 @@ def upload_session_bluesheets(request, session_id, num):
})
def save_bluesheet(request, session, file, encoding='utf-8'):
bluesheet_sp = session.sessionpresentation_set.filter(document__type='bluesheets').first()
_, ext = os.path.splitext(file.name)
if bluesheet_sp:
doc = bluesheet_sp.document
doc.rev = '%02d' % (int(doc.rev)+1)
bluesheet_sp.rev = doc.rev
bluesheet_sp.save()
else:
ota = session.official_timeslotassignment()
sess_time = ota and ota.timeslot.time
if session.meeting.type_id=='ietf':
name = 'bluesheets-%s-%s-%s' % (session.meeting.number,
session.group.acronym,
sess_time.strftime("%Y%m%d%H%M"))
title = 'Bluesheets IETF%s: %s : %s' % (session.meeting.number,
session.group.acronym,
sess_time.strftime("%a %H:%M"))
else:
name = 'bluesheets-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M"))
title = 'Bluesheets %s: %s' % (session.meeting.number, sess_time.strftime("%a %H:%M"))
doc = Document.objects.create(
name = name,
type_id = 'bluesheets',
title = title,
group = session.group,
rev = '00',
)
doc.states.add(State.objects.get(type_id='bluesheets',slug='active'))
DocAlias.objects.create(name=doc.name).docs.add(doc)
session.sessionpresentation_set.create(document=doc,rev='00')
filename = '%s-%s%s'% ( doc.name, doc.rev, ext)
doc.uploaded_filename = filename
e = NewRevisionDocEvent.objects.create(doc=doc, rev=doc.rev, by=request.user.person, type='new_revision', desc='New revision available: %s'%doc.rev)
save_error = handle_upload_file(file, filename, session.meeting, 'bluesheets', request=request, encoding=encoding)
if not save_error:
doc.save_with_history([e])
return save_error
class UploadMinutesForm(FileUploadForm):
apply_to_all = forms.BooleanField(label='Apply to all group sessions at this meeting',initial=True,required=False)
@ -3024,6 +3038,64 @@ def api_set_session_video_url(request):
return HttpResponse("Done", status=200, content_type='text/plain')
@require_api_key
@role_required('Recording Manager', 'Secretariat')
@csrf_exempt
def api_upload_bluesheet(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: number as string, i.e., '101', or 'interim-2018-quic-02'
# group: acronym or special, i.e., 'quic' or 'plenary'
# item: '1', '2', '3' (the group's first, second, third etc.
# session during the week)
# bluesheet: json blob with [{'name': 'Name', 'affiliation': 'Organization', }, ...]
for item in ['meeting', 'group', 'item', 'bluesheet',]:
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(400, "No sessions found for meeting '%s'" % (number, ))
acronym = request.POST.get('group')
sessions = sessions.filter(group__acronym=acronym)
if not sessions.exists():
return err(400, "No sessions found in meeting '%s' for group '%s'" % (number, acronym))
session_times = [ (s.official_timeslotassignment().timeslot.time, s) for s in sessions if s.official_timeslotassignment() ]
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, ))
bjson = request.POST.get('bluesheet')
try:
data = json.loads(bjson)
except json.decoder.JSONDecodeError:
return err(400, "Invalid json value: '%s'" % (bjson, ))
fd, name = tempfile.mkstemp(suffix=".txt", text=True)
os.close(fd)
with open(name, "w") as file:
file.write("Bluesheets for %s\n\n" % session)
for item in data:
file.write("{name}\t{affiliation}\n".format(**item))
with open(name, "br") as file:
save_err = save_bluesheet(request, session, file)
if save_err:
return err(400, save_err)
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

@ -59,8 +59,11 @@ def handle_upload_file(file,filename,meeting,subdir, request=None, encoding=None
"Your upload %s was changed by the sanitization; please check the "
"resulting content. " % (filename, ))
else:
for chunk in file.chunks():
destination.write(chunk)
if hasattr(file, 'chunks'):
for chunk in file.chunks():
destination.write(chunk)
else:
destination.write(file.read())
destination.close()
# unzip zipfile