diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 07bba940a..331a44fc9 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -521,7 +521,7 @@ def document_main(request, name, rev=None): # TODO : Add "recording", and "bluesheets" here when those documents are appropriately # created and content is made available on disk - if doc.type_id in ("slides", "agenda", "minutes"): + if doc.type_id in ("slides", "agenda", "minutes", "bluesheets",): can_manage_material = can_manage_materials(request.user, doc.group) presentations = doc.future_presentations() if doc.meeting_related(): @@ -540,7 +540,7 @@ def document_main(request, name, rev=None): for g in globs: extension = os.path.splitext(g)[1] t = os.path.splitext(g)[1].lstrip(".") - url = doc.href() + url = doc.get_absolute_url() if doc.type_id=='bluesheets' else doc.href() urlbase, urlext = os.path.splitext(url) if not url.endswith("/") and not url.endswith(extension): url = urlbase + extension diff --git a/ietf/meeting/migrations/0032_reconstruct_bluesheet_docs_95through96.py b/ietf/meeting/migrations/0032_reconstruct_bluesheet_docs_95through96.py index b39faa600..74e8069a5 100644 --- a/ietf/meeting/migrations/0032_reconstruct_bluesheet_docs_95through96.py +++ b/ietf/meeting/migrations/0032_reconstruct_bluesheet_docs_95through96.py @@ -12,6 +12,7 @@ def official_time(session): def forward(apps, schema_editor): Document = apps.get_model('doc','Document') + NewRevisionDocEvent = apps.get_model('doc','NewRevisionDocEvent') State = apps.get_model('doc','State') Group = apps.get_model('group','Group') Meeting = apps.get_model('meeting', 'Meeting') @@ -46,12 +47,14 @@ def forward(apps, schema_editor): doc = Document.objects.create( name=bs[n][:-4], type_id='bluesheets', - title='Bluesheets IETF%d : %s : %s ' % (num,acronym,official_time(sess[n]).timeslot.time.strftime('%a %H:%M')), + title='Bluesheets IETF%d : %s : %s' % (num,acronym,official_time(sess[n]).timeslot.time.strftime('%a %H:%M')), group=group, rev='00', external_url=bs[n], ) doc.states.add(active) + doc.docalias_set.create(name=doc.name) + NewRevisionDocEvent.objects.create(doc=doc,time=doc.time,by_id=1,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) sess[n].sessionpresentation_set.create(document=doc,rev='00') def reverse(apps, schema_editor): diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 4c73a7299..f08f62626 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -12,6 +12,7 @@ from django.conf import settings from django.contrib.auth.models import User from pyquery import PyQuery +from StringIO import StringIO from ietf.doc.models import Document from ietf.group.models import Group @@ -1198,3 +1199,45 @@ class FinalizeProceedingsTests(TestCase): self.assertEqual(meeting.proceedings_final,True) self.assertEqual(meeting.session_set.filter(group__acronym="mars").first().sessionpresentation_set.filter(document__type="draft").first().rev,'00') +class BluesheetsTests(TestCase): + def test_upload_blusheets(self): + session = SessionFactory(meeting__type_id='ietf') + url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertFalse(q("div.alert")) + self.assertFalse(session.sessionpresentation_set.exists()) + test_file = StringIO('this is some text for a test') + test_file.name = "not_really.pdf" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 302) + bs_doc = session.sessionpresentation_set.filter(document__type_id='bluesheets').first().document + self.assertEqual(bs_doc.rev,'00') + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q("div.alert")) + test_file = StringIO('this is some different text for a test') + test_file.name = "also_not_really.pdf" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 302) + bs_doc = Document.objects.get(pk=bs_doc.pk) + self.assertEqual(bs_doc.rev,'01') + + def test_upload_bluesheets_interim(self): + session=SessionFactory(meeting__type_id='interim') + url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertFalse(q("div.alert")) + self.assertFalse(session.sessionpresentation_set.exists()) + test_file = StringIO('this is some text for a test') + test_file.name = "not_really.pdf" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 302) + bs_doc = session.sessionpresentation_set.filter(document__type_id='bluesheets').first().document + self.assertEqual(bs_doc.rev,'00') diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 26093b87b..b08a7d37d 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -9,6 +9,7 @@ from ietf.meeting import ajax safe_for_all_meeting_types = [ url(r'^session/(?P[-a-z0-9]+)/?$', views.session_details), url(r'^session/(?P\d+)/drafts$', views.add_session_drafts), + url(r'^session/(?P\d+)/bluesheets$', views.upload_session_bluesheets), ] type_ietf_only_patterns = [ diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 4bdc08bbc..ba8074d5b 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -51,6 +51,8 @@ from ietf.meeting.helpers import send_interim_cancellation_notice from ietf.meeting.helpers import send_interim_approval_request from ietf.meeting.helpers import send_interim_announcement_request from ietf.meeting.utils import finalize +from ietf.person.models import Person +from ietf.secr.proceedings.utils import handle_upload_file from ietf.utils.mail import send_mail_message from ietf.utils.pipe import pipe from ietf.utils.pdf import pdf_pages @@ -1085,6 +1087,67 @@ def add_session_drafts(request, session_id, num): 'form': form, }) +class UploadBlueSheetForm(forms.Form): + file = forms.FileField(label='Bluesheet scan to upload') + +@role_required('Secretariat') +def upload_session_bluesheets(request, session_id, num): + # num is redundant, but we're dragging it along an artifact of where we are in the current URL structure + session = get_object_or_404(Session,pk=session_id) + + session_number = None + sessions = get_sessions(session.meeting.number,session.group.acronym) + 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) + else: + sess_time = session.official_timeslotassignment().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')) + doc.docalias_set.create(name=doc.name) + session.sessionpresentation_set.create(document=doc,rev='00') + filename = '%s-%s%s'% ( doc.name, doc.rev, ext) + doc.external_url = filename + doc.save() + NewRevisionDocEvent.objects.create(doc=doc,time=doc.time,by=Person.objects.get(name='(System)'),type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + handle_upload_file(file, filename, session.meeting, 'bluesheets') + return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) + else: + form = UploadBlueSheetForm() + + return render(request, "meeting/upload_session_bluesheets.html", + {'session': session, + 'session_number': session_number, + 'bluesheet_sp' : bluesheet_sp, + 'form': form, + }) + @role_required('Secretariat') def make_schedule_official(request, num, owner, name): diff --git a/ietf/secr/proceedings/utils.py b/ietf/secr/proceedings/utils.py new file mode 100644 index 000000000..799951c7d --- /dev/null +++ b/ietf/secr/proceedings/utils.py @@ -0,0 +1,39 @@ +import glob +import os + +import debug # pyflakes:ignore + +def handle_upload_file(file,filename,meeting,subdir): + ''' + This function takes a file object, a filename and a meeting object and subdir as string. + It saves the file to the appropriate directory, get_materials_path() + subdir. + If the file is a zip file, it creates a new directory in 'slides', which is the basename of the + zip file and unzips the file in the new directory. + ''' + base, extension = os.path.splitext(filename) + + if extension == '.zip': + path = os.path.join(meeting.get_materials_path(),subdir,base) + if not os.path.exists(path): + os.mkdir(path) + else: + path = os.path.join(meeting.get_materials_path(),subdir) + if not os.path.exists(path): + os.makedirs(path) + + # agendas and minutes can only have one file instance so delete file if it already exists + if subdir in ('agenda','minutes'): + old_files = glob.glob(os.path.join(path,base) + '.*') + for f in old_files: + os.remove(f) + + destination = open(os.path.join(path,filename), 'wb+') + for chunk in file.chunks(): + destination.write(chunk) + destination.close() + + # unzip zipfile + if extension == '.zip': + os.chdir(path) + os.system('unzip %s' % filename) + diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py index e49f848d6..e17648910 100644 --- a/ietf/secr/proceedings/views.py +++ b/ietf/secr/proceedings/views.py @@ -31,6 +31,7 @@ from ietf.secr.proceedings.forms import EditSlideForm, RecordingForm, RecordingE from ietf.secr.proceedings.proc_utils import ( gen_acknowledgement, gen_agenda, gen_areas, gen_attendees, gen_group_pages, gen_index, gen_irtf, gen_overview, gen_plenaries, gen_progress, gen_research, gen_training, create_proceedings, create_recording ) +from ietf.secr.proceedings.utils import handle_upload_file from ietf.utils.log import log # ------------------------------------------------- @@ -138,40 +139,6 @@ def get_next_order_num(session): return max_order + 1 if max_order else 1 -def handle_upload_file(file,filename,meeting,subdir): - ''' - This function takes a file object, a filename and a meeting object and subdir as string. - It saves the file to the appropriate directory, get_materials_path() + subdir. - If the file is a zip file, it creates a new directory in 'slides', which is the basename of the - zip file and unzips the file in the new directory. - ''' - base, extension = os.path.splitext(filename) - - if extension == '.zip': - path = os.path.join(meeting.get_materials_path(),subdir,base) - if not os.path.exists(path): - os.mkdir(path) - else: - path = os.path.join(meeting.get_materials_path(),subdir) - if not os.path.exists(path): - os.makedirs(path) - - # agendas and minutes can only have one file instance so delete file if it already exists - if subdir in ('agenda','minutes'): - old_files = glob.glob(os.path.join(path,base) + '.*') - for f in old_files: - os.remove(f) - - destination = open(os.path.join(path,filename), 'wb+') - for chunk in file.chunks(): - destination.write(chunk) - destination.close() - - # unzip zipfile - if extension == '.zip': - os.chdir(path) - os.system('unzip %s' % filename) - def parsedate(d): ''' This function takes a date object and returns a tuple of year,month,day diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index 67369a8f5..0a3cc674f 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -23,6 +23,9 @@ Link additional drafts to session + {% if user|has_role:"Secretariat" %} + Upload Bluesheets + {% endif %} {% if not type_counter.agenda %} This session does not yet have an agenda {% endif %} diff --git a/ietf/templates/meeting/upload_session_bluesheets.html b/ietf/templates/meeting/upload_session_bluesheets.html new file mode 100644 index 000000000..53a59f618 --- /dev/null +++ b/ietf/templates/meeting/upload_session_bluesheets.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin staticfiles bootstrap3 %} + +{% block title %}Upload Bluesheets for {{ session.meeting }} : {{ session.group.acronym }}{% endblock %} + +{% block content %} + {% origin %} + +

Upload Bluesheets for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}

+{% if session_number %}

Session {{session_number}} : {{session.official_timeslotassignment.timeslot.time|date:"D M-d-Y Hi"}}

{% endif %} + + {% if bluesheet_sp %} +
+ Bluesheets have alrady been uploaded for this session. + See {{bluesheet_sp.document.name}}-{{bluesheet_sp.document.rev}}. + Continue with this upload to provide an updated version of that document. +
+ {% endif %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
+ + +{% endblock %}