diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 9e7012a8a..c931fa507 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -60,7 +60,7 @@ class DocumentInfo(models.Model): abstract = models.TextField(blank=True) rev = models.CharField(verbose_name="revision", max_length=16, blank=True) pages = models.IntegerField(blank=True, null=True) - order = models.IntegerField(default=1, blank=True) + order = models.IntegerField(default=1, blank=True) # This is probably obviated by SessionPresentaion.order intended_std_level = models.ForeignKey(IntendedStdLevelName, verbose_name="Intended standardization level", blank=True, null=True) std_level = models.ForeignKey(StdLevelName, verbose_name="Standardization level", blank=True, null=True) ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) diff --git a/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py b/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py new file mode 100644 index 000000000..c823c62d5 --- /dev/null +++ b/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0036_add_order_to_sessionpresentation'), + ] + + operations = [ + migrations.AlterModelOptions( + name='sessionpresentation', + options={'ordering': ('order',)}, + ), + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 27e1ee4f6..af12e2162 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -963,6 +963,7 @@ class SessionPresentation(models.Model): class Meta: db_table = 'meeting_session_materials' + ordering = ('order',) def __unicode__(self): return u"%s -> %s-%s" % (self.session, self.document.name, self.rev) @@ -1005,7 +1006,7 @@ class Session(models.Model): for d in l: d.meeting_related = lambda: True else: - l = self.materials.filter(type=material_type).exclude(states__type=material_type, states__slug='deleted').order_by("order") + l = self.materials.filter(type=material_type).exclude(states__type=material_type, states__slug='deleted').order_by('sessionpresentation__order') if only_one: if l: diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 3811f2dd4..66badd226 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -1271,7 +1271,7 @@ 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): +class MaterialsTests(TestCase): def setUp(self): self.materials_dir = os.path.abspath(settings.TEST_MATERIALS_DIR) @@ -1284,14 +1284,14 @@ class BluesheetsTests(TestCase): settings.AGENDA_PATH = self.saved_agenda_path shutil.rmtree(self.materials_dir) - def test_upload_blusheets(self): + def test_upload_bluesheets(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.assertTrue('Upload' in unicode(q("title"))) self.assertFalse(session.sessionpresentation_set.exists()) test_file = StringIO('this is some text for a test') test_file.name = "not_really.pdf" @@ -1302,7 +1302,7 @@ class BluesheetsTests(TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertTrue(q("div.alert")) + self.assertTrue('Revise' in unicode(q("title"))) 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)) @@ -1317,7 +1317,7 @@ class BluesheetsTests(TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertFalse(q("div.alert")) + self.assertTrue('Upload' in unicode(q("title"))) self.assertFalse(session.sessionpresentation_set.exists()) test_file = StringIO('this is some text for a test') test_file.name = "not_really.pdf" @@ -1325,3 +1325,129 @@ class BluesheetsTests(TestCase): self.assertEqual(r.status_code, 302) bs_doc = session.sessionpresentation_set.filter(document__type_id='bluesheets').first().document self.assertEqual(bs_doc.rev,'00') + + def test_upload_minutes_agenda(self): + for doctype in ('minutes','agenda'): + session = SessionFactory(meeting__type_id='ietf') + if doctype == 'minutes': + url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id}) + else: + url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id}) + self.client.logout() + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('Upload' in unicode(q("Title"))) + self.assertFalse(session.sessionpresentation_set.exists()) + self.assertFalse(q('form input[type="checkbox"]')) + + session2 = SessionFactory(meeting=session.meeting,group=session.group) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('form input[type="checkbox"]')) + + test_file = StringIO('this is some text for a test') + test_file.name = "not_really.json" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('form .has-error')) + + test_file = StringIO('this is some text for a test'*1510000) + test_file.name = "not_really.pdf" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('form .has-error')) + + test_file = StringIO('this is some text for a test') + test_file.name = "not_really.txt" + r = self.client.post(url,dict(file=test_file,apply_to_all=False)) + self.assertEqual(r.status_code, 302) + doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document + self.assertEqual(doc.rev,'00') + self.assertFalse(session2.sessionpresentation_set.filter(document__type_id=doctype)) + + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('Revise' in unicode(q("Title"))) + test_file = StringIO('this is some different text for a test') + test_file.name = "also_not_really.txt" + r = self.client.post(url,dict(file=test_file,apply_to_all=True)) + self.assertEqual(r.status_code, 302) + doc = Document.objects.get(pk=doc.pk) + self.assertEqual(doc.rev,'01') + self.assertTrue(session2.sessionpresentation_set.filter(document__type_id=doctype)) + + def test_upload_minutes_agenda_interim(self): + session=SessionFactory(meeting__type_id='interim') + for doctype in ('minutes','agenda'): + if doctype=='minutes': + url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id}) + else: + url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id}) + self.client.logout() + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('Upload' in unicode(q("title"))) + self.assertFalse(session.sessionpresentation_set.filter(document__type_id=doctype)) + test_file = StringIO('this is some text for a test') + test_file.name = "not_really.txt" + r = self.client.post(url,dict(file=test_file)) + self.assertEqual(r.status_code, 302) + doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document + self.assertEqual(doc.rev,'00') + + def test_upload_slides(self): + + session1 = SessionFactory(meeting__type_id='ietf') + session2 = SessionFactory(meeting=session1.meeting,group=session1.group) + url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session1.meeting.number,'session_id':session1.id}) + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('Upload' in unicode(q("title"))) + self.assertFalse(session1.sessionpresentation_set.filter(document__type_id='slides')) + test_file = StringIO('this is not really a slide') + test_file.name = 'not_really.txt' + r = self.client.post(url,dict(file=test_file,title='a test slide file',apply_to_all=True)) + self.assertEqual(r.status_code, 302) + self.assertEqual(session1.sessionpresentation_set.count(),1) + self.assertEqual(session2.sessionpresentation_set.count(),1) + sp = session2.sessionpresentation_set.first() + self.assertEqual(sp.document.name, 'slides-%s-%s-a-test-slide-file' % (session1.meeting.number,session1.group.acronym ) ) + self.assertEqual(sp.order,1) + + url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session2.meeting.number,'session_id':session2.id}) + test_file = StringIO('some other thing still not slidelike') + test_file.name = 'also_not_really.txt' + r = self.client.post(url,dict(file=test_file,title='a different slide file',apply_to_all=False)) + self.assertEqual(r.status_code, 302) + self.assertEqual(session1.sessionpresentation_set.count(),1) + self.assertEqual(session2.sessionpresentation_set.count(),2) + sp = session2.sessionpresentation_set.get(document__name__endswith='-a-different-slide-file') + self.assertEqual(sp.order,2) + self.assertEqual(sp.rev,u'00') + self.assertEqual(sp.document.rev,u'00') + + url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session2.meeting.number,'session_id':session2.id,'name':session2.sessionpresentation_set.get(order=2).document.name}) + r = self.client.get(url) + self.assertTrue(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue('Revise' in unicode(q("title"))) + test_file = StringIO('new content for the second slide deck') + test_file.name = 'doesnotmatter.txt' + r = self.client.post(url,dict(file=test_file,title='rename the presentation',apply_to_all=False)) + self.assertEqual(r.status_code, 302) + self.assertEqual(session1.sessionpresentation_set.count(),1) + self.assertEqual(session2.sessionpresentation_set.count(),2) + sp = session2.sessionpresentation_set.get(order=2) + self.assertEqual(sp.rev,u'01') + self.assertEqual(sp.document.rev,u'01') + diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 045821e50..659f124dd 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -11,6 +11,9 @@ 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), + url(r'^session/(?P\d+)/minutes$', views.upload_session_minutes), + url(r'^session/(?P\d+)/agenda$', views.upload_session_agenda), + url(r'^session/(?P\d+)/slides(?:/%(name)s)?$' % settings.URL_REGEXPS, views.upload_session_slides), ] diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 6b10fe0d1..eeba96c99 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -28,7 +28,9 @@ from django.forms import ModelForm from django.template.loader import render_to_string from django.utils.functional import curry from django.views.decorators.cache import cache_page +from django.utils.text import slugify from django.views.decorators.csrf import ensure_csrf_cookie +from django.template.defaultfilters import filesizeformat from ietf.doc.fields import SearchableDocumentsField from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent @@ -52,7 +54,6 @@ 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 @@ -1011,10 +1012,9 @@ def session_details(request, num, acronym ): if not sessions: raise Http404 - type_counter = Counter() - for session in sessions: + session.type_counter = Counter() ss = session.timeslotassignments.filter(schedule=meeting.agenda).order_by('timeslot__time') if ss: session.time = ', '.join(x.timeslot.time.strftime("%A %b-%d-%Y %H%M") for x in ss) @@ -1033,10 +1033,7 @@ def session_details(request, num, acronym ): # TODO FIXME Deleted materials shouldn't be in the sessionpresentation_set for qs in [session.filtered_artifacts,session.filtered_slides,session.filtered_drafts]: qs = [p for p in qs if p.document.get_state_slug(p.document.type_id)!='deleted'] - type_counter.update([p.document.type.slug for p in qs]) - - #session.filtered_sessionpresentation_set = [p for p in session.sessionpresentation_set.all() if p.document.get_state_slug(p.document.type_id)!='deleted'] - #type_counter.update([p.document.type.slug for p in session.filtered_sessionpresentation_set]) + session.type_counter.update([p.document.type.slug for p in qs]) can_manage = can_manage_materials(request.user, Group.objects.get(acronym=acronym)) @@ -1045,7 +1042,6 @@ def session_details(request, num, acronym ): 'meeting' :meeting , 'acronym' :acronym, 'can_manage_materials' : can_manage, - 'type_counter': type_counter, }) class SessionDraftsForm(forms.Form): @@ -1119,6 +1115,8 @@ def upload_session_bluesheets(request, session_id, num): if bluesheet_sp: doc = bluesheet_sp.document doc.rev = '%02d' % (int(doc.rev)+1) + bluesheet_sp.rev = doc.rev + bluesheet_sp.save() else: sess_time = session.official_timeslotassignment().timeslot.time if session.meeting.type_id=='ietf': @@ -1143,7 +1141,7 @@ def upload_session_bluesheets(request, session_id, num): session.sessionpresentation_set.create(document=doc,rev='00') filename = '%s-%s%s'% ( doc.name, doc.rev, ext) doc.external_url = filename - e = NewRevisionDocEvent.objects.create(doc=doc, by=Person.objects.get(name='(System)'),type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + e = NewRevisionDocEvent.objects.create(doc=doc,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) doc.save_with_history([e]) handle_upload_file(file, filename, session.meeting, 'bluesheets') return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) @@ -1157,6 +1155,301 @@ def upload_session_bluesheets(request, session_id, num): 'form': form, }) +VALID_MINUTES_EXTENSIONS = ('.txt','.html','.htm','.pdf') +# FIXME: This form validation code (based on the secretariat upload code) only looks at filename extensions +# It should look at the contents of the files instead. +class UploadMinutesForm(forms.Form): + file = forms.FileField(label='Minutes file to upload. Note that you can only upload minutes in txt, html, or pdf formats.') + apply_to_all = forms.BooleanField(label='Apply to all group sessions at this meeting',initial=True,required=False) + + def __init__(self, num_sessions, *args, **kwargs): + super(UploadMinutesForm, self).__init__(*args, **kwargs) + if num_sessions<2: + self.fields.pop('apply_to_all') + + def clean_file(self): + file = self.cleaned_data['file'] + if file._size > settings.SECR_MAX_UPLOAD_SIZE: + raise forms.ValidationError('Please keep filesize under %s. Requested upload size is %s' % (filesizeformat(settings.SECR_MAX_UPLOAD_SIZE),filesizeformat(file._size))) + if os.path.splitext(file.name)[1].lower() not in VALID_MINUTES_EXTENSIONS: + raise forms.ValidationError('Only these file types supported for minutes: %s' % ','.join(VALID_MINUTES_EXTENSIONS)) + return file + +def upload_session_minutes(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) + + if not session.can_manage_materials(request.user): + return HttpResponseForbidden("You don't have permission to upload minutes for this session.") + if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"): + return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.") + + session_number = None + sessions = get_sessions(session.meeting.number,session.group.acronym) + num_sessions = len(sessions) + if len(sessions) > 1: + session_number = 1 + sessions.index(session) + + minutes_sp = session.sessionpresentation_set.filter(document__type='minutes').first() + + if request.method == 'POST': + form = UploadMinutesForm(num_sessions,request.POST,request.FILES) + if form.is_valid(): + file = request.FILES['file'] + _, ext = os.path.splitext(file.name) + apply_to_all = True + if num_sessions > 1: + apply_to_all = form.cleaned_data['apply_to_all'] + if minutes_sp: + doc = minutes_sp.document + doc.rev = '%02d' % (int(doc.rev)+1) + minutes_sp.rev = doc.rev + minutes_sp.save() + else: + sess_time = session.official_timeslotassignment().timeslot.time + if session.meeting.type_id=='ietf': + name = 'minutes-%s-%s' % (session.meeting.number, + session.group.acronym) + title = 'Minutes IETF%s: %s' % (session.meeting.number, + session.group.acronym) + if not apply_to_all: + name += '-%s' % (sess_time.strftime("%Y%m%d%H%M"),) + title += ': %s' % (sess_time.strftime("%a %H:%M"),) + else: + name = 'minutes-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M")) + title = 'Minutes %s: %s' % (session.meeting.number, sess_time.strftime("%a %H:%M")) + doc = Document.objects.create( + name = name, + type_id = 'minutes', + title = title, + group = session.group, + rev = '00', + ) + doc.states.add(State.objects.get(type_id='minutes',slug='active')) + doc.docalias_set.create(name=doc.name) + session.sessionpresentation_set.create(document=doc,rev='00') + if apply_to_all: + for other_session in sessions: + if other_session != session: + other_session.sessionpresentation_set.filter(document__type='minutes').delete() + other_session.sessionpresentation_set.create(document=doc,rev=doc.rev) + filename = '%s-%s%s'% ( doc.name, doc.rev, ext) + doc.external_url = filename + e = NewRevisionDocEvent.objects.create(doc=doc,time=doc.time,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + doc.save_with_history([e]) + # The way this function builds the filename it will never trigger the file delete in handle_file_upload. + handle_upload_file(file, filename, session.meeting, 'minutes') + return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) + else: + form = UploadMinutesForm(num_sessions) + + return render(request, "meeting/upload_session_minutes.html", + {'session': session, + 'session_number': session_number, + 'minutes_sp' : minutes_sp, + 'form': form, + }) + +VALID_AGENDA_EXTENSIONS = ('.txt','.html','.htm',) +# FIXME: This form validation code (based on the secretariat upload code) only looks at filename extensions +# It should look at the contents of the files instead. +class UploadAgendaForm(forms.Form): + file = forms.FileField(label='Agenda file to upload. Note that you can only upload agendas in txt or html formats.') + apply_to_all = forms.BooleanField(label='Apply to all group sessions at this meeting',initial=True,required=False) + + def __init__(self, num_sessions, *args, **kwargs): + super(UploadAgendaForm, self).__init__(*args, **kwargs) + if num_sessions<2: + self.fields.pop('apply_to_all') + + def clean_file(self): + file = self.cleaned_data['file'] + if file._size > settings.SECR_MAX_UPLOAD_SIZE: + raise forms.ValidationError('Please keep filesize under %s. Requested upload size is %s' % (filesizeformat(settings.SECR_MAX_UPLOAD_SIZE),filesizeformat(file._size))) + if os.path.splitext(file.name)[1].lower() not in VALID_AGENDA_EXTENSIONS: + raise forms.ValidationError('Only these file types supported for agendas: %s' % ','.join(VALID_AGENDA_EXTENSIONS)) + return file + +def upload_session_agenda(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) + + if not session.can_manage_materials(request.user): + return HttpResponseForbidden("You don't have permission to upload an agenda for this session.") + if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"): + return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.") + + session_number = None + sessions = get_sessions(session.meeting.number,session.group.acronym) + num_sessions = len(sessions) + if len(sessions) > 1: + session_number = 1 + sessions.index(session) + + agenda_sp = session.sessionpresentation_set.filter(document__type='agenda').first() + + if request.method == 'POST': + form = UploadAgendaForm(num_sessions,request.POST,request.FILES) + if form.is_valid(): + file = request.FILES['file'] + _, ext = os.path.splitext(file.name) + apply_to_all = True + if num_sessions > 1: + apply_to_all = form.cleaned_data['apply_to_all'] + if agenda_sp: + doc = agenda_sp.document + doc.rev = '%02d' % (int(doc.rev)+1) + agenda_sp.rev = doc.rev + agenda_sp.save() + else: + sess_time = session.official_timeslotassignment().timeslot.time + if session.meeting.type_id=='ietf': + name = 'agenda-%s-%s' % (session.meeting.number, + session.group.acronym) + title = 'Agenda IETF%s: %s' % (session.meeting.number, + session.group.acronym) + if not apply_to_all: + name += '-%s' % (sess_time.strftime("%Y%m%d%H%M"),) + title += ': %s' % (sess_time.strftime("%a %H:%M"),) + else: + name = 'agenda-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M")) + title = 'Agenda %s: %s' % (session.meeting.number, sess_time.strftime("%a %H:%M")) + doc = Document.objects.create( + name = name, + type_id = 'agenda', + title = title, + group = session.group, + rev = '00', + ) + doc.states.add(State.objects.get(type_id='agenda',slug='active')) + doc.docalias_set.create(name=doc.name) + session.sessionpresentation_set.create(document=doc,rev='00') + if apply_to_all: + for other_session in sessions: + if other_session != session: + other_session.sessionpresentation_set.filter(document__type='agenda').delete() + other_session.sessionpresentation_set.create(document=doc,rev=doc.rev) + filename = '%s-%s%s'% ( doc.name, doc.rev, ext) + doc.external_url = filename + e = NewRevisionDocEvent.objects.create(doc=doc,time=doc.time,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + doc.save_with_history([e]) + # The way this function builds the filename it will never trigger the file delete in handle_file_upload. + handle_upload_file(file, filename, session.meeting, 'agenda') + return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) + else: + form = UploadAgendaForm(num_sessions) + + return render(request, "meeting/upload_session_agenda.html", + {'session': session, + 'session_number': session_number, + 'agenda_sp' : agenda_sp, + 'form': form, + }) + +VALID_SLIDE_EXTENSIONS = ('.doc','.docx','.pdf','.ppt','.pptx','.txt') # Note the removal of .zip +# FIXME: This form validation code (based on the secretariat upload code) only looks at filename extensions +# It should look at the contents of the files instead. +class UploadSlidesForm(forms.Form): + title = forms.CharField(max_length=255) + file = forms.FileField(label='Slides file to upload.') + apply_to_all = forms.BooleanField(label='Apply to all group sessions at this meeting',initial=True,required=False) + + def __init__(self, num_sessions, *args, **kwargs): + super(UploadSlidesForm, self).__init__(*args, **kwargs) + if num_sessions<2: + self.fields.pop('apply_to_all') + + def clean_file(self): + file = self.cleaned_data['file'] + if file._size > settings.SECR_MAX_UPLOAD_SIZE: + raise forms.ValidationError('Please keep filesize under %s. Requested upload size is %s' % (filesizeformat(settings.SECR_MAX_UPLOAD_SIZE),filesizeformat(file._size))) + if os.path.splitext(file.name)[1].lower() not in VALID_SLIDE_EXTENSIONS: + raise forms.ValidationError('Only these file types supported for slides: %s' % ','.join(VALID_SLIDE_EXTENSIONS)) + return file + +def upload_session_slides(request, session_id, num, name): + # 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) + if not session.can_manage_materials(request.user): + return HttpResponseForbidden("You don't have permission to upload slides for this session.") + if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"): + return HttpResponseForbidden("The materials cutoff for this session has passed. Contact the secretariat for further action.") + + session_number = None + sessions = get_sessions(session.meeting.number,session.group.acronym) + num_sessions = len(sessions) + if len(sessions) > 1: + session_number = 1 + sessions.index(session) + + slides = None + slides_sp = None + if name: + slides = Document.objects.filter(name=name).first() + if not (slides and slides.type_id=='slides'): + raise Http404 + slides_sp = session.sessionpresentation_set.filter(document=slides).first() + + if request.method == 'POST': + form = UploadSlidesForm(num_sessions,request.POST,request.FILES) + if form.is_valid(): + file = request.FILES['file'] + _, ext = os.path.splitext(file.name) + apply_to_all = True + if num_sessions > 1: + apply_to_all = form.cleaned_data['apply_to_all'] + if slides_sp: + doc = slides_sp.document + doc.rev = '%02d' % (int(doc.rev)+1) + doc.title = form.cleaned_data['title'] + slides_sp.rev = doc.rev + slides_sp.save() + else: + title = form.cleaned_data['title'] + sess_time = session.official_timeslotassignment().timeslot.time + if session.meeting.type_id=='ietf': + name = 'slides-%s-%s' % (session.meeting.number, + session.group.acronym) + if not apply_to_all: + name += '-%s' % (sess_time.strftime("%Y%m%d%H%M"),) + else: + name = 'slides-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M")) + name = name + '-' + slugify(title) + doc = Document.objects.create( + name = name, + type_id = 'slides', + title = title, + group = session.group, + rev = '00', + ) + doc.states.add(State.objects.get(type_id='slides',slug='active')) + doc.states.add(State.objects.get(type_id='reuse_policy',slug='single')) + doc.docalias_set.create(name=doc.name) + max_order = session.sessionpresentation_set.filter(document__type='slides').aggregate(Max('order'))['order__max'] or 0 + session.sessionpresentation_set.create(document=doc,rev=doc.rev,order=max_order+1) + if apply_to_all: + for other_session in sessions: + if other_session != session: + max_order = other_session.sessionpresentation_set.filter(document__type='slides').aggregate(Max('order'))['order__max'] or 0 + other_session.sessionpresentation_set.create(document=doc,rev=doc.rev,order=max_order+1) + filename = '%s-%s%s'% ( doc.name, doc.rev, ext) + doc.external_url = filename + e = NewRevisionDocEvent.objects.create(doc=doc,time=doc.time,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + doc.save_with_history([e]) + # The way this function builds the filename it will never trigger the file delete in handle_file_upload. + handle_upload_file(file, filename, session.meeting, 'slides') + return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) + else: + initial = {} + if slides: + initial = {'title':slides.title} + form = UploadSlidesForm(num_sessions, initial=initial) + + return render(request, "meeting/upload_session_slides.html", + {'session': session, + 'session_number': session_number, + 'slides_sp' : slides_sp, + 'form': form, + }) + @role_required('Secretariat') def make_schedule_official(request, num, owner, name): diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index a6f4f3abf..41319f287 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -12,95 +12,115 @@

{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ session.time }}{% if session.name %} : {{ session.name }}{% endif %}

{% if session.agenda_note %}

{{session.agenda_note}}

{% endif %} - {% if can_manage_materials %} - {% if session.status.slug == 'sched' or session.status.slug == 'schedw' %} -
- {% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %} - Meeting Details - {% endif %} - - Upload/Edit materials - - - 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 %} -
- {% endif %} - {% endif %} + {% if can_manage_materials %} + {% if session.status.slug == 'sched' or session.status.slug == 'schedw' %} +
+ {% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %} + Meeting Details + {% endif %} +
+ {% if not session.type_counter.agenda %} + This session does not yet have an agenda + {% endif %} + {% endif %} + {% endif %} - {% if session.filtered_artifacts %} -
-
Artifacts
-
- - {% for pres in session.filtered_artifacts %} - - - - {% endfor %} -
- {% if pres.rev %} - {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} - {% else %} - {% url 'doc_view' name=pres.document.name as url %} - {% endif %} - {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) - -
-
-
+
+
Artifacts
+
+ + {% for pres in session.filtered_artifacts %} + + {% if pres.rev %} + {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} + {% else %} + {% url 'doc_view' name=pres.document.name as url %} + {% endif %} + + {% if user|has_role:"Secretariat" or can_manage_materials %} + + {% endif %} + + {% endfor %} +
+ {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) + + {% if pres.document.type.slug == 'minutes' %} + {% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number as upload_url %} + {% elif pres.document.type.slug == 'agenda' %} + {% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number as upload_url %} + {% else %} + {% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number as upload_url %} + {% endif %} + {% if pres.document.type.slug != 'bluesheets' or user|has_role:"Secretariat" %} + Upload Revision + {% endif %} +
+ {% if can_manage_materials %} + {% if not session.type_counter.agenda %} + Upload Agenda + {% endif %} + {% if not session.type_counter.minutes %} + Upload Minutes + {% endif %} {% endif %} - {% if session.filtered_slides %} -
-
Slides
-
- - {% for pres in session.filtered_slides %} - - - - {% endfor %} -
- {% if pres.rev %} - {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} - {% else %} - {% url 'doc_view' name=pres.document.name as url %} - {% endif %} - {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) - -
-
-
+ {% if user|has_role:"Secretariat" and not session.type_counter.bluesheets %} + Upload Bluesheets {% endif %} - {% if session.filtered_drafts %} -
-
Drafts
-
- - {% for pres in session.filtered_drafts %} - - - - {% endfor %} -
- {% if pres.rev %} - {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} - {% else %} - {% url 'doc_view' name=pres.document.name as url %} - {% endif %} - {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) - -
-
-
+
+
+
+
Slides
+
+ + {% for pres in session.filtered_slides %} + + {% if pres.rev %} + {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} + {% else %} + {% url 'doc_view' name=pres.document.name as url %} + {% endif %} + + {% if can_manage_materials %} + + {% endif %} + + {% endfor %} +
+ {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) + + Upload Revision +
+ {% if can_manage_materials %} + Upload New Slides {% endif %} - +
+
+
+
Drafts +
+
+ + {% for pres in session.filtered_drafts %} + + + + {% endfor %} +
+ {% if pres.rev %} + {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} + {% else %} + {% url 'doc_view' name=pres.document.name as url %} + {% endif %} + {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) +
+ {% if can_manage_materials %} + + Link additional drafts to session + + {% endif %} +
+
{% endfor %} {% endblock %} diff --git a/ietf/templates/meeting/upload_session_agenda.html b/ietf/templates/meeting/upload_session_agenda.html new file mode 100644 index 000000000..1ddec4ca7 --- /dev/null +++ b/ietf/templates/meeting/upload_session_agenda.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin staticfiles bootstrap3 %} + +{% block title %}{% if agenda_sp %}Revise{% else %}Upload{% endif %} Agenda for {{ session.meeting }} : {{ session.group.acronym }}{% endblock %} + +{% block content %} + {% origin %} + +

{% if agenda_sp %}Revise{% else %}Upload{% endif %} Agenda 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 %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
+ + +{% endblock %} diff --git a/ietf/templates/meeting/upload_session_bluesheets.html b/ietf/templates/meeting/upload_session_bluesheets.html index 53a59f618..8698b267f 100644 --- a/ietf/templates/meeting/upload_session_bluesheets.html +++ b/ietf/templates/meeting/upload_session_bluesheets.html @@ -2,22 +2,14 @@ {# Copyright The IETF Trust 2015, All Rights Reserved #} {% load origin staticfiles bootstrap3 %} -{% block title %}Upload Bluesheets for {{ session.meeting }} : {{ session.group.acronym }}{% endblock %} +{% block title %}{% if bluesheet_sp %}Revise{% else %}Upload{% endif %} 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 bluesheet_sp %}Revise{% else %}Upload{% endif %} 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 %} diff --git a/ietf/templates/meeting/upload_session_minutes.html b/ietf/templates/meeting/upload_session_minutes.html new file mode 100644 index 000000000..ba96cf761 --- /dev/null +++ b/ietf/templates/meeting/upload_session_minutes.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin staticfiles bootstrap3 %} + +{% block title %}{% if minutes_sp %}Revise{% else %}Upload{% endif %} Minutes for {{ session.meeting }} : {{ session.group.acronym }}{% endblock %} + +{% block content %} + {% origin %} + +

{% if minutes_sp %}Revise{% else %}Upload{% endif %} Minutes 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 %} + + + {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
+ + +{% endblock %} diff --git a/ietf/templates/meeting/upload_session_slides.html b/ietf/templates/meeting/upload_session_slides.html new file mode 100644 index 000000000..92dd9af51 --- /dev/null +++ b/ietf/templates/meeting/upload_session_slides.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin staticfiles bootstrap3 %} + +{% block title %}{% if slides_sp %}Revise{% else %}Upload New{% endif %} Slides for {{ session.meeting }} : {{ session.group.acronym }}{% endblock %} + +{% block content %} + {% origin %} + +

{% if slides_sp %}Revise{% else %}Upload New{% endif %} Slides 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 slides_sp %}

{{slides_sp.document.name}}

{% endif %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
+ + +{% endblock %}