From 89a3afe025926b49c5f6c336dc0956ba735807d4 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 26 Aug 2016 21:06:01 +0000 Subject: [PATCH 1/9] Added an order field to sessionpresentation to allow ordering slides. Reworked the session details page to break slides and drafts into their own sections. Commit ready for merge. - Legacy-Id: 11888 --- .../migrations/0009_auto_20150930_0758.py | 1 + .../0036_add_order_to_sessionpresentation.py | 31 +++++++ ietf/meeting/models.py | 1 + ietf/meeting/views.py | 11 ++- ietf/templates/meeting/session_details.html | 92 ++++++++++++++----- 5 files changed, 110 insertions(+), 26 deletions(-) create mode 100644 ietf/meeting/migrations/0036_add_order_to_sessionpresentation.py diff --git a/ietf/group/migrations/0009_auto_20150930_0758.py b/ietf/group/migrations/0009_auto_20150930_0758.py index e84626a50..2071cc898 100644 --- a/ietf/group/migrations/0009_auto_20150930_0758.py +++ b/ietf/group/migrations/0009_auto_20150930_0758.py @@ -109,6 +109,7 @@ class Migration(migrations.Migration): dependencies = [ ('doc', '0010_auto_20150930_0251'), ('group', '0008_auto_20160505_0523'), + ('community', '0004_cleanup_data'), ] operations = [ diff --git a/ietf/meeting/migrations/0036_add_order_to_sessionpresentation.py b/ietf/meeting/migrations/0036_add_order_to_sessionpresentation.py new file mode 100644 index 000000000..0388eef09 --- /dev/null +++ b/ietf/meeting/migrations/0036_add_order_to_sessionpresentation.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import debug # pyflakes:ignore + +from django.db import models, migrations + +def forward(apps, schema_editor): + SessionPresentation = apps.get_model("meeting","SessionPresentation") + for sp in SessionPresentation.objects.filter(document__type__slug='slides',session__meeting__number__in=['95','96']): + sp.order = int(sp.document.name.split('-')[-1]) + sp.save() + +def reverse(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0035_auto_20160818_1610'), + ] + + operations = [ + migrations.AddField( + model_name='sessionpresentation', + name='order', + field=models.PositiveSmallIntegerField(default=0), + preserve_default=True, + ), + migrations.RunPython(forward,reverse) + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index efcddd629..e37d435de 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -959,6 +959,7 @@ class SessionPresentation(models.Model): session = models.ForeignKey('Session') document = models.ForeignKey(Document) rev = models.CharField(verbose_name="revision", max_length=16, null=True, blank=True) + order = models.PositiveSmallIntegerField(default=0) class Meta: db_table = 'meeting_session_materials' diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 94fe57d3e..42c7f0884 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1022,9 +1022,16 @@ def session_details(request, num, acronym ): else: session.time = session.status.name + session.filtered_artifacts = session.sessionpresentation_set.filter(document__type__slug__in=['agenda','minutes','bluesheets']) + session.filtered_slides = session.sessionpresentation_set.filter(document__type__slug='slides').order_by('order') + session.filtered_drafts = session.sessionpresentation_set.filter(document__type__slug='draft') # TODO FIXME Deleted materials shouldn't be in the sessionpresentation_set - 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]) + 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]) can_manage = can_manage_materials(request.user, Group.objects.get(acronym=acronym)) diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index 0a3cc674f..a6f4f3abf 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -10,6 +10,7 @@ {% for session in sessions %}

{% 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' %} @@ -33,30 +34,73 @@ {% endif %} {% endif %} - {% if session.filtered_sessionpresentation_set %} -
-
Materials:
-
- - {% for pres in session.filtered_sessionpresentation_set %} - {% if pres.document.type_id != 'bluesheets' and pres.document.type_id != 'recording' %} - - - - {% endif %} - {% 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 %}) - -
-
-
- {% 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 %}) + +
+
+
+ {% 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 %}) + +
+
+
+ {% 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 %}) + +
+
+
+ {% endif %} + {% endfor %} {% endblock %} From 53f41a6327e0a8f921aba7adf944f89d92a5d54d Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 14 Sep 2016 19:11:31 +0000 Subject: [PATCH 2/9] Added an order field to SessionPresentation. Set up removing the order field from Document. Modified the meeting materials, proceedings, and session details views to use the order field on SessionPresentation. Added bootstrapped views to upload session materials (agendas, slides, minutes, and bluesheets). Integrated upload into the session details view. Commit ready for merge. - Legacy-Id: 11989 --- ietf/doc/models.py | 2 +- ...nge_meta_options_on_sessionpresentation.py | 18 + ietf/meeting/models.py | 3 +- ietf/meeting/tests_views.py | 136 +++++++- ietf/meeting/urls.py | 3 + ietf/meeting/views.py | 311 +++++++++++++++++- ietf/templates/meeting/session_details.html | 190 ++++++----- .../meeting/upload_session_agenda.html | 22 ++ .../meeting/upload_session_bluesheets.html | 12 +- .../meeting/upload_session_minutes.html | 22 ++ .../meeting/upload_session_slides.html | 23 ++ 11 files changed, 631 insertions(+), 111 deletions(-) create mode 100644 ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py create mode 100644 ietf/templates/meeting/upload_session_agenda.html create mode 100644 ietf/templates/meeting/upload_session_minutes.html create mode 100644 ietf/templates/meeting/upload_session_slides.html diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 4937280de..96f40dc05 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -59,7 +59,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 e37d435de..f88279b94 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 42c7f0884..6ef9386e8 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -27,7 +27,9 @@ from django.forms.models import modelform_factory, inlineformset_factory from django.forms import ModelForm from django.template.loader import render_to_string from django.utils.functional import curry +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 @@ -51,7 +53,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 @@ -1006,10 +1007,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) @@ -1028,10 +1028,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)) @@ -1040,7 +1037,6 @@ def session_details(request, num, acronym ): 'meeting' :meeting , 'acronym' :acronym, 'can_manage_materials' : can_manage, - 'type_counter': type_counter, }) class SessionDraftsForm(forms.Form): @@ -1114,6 +1110,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': @@ -1138,7 +1136,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,time=doc.time,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,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]) handle_upload_file(file, filename, session.meeting, 'bluesheets') return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) @@ -1152,6 +1150,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 %} From bcac1dfff894674f82114e5b01d18c375ef1bacc Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 16 Sep 2016 20:36:59 +0000 Subject: [PATCH 3/9] Added slide sorting. Commit ready to merge. - Legacy-Id: 11995 --- ietf/meeting/tests_js.py | 42 +++++++++++++ ietf/meeting/urls.py | 1 + ietf/meeting/views.py | 27 +++++++++ ietf/templates/meeting/session_details.html | 67 +++++++++++++++++++-- 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 5714b2dab..8cae09dc4 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -7,10 +7,13 @@ from unittest import skipIf from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.core.urlresolvers import reverse as urlreverse +#from django.test.utils import override_settings import debug # pyflakes:ignore +from ietf.doc.factories import DocumentFactory from ietf.group import colors +from ietf.meeting.factories import SessionFactory from ietf.meeting.test_data import make_meeting_test_data from ietf.meeting.models import SchedTimeSessAssignment from ietf.utils.test_runner import set_coverage_checking @@ -88,6 +91,45 @@ class ScheduleEditTests(StaticLiveServerTestCase): time.sleep(0.1) # The API that modifies the database runs async self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars',schedule__name='test-agenda').count(),0) +@skipIf(skip_selenium, skip_message) +class SlideReorderTests(StaticLiveServerTestCase): + def setUp(self): + set_coverage_checking(False) + self.session = SessionFactory(meeting__type_id='ietf') + self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1) + self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2) + self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3) + self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH) + self.driver.set_window_size(1024,768) + + def absreverse(self,*args,**kwargs): + return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs)) + + def secr_login(self): + url = '%s%s'%(self.live_server_url, urlreverse('django.contrib.auth.views.login')) + self.driver.get(url) + self.driver.find_element_by_name('username').send_keys('secretary') + self.driver.find_element_by_name('password').send_keys('secretary+password') + self.driver.find_element_by_xpath('//button[@type="submit"]').click() + + #@override_settings(DEBUG=True) + def testReorderSlides(self): + return + url = self.absreverse('ietf.meeting.views.session_details', + kwargs=dict( + num=self.session.meeting.number, + acronym = self.session.group.acronym,)) + self.secr_login() + self.driver.get(url) + #debug.show('unicode(self.driver.page_source)') + second = self.driver.find_element_by_css_selector('#slides tr:nth-child(2)') + third = self.driver.find_element_by_css_selector('#slides tr:nth-child(3)') + ActionChains(self.driver).drag_and_drop(second,third).perform() + + time.sleep(0.1) # The API that modifies the database runs async + names=self.session.sessionpresentation_set.values_list('document__name',flat=True) + self.assertEqual(list(names),[u'one',u'three',u'two']) + # The following are useful debugging tools # If you add this to a LiveServerTestCase and run just this test, you can browse diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 659f124dd..0107b6c05 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -14,6 +14,7 @@ safe_for_all_meeting_types = [ 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), + url(r'^session/(?P\d+)/slides/%(name)s/order$' % settings.URL_REGEXPS, views.set_slide_order), ] diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index eeba96c99..fe465272d 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1450,6 +1450,32 @@ def upload_session_slides(request, session_id, num, name): 'form': form, }) +def set_slide_order(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 Document.objects.filter(type_id='slides',name=name).exists(): + raise Http404 + 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.") + + if request.method != 'POST' or not request.POST: + return HttpResponse(json.dumps({ 'success' : False, 'error' : 'No data submitted or not POST' }),content_type='application/json') + order_str = request.POST.get('order', None) + try: + order = int(order_str) + except ValueError: + return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json') + if order <=0 or order > 32767 : + return HttpResponse(json.dumps({ 'success' : False, 'error' : 'Supplied order is not valid' }),content_type='application/json') + + sp = session.sessionpresentation_set.get(document__name = name) + sp.order = order + sp.save() + + return HttpResponse(json.dumps({'success':True}),content_type='application/json') + @role_required('Secretariat') def make_schedule_official(request, num, owner, name): @@ -1962,3 +1988,4 @@ def proceedings_overview(request, num=None): 'meeting': meeting, 'template': template, }) + diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index 41319f287..a2bad5ee9 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -1,9 +1,15 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2015, All Rights Reserved #} -{% load origin ietf_filters %} +{% load origin ietf_filters staticfiles %} {% block title %}{{ meeting }} : {{ acronym }}{% endblock %} +{% block morecss %} + .ui-sortable tr { + cursor:pointer; + } +{% endblock %} + {% block content %} {% origin %}

{{ meeting }} : {{ acronym }}

@@ -70,11 +76,12 @@
-
Slides
+
Slides
- +
+ {% for pres in session.filtered_slides %} - + {% if pres.rev %} {% url 'doc_view' name=pres.document.name rev=pres.rev as url %} {% else %} @@ -90,6 +97,7 @@ {% endif %} {% endfor %} +
{% if can_manage_materials %} Upload New Slides @@ -124,3 +132,54 @@ {% endfor %} {% endblock %} + +{# TODO don't rely on secr/js version of jquery-ui #} +{# Sorting based loosely on the original secr upload sorting and on http://www.avtex.com/blog/2015/01/27/drag-and-drop-sorting-of-table-rows-in-priority-order/ #} +{% block js %} +{% if can_manage_materials %} + + + + + + +{% endif %} +{% endblock %} From c045f7ed739e2f6e9642eeafe9f4c60cb84d5ba3 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 14:17:52 +0000 Subject: [PATCH 4/9] improve cleanup after slide reorder test. Commit ready for merge. - Legacy-Id: 11996 --- ietf/meeting/tests_js.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 8cae09dc4..f1643eea4 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -102,6 +102,10 @@ class SlideReorderTests(StaticLiveServerTestCase): self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH) self.driver.set_window_size(1024,768) + def tearDown(self): + self.driver.close() + set_coverage_checking(True) + def absreverse(self,*args,**kwargs): return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs)) From 0f6e3f434eaff2ba3c90e1a99558b8ce1149daad Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 19:06:22 +0000 Subject: [PATCH 5/9] Closed off the old paths for materials upload (redirecting some old entry points). Commit ready for merge. - Legacy-Id: 12000 --- ietf/meeting/views.py | 6 +- ietf/secr/meetings/views.py | 3 +- ietf/secr/proceedings/forms.py | 72 --- ietf/secr/proceedings/tests.py | 67 +-- ietf/secr/proceedings/urls.py | 16 +- ietf/secr/proceedings/views.py | 456 +----------------- .../includes/buttons_submit_back.html | 6 - ietf/secr/templates/includes/slides.html | 25 - .../templates/proceedings/edit_slide.html | 38 -- ietf/secr/templates/proceedings/main.html | 2 +- .../templates/proceedings/replace_slide.html | 38 -- ietf/secr/templates/proceedings/select.html | 45 +- .../proceedings/upload_presentation.html | 34 -- .../templates/proceedings/upload_unified.html | 116 ----- 14 files changed, 46 insertions(+), 878 deletions(-) delete mode 100644 ietf/secr/templates/includes/buttons_submit_back.html delete mode 100644 ietf/secr/templates/includes/slides.html delete mode 100755 ietf/secr/templates/proceedings/edit_slide.html delete mode 100755 ietf/secr/templates/proceedings/replace_slide.html delete mode 100644 ietf/secr/templates/proceedings/upload_presentation.html delete mode 100755 ietf/secr/templates/proceedings/upload_unified.html diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index fe465272d..6bbe5ce0d 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -20,7 +20,7 @@ from django import forms from django.shortcuts import render, redirect, get_object_or_404 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.contrib import messages -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse,reverse_lazy from django.db.models import Min, Max from django.conf import settings from django.forms.models import modelform_factory, inlineformset_factory @@ -30,6 +30,7 @@ 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.views.generic import RedirectView from django.template.defaultfilters import filesizeformat from ietf.doc.fields import SearchableDocumentsField @@ -1989,3 +1990,6 @@ def proceedings_overview(request, num=None): 'template': template, }) +class OldUploadRedirect(RedirectView): + def get_redirect_url(self, **kwargs): + return reverse_lazy('ietf.meeting.views.session_details',kwargs=self.kwargs) diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index c781c2876..ad47992f0 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -25,7 +25,8 @@ from ietf.secr.meetings.blue_sheets import create_blue_sheets from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm, MeetingRoomForm, NewSessionForm, NonSessionEditForm, NonSessionForm, TimeSlotForm, UploadBlueSheetForm, get_next_slot ) -from ietf.secr.proceedings.views import build_choices, handle_upload_file +from ietf.secr.proceedings.views import build_choices +from ietf.secr.proceedings.utils import handle_upload_file from ietf.secr.sreq.forms import GroupSelectForm from ietf.secr.sreq.views import get_initial_session from ietf.secr.utils.meeting import get_session, get_timeslot diff --git a/ietf/secr/proceedings/forms.py b/ietf/secr/proceedings/forms.py index de4b48774..7243d4e10 100644 --- a/ietf/secr/proceedings/forms.py +++ b/ietf/secr/proceedings/forms.py @@ -1,11 +1,7 @@ -import os from django import forms -from django.conf import settings -from django.template.defaultfilters import filesizeformat from ietf.doc.models import Document -from ietf.name.models import DocTypeName from ietf.meeting.models import Session @@ -22,11 +18,6 @@ VALID_BLUESHEET_EXTENSIONS = ('.pdf','.jpg','.jpeg') # Forms #---------------------------------------------------------- -class EditSlideForm(forms.ModelForm): - class Meta: - model = Document - fields = ('title',) - class RecordingForm(forms.Form): external_url = forms.URLField(label='Url') session = forms.ModelChoiceField(queryset=Session.objects,empty_label='') @@ -46,66 +37,3 @@ class RecordingEditForm(forms.ModelForm): super(RecordingEditForm, self).__init__(*args, **kwargs) self.fields['external_url'].label='Url' -class ReplaceSlideForm(forms.ModelForm): - file = forms.FileField(label='Select File') - - class Meta: - model = Document - fields = ('title',) - - def clean_file(self): - file = self.cleaned_data.get('file') - ext = os.path.splitext(file.name)[1].lower() - if ext not in VALID_SLIDE_EXTENSIONS: - raise forms.ValidationError('Only these file types supported for presentation slides: %s' % ','.join(VALID_SLIDE_EXTENSIONS)) - if file._size > settings.SECR_MAX_UPLOAD_SIZE: - raise forms.ValidationError('Please keep filesize under %s. Current filesize %s' % (filesizeformat(settings.SECR_MAX_UPLOAD_SIZE), filesizeformat(file._size))) - return file - -class UnifiedUploadForm(forms.Form): - acronym = forms.CharField(widget=forms.HiddenInput()) - meeting_id = forms.CharField(widget=forms.HiddenInput()) - material_type = forms.ModelChoiceField(queryset=DocTypeName.objects.filter(slug__in=('minutes','agenda','slides','bluesheets')),empty_label=None) - slide_name = forms.CharField(label='Name of Presentation',max_length=255,required=False,help_text="For presentations only") - file = forms.FileField(label='Select File',help_text='
Note 1: You can only upload a presentation file in txt, pdf, doc, or ppt/pptx. System will not accept presentation files in any other format.

Note 2: All uploaded files will be available to the public immediately on the Preliminary Page. However, for the Proceedings, ppt/pptx files will be converted to html format and doc files will be converted to pdf format manually by the Secretariat staff.
') - - 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. Current filesize %s' % (filesizeformat(settings.SECR_MAX_UPLOAD_SIZE), filesizeformat(file._size))) - return file - - def clean(self): - super(UnifiedUploadForm, self).clean() - # if an invalid file type is supplied no file attribute will exist - if self.errors: - return self.cleaned_data - cleaned_data = self.cleaned_data - material_type = cleaned_data['material_type'] - slide_name = cleaned_data['slide_name'] - file = cleaned_data['file'] - ext = os.path.splitext(file.name)[1].lower() - - if material_type.slug == 'slides' and not slide_name: - raise forms.ValidationError('ERROR: Name of Presentaion cannot be blank') - - # only supporting PDFs per Alexa 04-05-2011 - #if material_type == 1 and not file_ext[1] == '.pdf': - # raise forms.ValidationError('Presentations must be a PDF file') - - # validate file extensions based on material type (slides,agenda,minutes,bluesheets) - # valid extensions per online documentation: meeting-materials.html - # 09-14-11 added ppt, pdf per Alexa - # 04-19-12 txt/html for agenda, +pdf for minutes per Russ - if material_type.slug == 'slides' and ext not in VALID_SLIDE_EXTENSIONS: - raise forms.ValidationError('Only these file types supported for presentation slides: %s' % ','.join(VALID_SLIDE_EXTENSIONS)) - if material_type.slug == 'agenda' and ext not in VALID_AGENDA_EXTENSIONS: - raise forms.ValidationError('Only these file types supported for agendas: %s' % ','.join(VALID_AGENDA_EXTENSIONS)) - if material_type.slug == 'minutes' and ext not in VALID_MINUTES_EXTENSIONS: - raise forms.ValidationError('Only these file types supported for minutes: %s' % ','.join(VALID_MINUTES_EXTENSIONS)) - if material_type.slug == 'bluesheets' and ext not in VALID_BLUESHEET_EXTENSIONS: - raise forms.ValidationError('Only these file types supported for bluesheets: %s' % ','.join(VALID_BLUESHEET_EXTENSIONS)) - - return cleaned_data - - diff --git a/ietf/secr/proceedings/tests.py b/ietf/secr/proceedings/tests.py index 62a347c13..448934fa2 100644 --- a/ietf/secr/proceedings/tests.py +++ b/ietf/secr/proceedings/tests.py @@ -2,20 +2,19 @@ import debug # pyflakes:ignore import os import shutil -from StringIO import StringIO - -from django.core.urlresolvers import reverse from django.conf import settings +from django.core.urlresolvers import reverse -from ietf.doc.models import Document from ietf.group.models import Group -from ietf.meeting.models import Meeting, Session +from ietf.meeting.models import Session from ietf.meeting.test_data import make_meeting_test_data from ietf.utils.test_data import make_test_data -from ietf.utils.test_utils import TestCase, unicontent +from ietf.utils.test_utils import TestCase from ietf.name.models import SessionStatusName -from ietf.secr.utils.meeting import get_proceedings_path +from ietf.meeting.factories import SessionFactory + +from ietf.secr.proceedings.proc_utils import create_proceedings SECR_USER='secretary' @@ -64,47 +63,23 @@ class RecordingTestCase(TestCase): self.assertEqual(response.status_code, 200) self.failUnless(external_url in response.content) - -class BluesheetTestCase(TestCase): +class OldProceedingsTestCase(TestCase): + ''' Ensure coverage of fragments of old proceedings generation until those are removed ''' def setUp(self): + self.session = SessionFactory(meeting__type_id='ietf') self.proceedings_dir = os.path.abspath("tmp-proceedings-dir") - if not os.path.exists(self.proceedings_dir): - os.mkdir(self.proceedings_dir) + + # This unintuitive bit is a consequence of the surprising implementation of meeting.get_materials_path self.saved_agenda_path = settings.AGENDA_PATH - settings.AGENDA_PATH = self.proceedings_dir - - self.interim_listing_dir = os.path.abspath("tmp-interim-listing-dir") - if not os.path.exists(self.interim_listing_dir): - os.mkdir(self.interim_listing_dir) - self.saved_secr_interim_listing_dir = settings.SECR_INTERIM_LISTING_DIR - settings.SECR_INTERIM_LISTING_DIR = self.interim_listing_dir - + settings.AGENDA_PATH= self.proceedings_dir + + target_path = self.session.meeting.get_materials_path() + if not os.path.exists(target_path): + os.makedirs(target_path) + def tearDown(self): - settings.AGENDA_PATH = self.saved_agenda_path shutil.rmtree(self.proceedings_dir) - settings.SECR_INTERIM_LISTING_DIR = self.saved_secr_interim_listing_dir - shutil.rmtree(self.interim_listing_dir) - - def test_upload(self): - make_meeting_test_data() - meeting = Meeting.objects.filter(type='interim',session__status='sched').first() - #self.assertTrue(meeting) - group = Group.objects.get(acronym='mars') - #Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched',type_id='session') - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':'mars'}) - upfile = StringIO('dummy file') - upfile.name = "scan1.pdf" - self.client.login(username="marschairman", password="marschairman+password") - r = self.client.post(url, - dict(acronym='mars',meeting_id=meeting.id,material_type='bluesheets',file=upfile),follow=True) - self.assertEqual(r.status_code, 200) - doc = Document.objects.get(type='bluesheets') - self.failUnless(doc.external_url in unicontent(r)) - self.failUnless(os.path.exists(os.path.join(doc.get_file_path(),doc.external_url))) - # test that proceedings has bluesheets on it - path = get_proceedings_path(meeting,group) - self.failUnless(os.path.exists(path)) - with open(path) as f: - data = f.read() - self.failUnless(doc.external_url.encode('utf-8') in data) - + settings.AGENDA_PATH = self.saved_agenda_path + + def test_old_generate(self): + create_proceedings(self.session.meeting,self.session.group,is_final=True) diff --git a/ietf/secr/proceedings/urls.py b/ietf/secr/proceedings/urls.py index 56369142f..d931f8aa8 100644 --- a/ietf/secr/proceedings/urls.py +++ b/ietf/secr/proceedings/urls.py @@ -1,26 +1,16 @@ from django.conf.urls import patterns, url from django.conf import settings +from ietf.meeting.views import OldUploadRedirect urlpatterns = patterns('ietf.secr.proceedings.views', url(r'^$', 'main', name='proceedings'), url(r'^ajax/generate-proceedings/(?P\d{1,3})/$', 'ajax_generate_proceedings', name='proceedings_ajax_generate_proceedings'), - url(r'^ajax/order-slide/$', 'ajax_order_slide', name='proceedings_ajax_order_slide'), # special offline URL for testing proceedings build - url(r'^build/(?P\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/(?P[-a-z0-9]+)/$', - 'build', name='proceedings_build'), - url(r'^delete/(?P[A-Za-z0-9._\-\+]+)/$', 'delete_material', name='proceedings_delete_material'), - url(r'^edit-slide/(?P[A-Za-z0-9._\-\+]+)/$', 'edit_slide', name='proceedings_edit_slide'), - url(r'^move-slide/(?P[A-Za-z0-9._\-\+]+)/(?P(up|down))/$', - 'move_slide', name='proceedings_move_slide'), url(r'^process-pdfs/(?P\d{1,3})/$', 'process_pdfs', name='proceedings_process_pdfs'), url(r'^progress-report/(?P\d{1,3})/$', 'progress_report', name='proceedings_progress_report'), - url(r'^replace-slide/(?P[A-Za-z0-9._\-\+]+)/$', 'replace_slide', name='proceedings_replace_slide'), url(r'^(?P\d{1,3})/$', 'select', name='proceedings_select'), url(r'^(?P\d{1,3})/recording/$', 'recording', name='proceedings_recording'), url(r'^(?P\d{1,3})/recording/edit/(?P[A-Za-z0-9_\-\+]+)$', 'recording_edit', name='proceedings_recording_edit'), - # NOTE: we have two entries here which both map to upload_unified, passing session_id or acronym - url(r'^(?P\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/(?P\d{1,6})/$', - 'upload_unified', name='proceedings_upload_unified'), - url(r'^(?P\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/%(acronym)s/$' % settings.URL_REGEXPS, - 'upload_unified', name='proceedings_upload_unified'), + url(r'^(?P\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/%(acronym)s/$' % settings.URL_REGEXPS, + OldUploadRedirect.as_view()), ) diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py index 7ddfad4e1..184c01846 100644 --- a/ietf/secr/proceedings/views.py +++ b/ietf/secr/proceedings/views.py @@ -8,31 +8,25 @@ import debug # pyflakes:ignore from django.conf import settings from django.contrib import messages -from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Max from django.http import HttpResponseRedirect from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext -from django.utils.text import slugify -from ietf.secr.lib.template import jsonapi -from ietf.secr.sreq.forms import GroupSelectForm -from ietf.secr.utils.decorators import check_permissions, sec_only -from ietf.secr.utils.document import get_full_path -from ietf.secr.utils.group import get_my_groups, groups_by_session -from ietf.secr.utils.meeting import get_materials, get_timeslot, get_proceedings_path, get_proceedings_url -from ietf.doc.models import Document, DocAlias, DocEvent, State, NewRevisionDocEvent +from ietf.secr.utils.decorators import sec_only +from ietf.secr.utils.group import get_my_groups +from ietf.secr.utils.meeting import get_timeslot, get_proceedings_url +from ietf.doc.models import Document, DocEvent from ietf.group.models import Group from ietf.person.models import Person from ietf.ietfauth.utils import has_role, role_required -from ietf.meeting.models import Meeting, Session, TimeSlot, SchedTimeSessAssignment +from ietf.meeting.models import Meeting, Session, TimeSlot -from ietf.secr.proceedings.forms import EditSlideForm, RecordingForm, RecordingEditForm, ReplaceSlideForm, UnifiedUploadForm +from ietf.secr.proceedings.forms import RecordingForm, RecordingEditForm 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 # ------------------------------------------------- @@ -226,132 +220,10 @@ def ajax_generate_proceedings(request, meeting_num): RequestContext(request,{}), ) -@jsonapi -def ajax_order_slide(request): - ''' - Ajax function to change the order of presentation slides. - This function expects a POST request with the following parameters - order: new order of slide, 0 based - slide_name: slide primary key (name) - ''' - if request.method != 'POST' or not request.POST: - return { 'success' : False, 'error' : 'No data submitted or not POST' } - slide_name = request.POST.get('slide_name',None) - order = request.POST.get('order',None) - slide = get_object_or_404(Document, name=slide_name) - - # get all the slides for this session - session = slide.session_set.all()[0] - qs = session.materials.exclude(states__slug='deleted').filter(type='slides').order_by('order') - - # move slide and reorder list - slides = list(qs) - index = slides.index(slide) - slides.pop(index) - slides.insert(int(order),slide) - for ord,item in enumerate(slides,start=1): - if item.order != ord: - item.order = ord - item.save() - - return {'success':True,'order':order,'slide':slide_name} - # -------------------------------------------------- # STANDARD VIEW FUNCTIONS # -------------------------------------------------- -@role_required('Secretariat') -def build(request,meeting_num,acronym): - ''' - This is a utility or test view. It simply rebuilds the proceedings html for the specified - meeting / group. - ''' - meeting = Meeting.objects.get(number=meeting_num) - group = get_object_or_404(Group,acronym=acronym) - create_proceedings(meeting,group,is_final=True) - - messages.success(request,'proceedings.html was rebuilt') - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting_num,'acronym':acronym}) - return HttpResponseRedirect(url) - -@check_permissions -def delete_material(request,slide_id): - ''' - This view handles deleting meeting materials. We don't actually delete the - document object but set the state to deleted and add a 'deleted' DocEvent. - ''' - doc = get_object_or_404(Document, name=slide_id) - # derive other objects - session = doc.session_set.all()[0] - meeting = session.meeting - group = session.group - - path = get_full_path(doc) - if path and os.path.exists(path): - os.remove(path) - - # leave it related - #session.materials.remove(doc) - - state = State.objects.get(type=doc.type,slug='deleted') - doc.set_state(state) - - # create deleted_document - e = DocEvent.objects.create(doc=doc, - by=request.user.person, - type='deleted', - desc="State set to deleted") - - doc.save_with_history([e]) - - create_proceedings(meeting,group) - - messages.success(request,'The material was deleted successfully') - if group.type.slug in ('wg','rg'): - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym}) - else: - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id}) - - return HttpResponseRedirect(url) - -@check_permissions -def edit_slide(request, slide_id): - ''' - This view allows the user to edit the name of a slide. - ''' - slide = get_object_or_404(Document, name=slide_id) - # derive other objects - session = slide.session_set.all()[0] - meeting = session.meeting - group = session.group - - if group.type.slug in ('wg','rg'): - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym}) - else: - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id}) - - if request.method == 'POST': # If the form has been submitted... - button_text = request.POST.get('submit', '') - if button_text == 'Cancel': - return HttpResponseRedirect(url) - - form = EditSlideForm(request.POST, instance=slide) # A form bound to the POST data - if form.is_valid(): - form.save() - - # rebuild proceedings.html - create_proceedings(meeting,group) - return HttpResponseRedirect(url) - else: - form = EditSlideForm(instance=slide) - - return render_to_response('proceedings/edit_slide.html',{ - 'group': group, - 'meeting':meeting, - 'slide':slide, - 'form':form}, - RequestContext(request, {}), - ) @role_required(*AUTHORIZED_ROLES) def main(request): @@ -391,42 +263,6 @@ def main(request): RequestContext(request,{}), ) -@check_permissions -def move_slide(request, slide_id, direction): - ''' - This view will re-order slides. In addition to meeting, group and slide IDs it takes - a direction argument which is a string [up|down]. - ''' - slide = get_object_or_404(Document, name=slide_id) - - # derive other objects - session = slide.session_set.all()[0] - meeting = session.meeting - group = session.group - qs = session.materials.exclude(states__slug='deleted').filter(type='slides').order_by('order') - - # if direction is up and we aren't already the first slide - if direction == 'up' and slide_id != str(qs[0].pk): - index = find_index(slide_id, qs) - slide_before = qs[index-1] - slide_before.order, slide.order = slide.order, slide_before.order - slide.save() - slide_before.save() - - # if direction is down, more than one slide and we aren't already the last slide - if direction == 'down' and qs.count() > 1 and slide_id != str(qs[qs.count()-1].pk): - index = find_index(slide_id, qs) - slide_after = qs[index+1] - slide_after.order, slide.order = slide.order, slide_after.order - slide.save() - slide_after.save() - - if group.type.slug in ('wg','rg'): - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym}) - else: - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id}) - return HttpResponseRedirect(url) - @sec_only def process_pdfs(request, meeting_num): ''' @@ -554,88 +390,14 @@ def recording_edit(request, meeting_num, name): RequestContext(request, {}), ) -@check_permissions -def replace_slide(request, slide_id): - ''' - This view allows the user to upload a new file to replace a slide. - ''' - slide = get_object_or_404(Document, name=slide_id) - # derive other objects - session = slide.session_set.all()[0] - meeting = session.meeting - group = session.group - - if group.type.slug in ('wg','rg'): - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym}) - else: - url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id}) - - if request.method == 'POST': # If the form has been submitted... - button_text = request.POST.get('submit', '') - if button_text == 'Cancel': - return HttpResponseRedirect(url) - - form = ReplaceSlideForm(request.POST,request.FILES,instance=slide) # A form bound to the POST data - if form.is_valid(): - new_slide = form.save(commit=False) - new_slide.time = datetime.datetime.now() - - file = request.FILES[request.FILES.keys()[0]] - file_ext = os.path.splitext(file.name)[1] - disk_filename = new_slide.name + file_ext - handle_upload_file(file,disk_filename,meeting,'slides') - - new_slide.external_url = disk_filename - - # create DocEvent uploaded - e = DocEvent.objects.create(doc=slide, - by=request.user.person, - type='uploaded', - desc="Uploaded") - new_slide.save_with_history([e]) - - post_process(new_slide) - - # rebuild proceedings.html - create_proceedings(meeting,group) - - return HttpResponseRedirect(url) - else: - form = ReplaceSlideForm(instance=slide) - - return render_to_response('proceedings/replace_slide.html',{ - 'group': group, - 'meeting':meeting, - 'slide':slide, - 'form':form}, - RequestContext(request, {}), - ) - -@role_required(*AUTHORIZED_ROLES) +# TODO - should probably rename this since it's not selecting groups anymore +@role_required('Secretariat') def select(request, meeting_num): ''' - A screen to select which group you want to upload material for. Users of this view area - Secretariat staff and community (WG Chairs, ADs, etc). Only those groups with sessions - scheduled for the given meeting will appear in drop-downs. For Group and IRTF selects, the - value will be group.acronym to use in pretty URLs. Since Training sessions have no acronym - we'll use the session id. + Provide the secretariat only functions related to meeting materials management ''' - if request.method == 'POST': - if request.POST.get('group',None): - redirect_url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting_num,'acronym':request.POST['group']}) - return HttpResponseRedirect(redirect_url) - else: - messages.error(request, 'No Group selected') - meeting = get_object_or_404(Meeting, number=meeting_num) - user = request.user - try: - person = user.person - except ObjectDoesNotExist: - messages.warning(request, 'The account %s is not associated with any groups. If you have multiple Datatracker accounts you may try another or report a problem to ietf-action@ietf.org' % request.user) - return HttpResponseRedirect(reverse('proceedings')) - groups_session, groups_no_session = groups_by_session(user, meeting) proceedings_url = get_proceedings_url(meeting) # get the time proceedings were generated @@ -645,57 +407,13 @@ def select(request, meeting_num): else: last_run = None - # initialize group form - wgs = filter(lambda x: x.type_id in ('wg','ag','team'),groups_session) - group_form = GroupSelectForm(choices=build_choices(wgs)) - - # intialize IRTF form, only show if user is sec or irtf chair - if has_role(user,'Secretariat') or person.role_set.filter(name__slug='chair',group__type__slug__in=('irtf','rg')): - rgs = filter(lambda x: x.type_id == 'rg',groups_session) - irtf_form = GroupSelectForm(choices=build_choices(rgs)) - else: - irtf_form = None - - # initialize Training form, this select widget needs to have a session id, because - # it's utilmately the session that we associate material with - other_groups = filter(lambda x: x.type_id not in ('wg','ag','rg'),groups_session) - if other_groups: - add_choices = [] - sessions = Session.objects.filter(meeting=meeting,group__in=other_groups) - for session in sessions: - if session.name.lower().find('plenary') != -1: - continue - if session.name: - name = (session.name[:75] + '..') if len(session.name) > 75 else session.name - add_choices.append((session.id,name)) - else: - add_choices.append((session.id,session.group.name)) - choices = sorted(add_choices,key=lambda x: x[1]) - training_form = GroupSelectForm(choices=choices) - else: - training_form = None - - # iniialize plenary form - if has_role(user,['Secretariat','IETF Chair','IETF Trust Chair','IAB Chair','IAOC Chair','IAD']): - ss = SchedTimeSessAssignment.objects.filter(schedule=meeting.agenda,timeslot__type='plenary') - choices = [ (i.session.id, i.session.name) for i in sorted(ss,key=lambda x: x.session.name) ] - plenary_form = GroupSelectForm(choices=choices) - else: - plenary_form = None - # count PowerPoint files waiting to be converted - if has_role(user,'Secretariat'): - ppt = Document.objects.filter(session__meeting=meeting,type='slides',external_url__endswith='.ppt').exclude(states__slug='deleted') - pptx = Document.objects.filter(session__meeting=meeting,type='slides',external_url__endswith='.pptx').exclude(states__slug='deleted') - ppt_count = ppt.count() + pptx.count() - else: - ppt_count = 0 + # TODO : This should look at SessionPresentation instead + ppt = Document.objects.filter(session__meeting=meeting,type='slides',external_url__endswith='.ppt').exclude(states__slug='deleted') + pptx = Document.objects.filter(session__meeting=meeting,type='slides',external_url__endswith='.pptx').exclude(states__slug='deleted') + ppt_count = ppt.count() + pptx.count() return render_to_response('proceedings/select.html', { - 'group_form': group_form, - 'irtf_form': irtf_form, - 'training_form': training_form, - 'plenary_form': plenary_form, 'meeting': meeting, 'last_run': last_run, 'proceedings_url': proceedings_url, @@ -703,151 +421,3 @@ def select(request, meeting_num): RequestContext(request,{}), ) -@check_permissions -def upload_unified(request, meeting_num, acronym=None, session_id=None): - ''' - This view is the main view for uploading / re-ordering material for regular and interim - meetings. There are two urls.py entries which map to this view. The acronym_id option is used - most often for groups of regular and interim meetings. session_id is used for uploading - material for Training sessions (where group is not a unique identifier). We could have used - session_id all the time but this makes for an ugly URL which most of the time would be - avoided by using acronym. - ''' - def redirection_back(meeting, group): - if meeting.type.slug == 'interim': - url = reverse('proceedings') - else: - url = reverse('proceedings_select', kwargs={'meeting_num':meeting.number}) - return HttpResponseRedirect(url) - - meeting = get_object_or_404(Meeting, number=meeting_num) - now = datetime.datetime.now() - if acronym: - group = get_object_or_404(Group, acronym=acronym) - sessions = Session.objects.filter(meeting=meeting,group=group) - if not sessions.exists(): - meeting_name = "IETF %s"%meeting.number if meeting.number.isdigit() else meeting.number - messages.warning(request, 'There does not seem to be a %s session in %s.' % (group.acronym, meeting_name)) - return redirection_back(meeting, group) - session = sessions[0] - session_name = '' - elif session_id: - session = get_object_or_404(Session, id=int(session_id)) - sessions = [session] - group = session.group - session_name = session.name - - if request.method == 'POST': - button_text = request.POST.get('submit','') - if button_text == 'Back': - return redirection_back(meeting, group) - form = UnifiedUploadForm(request.POST,request.FILES) - if form.is_valid(): - material_type = form.cleaned_data['material_type'] - slide_name = form.cleaned_data['slide_name'] - - file = request.FILES[request.FILES.keys()[0]] - file_ext = os.path.splitext(file.name)[1] - - # set the filename - if meeting.type.slug == 'ietf': - filename = '%s-%s-%s' % (material_type.slug,meeting.number,group.acronym) - elif meeting.type.slug == 'interim': - filename = '%s-%s' % (material_type.slug,meeting.number) - - # NonSession material, use short name for shorter URLs - if session.short: - filename += "-%s" % session.short - elif session_name: - filename += "-%s" % slugify(session_name) - # -------------------------------- - - if material_type.slug == 'slides': - order_num = get_next_order_num(session) - slide_num = get_next_slide_num(session) - filename += "-%s" % slide_num - - disk_filename = filename + file_ext - - # create the Document object, in the case of slides the name will always be unique - # so you'll get a new object, agenda and minutes will reuse doc object if it exists - doc, created = Document.objects.get_or_create(type=material_type, - group=group, - name=filename) - doc.external_url = disk_filename - doc.time = now - if created: - doc.rev = '1' - else: - doc.rev = str(int(doc.rev) + 1) - if material_type.slug == 'slides': - doc.order=order_num - if slide_name: - doc.title = slide_name - else: - doc.title = doc.name - else: - doc.title = '%s for %s at %s' % (material_type.slug.capitalize(), group.acronym.upper(), meeting) - - DocAlias.objects.get_or_create(name=doc.name, document=doc) - - handle_upload_file(file,disk_filename,meeting,material_type.slug) - - # set Doc state - if doc.type.slug=='slides': - doc.set_state(State.objects.get(type=doc.type,slug='archived')) - doc.set_state(State.objects.get(type='reuse_policy',slug='single')) - else: - doc.set_state(State.objects.get(type=doc.type,slug='active')) - - # create session relationship, per Henrik we should associate documents to all sessions - # for the current meeting (until tools support different materials for diff sessions) - for s in sessions: - try: - sp = s.sessionpresentation_set.get(document=doc) - sp.rev = doc.rev - sp.save() - except ObjectDoesNotExist: - s.sessionpresentation_set.create(document=doc,rev=doc.rev) - - # create NewRevisionDocEvent instead of uploaded, per Ole - e = NewRevisionDocEvent.objects.create(type='new_revision', - by=request.user.person, - doc=doc, - rev=doc.rev, - desc='New revision available') - - doc.save_with_history([e]) - - post_process(doc) - create_proceedings(meeting,group) - messages.success(request,'File uploaded sucessfully') - - else: - form = UnifiedUploadForm(initial={'meeting_id':meeting.id,'acronym':group.acronym,'material_type':'slides'}) - - materials = get_materials(group,meeting) - - # gather DocEvents - # include deleted material to catch deleted doc events - #docs = session.materials.all() - # Don't report on draft DocEvents since the secr/materials app isn't managing them - docs = session.materials.exclude(type='draft') - docevents = DocEvent.objects.filter(doc__in=docs) - - path = get_proceedings_path(meeting,group) - if os.path.exists(path): - proceedings_url = get_proceedings_url(meeting,group) - else: - proceedings_url = '' - - return render_to_response('proceedings/upload_unified.html', { - 'docevents': docevents, - 'meeting': meeting, - 'group': group, - 'materials': materials, - 'form': form, - 'session_name': session_name, # for Tutorials, etc - 'proceedings_url': proceedings_url}, - RequestContext(request, {}), - ) diff --git a/ietf/secr/templates/includes/buttons_submit_back.html b/ietf/secr/templates/includes/buttons_submit_back.html deleted file mode 100644 index 9b3810731..000000000 --- a/ietf/secr/templates/includes/buttons_submit_back.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
    -
  • -
  • -
-
diff --git a/ietf/secr/templates/includes/slides.html b/ietf/secr/templates/includes/slides.html deleted file mode 100644 index 71146bef6..000000000 --- a/ietf/secr/templates/includes/slides.html +++ /dev/null @@ -1,25 +0,0 @@ -{% load ams_filters %} - - - - - - - - - - - - - {% for slide in materials.slides %} - - - - - - - - - {% endfor %} - -
SlideFilenameEditReplaceDelete
{{ slide.title }}{{ slide.external_url }}{% if slide.external_url|is_ppt %} *{% endif %}EditReplaceDelete
diff --git a/ietf/secr/templates/proceedings/edit_slide.html b/ietf/secr/templates/proceedings/edit_slide.html deleted file mode 100755 index 2dde4a153..000000000 --- a/ietf/secr/templates/proceedings/edit_slide.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "base_site.html" %} -{% load staticfiles %} -{% block title %}Edit Slide{% endblock %} - -{% block extrahead %}{{ block.super }} - -{% endblock %} - -{% block breadcrumbs %}{{ block.super }} - {% if meeting.type_id == "interim" %} - » Proceedings - » {{ meeting }} - » {{ slide.title }} - {% else %} - » Proceedings - » Select - » {{ group.acronym }} - » {{ slide.title }} - {% endif %} - -{% endblock %} - -{% block content %} - -
-

Working Group - {{ group.acronym }}

-

Edit Slide:

-
{% csrf_token %} - - {{ form.as_table }} -
- - {% include "includes/buttons_save_cancel.html" %} - -
-
- -{% endblock %} diff --git a/ietf/secr/templates/proceedings/main.html b/ietf/secr/templates/proceedings/main.html index 88d2064e8..8cc612110 100644 --- a/ietf/secr/templates/proceedings/main.html +++ b/ietf/secr/templates/proceedings/main.html @@ -54,7 +54,7 @@ {{ meeting.group.acronym }} - {{ meeting.date }} + {{ meeting.date }} {% endfor %} diff --git a/ietf/secr/templates/proceedings/replace_slide.html b/ietf/secr/templates/proceedings/replace_slide.html deleted file mode 100755 index 8dc27f8aa..000000000 --- a/ietf/secr/templates/proceedings/replace_slide.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "base_site.html" %} -{% load staticfiles %} -{% block title %}Replace Slide{% endblock %} - -{% block extrahead %}{{ block.super }} - -{% endblock %} - -{% block breadcrumbs %}{{ block.super }} - {% if meeting.type_id == "interim" %} - » Proceedings - » {{ meeting }} - » {{ slide.title }} - {% else %} - » Proceedings - » Select - » {{ group.acronym }} - » {{ slide.title }} - {% endif %} - -{% endblock %} - -{% block content %} - -
-

Working Group - {{ group.acronym }}

-

Replace Slide:

-
{% csrf_token %} - - {{ form.as_table }} -
- - {% include "includes/buttons_save_cancel.html" %} - -
-
- -{% endblock %} diff --git a/ietf/secr/templates/proceedings/select.html b/ietf/secr/templates/proceedings/select.html index a22e66eaa..64b4ffcaf 100755 --- a/ietf/secr/templates/proceedings/select.html +++ b/ietf/secr/templates/proceedings/select.html @@ -29,7 +29,7 @@ {% block breadcrumbs %}{{ block.super }} » Proceedings - » {{ meeting.number }} - Select Group + » {{ meeting.number }} {% endblock %} {% block instructions %} @@ -39,49 +39,7 @@ {% block content %}
-

IETF {{ meeting.number }} - Upload Material

- - {% if user|has_role:"Secretariat" %} - {% endif %}
diff --git a/ietf/secr/templates/proceedings/upload_presentation.html b/ietf/secr/templates/proceedings/upload_presentation.html deleted file mode 100644 index 760ac4ef4..000000000 --- a/ietf/secr/templates/proceedings/upload_presentation.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "base_site.html" %} -{% load staticfiles %} -{% block title %}Proceedings - Upload Presentations {% endblock %} - -{% block extrahead %}{{ block.super }} - -{% endblock %} - -{% block breadcrumbs %}{{ block.super }} - » Proceedings - » {{ meeting.meeting_num }} - » Convert Material - » Upload Presentation -{% endblock %} - -{% block content %} -
-
{% csrf_token %} -

Proceedings - Upload (HTML-Zipped) Presentation : {{ slide.group_name }}

-

For Presentation : {{ slide.slide_name }}

- - {{ upload_presentation.as_table }} -
-
-
    -
  • - -
-
-
-
- -{% endblock %} - diff --git a/ietf/secr/templates/proceedings/upload_unified.html b/ietf/secr/templates/proceedings/upload_unified.html deleted file mode 100755 index 3f318b76c..000000000 --- a/ietf/secr/templates/proceedings/upload_unified.html +++ /dev/null @@ -1,116 +0,0 @@ -{% extends "base_site.html" %} -{% load ietf_filters %} -{% load staticfiles %} -{% block title %}Proceedings{% endblock %} - -{% block extrastyle %}{{ block.super }} - -{% endblock %} - -{% block extrahead %}{{ block.super }} - - -{% endblock %} - -{% block breadcrumbs %}{{ block.super }} - - {% if meeting.type_id == "interim" %} - » Proceedings - » {{ meeting }} - {% else %} - » Proceedings - » {{ meeting }} - » {% if session_name %}{{ session_name }}{% else %}{{ group.acronym }}{% endif %} - {% endif %} - -{% endblock %} - -{% block content %} - -
-

{{ meeting }} - Upload Material - {% if session_name %}{{ session_name }}{% else %}Group: {{ group.acronym }}{% endif %} -

- - - - - - - - - - - {% if proceedings_url %} - - - {% else %} - - - {% endif %} - - - - {% if materials.minutes %} - - - - {% else %} - - - - {% endif %} - - - {% if materials.agenda %} - - - - {% else %} - - - - {% endif %} - - {% if materials.bluesheets %} - {% for item in materials.bluesheets %} - - - - - - {% endfor %} - {% endif %} - -
TypeFilenameDelete
Proceedingsproceedings.htmlProceedingsProceedings not yet generated
Minutes{{ materials.minutes.external_url }}DeleteMinutes(not uploaded)
Agenda{{ materials.agenda.external_url }}DeleteAgenda(not uploaded)
Bluesheet{{ item.external_url }}{% if user|has_role:"Secretariat" %}Delete{% endif %}
- - - - -
- -{% if docevents %} -
-
- {% include "includes/docevents.html" %} -
-{% endif %} - -{% endblock %} - -{% block footer-extras %} - {% include "includes/upload_footer.html" %} -{% endblock %} From e24281cfacbfd99614681c9b996ffdf4b7a69810 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 20:02:40 +0000 Subject: [PATCH 6/9] Split links on the session detail page so that the title goes straight to the document content for non-draft documents. Commit ready for merge. - Legacy-Id: 12001 --- ietf/templates/meeting/session_details.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index a2bad5ee9..a08b4c8f6 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -43,7 +43,8 @@ {% url 'doc_view' name=pres.document.name as url %} {% endif %} - {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) + {{pres.document.title}} + ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) {% if user|has_role:"Secretariat" or can_manage_materials %} @@ -88,7 +89,8 @@ {% url 'doc_view' name=pres.document.name as url %} {% endif %} - {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) + {{pres.document.title}} + ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) {% if can_manage_materials %} From 6eda94db769182fc46644cb69017cc2900058b96 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 20:20:15 +0000 Subject: [PATCH 7/9] Use anchors on session_details to let the browser go directly to the particular session selected from meeting_materials. This is paricularly useful for the plenary. Commit ready for merge. - Legacy-Id: 12002 --- ietf/templates/meeting/edit_materials_button.html | 2 +- ietf/templates/meeting/session_details.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ietf/templates/meeting/edit_materials_button.html b/ietf/templates/meeting/edit_materials_button.html index ee4c124c0..07e1c494a 100644 --- a/ietf/templates/meeting/edit_materials_button.html +++ b/ietf/templates/meeting/edit_materials_button.html @@ -1,4 +1,4 @@ {% load ietf_filters session_filters %} {% if user|has_role:"Secretariat" or session|can_manage_materials:user and not session.is_material_submission_cutoff %} -Edit +Edit {% endif %} diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index a08b4c8f6..b712b5520 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -15,7 +15,7 @@

{{ meeting }} : {{ acronym }}

{% for session in sessions %} -

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

+

{% 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 %} From 3ef256b95960af21bcdb6ef930d21f9eac2d9ebe Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 20:32:32 +0000 Subject: [PATCH 8/9] Fixed an rm that should have been an svn rm. Commit ready for merge. - Legacy-Id: 12003 --- ietf/secr/templates/includes/docevents.html | 25 --------------------- 1 file changed, 25 deletions(-) delete mode 100644 ietf/secr/templates/includes/docevents.html diff --git a/ietf/secr/templates/includes/docevents.html b/ietf/secr/templates/includes/docevents.html deleted file mode 100644 index d5c434ac4..000000000 --- a/ietf/secr/templates/includes/docevents.html +++ /dev/null @@ -1,25 +0,0 @@ -

Activies Log

- From efa322831410d1bdc405e446dd63978b94be15d8 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 19 Sep 2016 22:05:59 +0000 Subject: [PATCH 9/9] Added the ability to remove documents from a session to session_details. Made slide upload code more robust against removing a set of slides from a session and then revising them. Commit ready for merge. - Legacy-Id: 12004 --- ietf/meeting/tests_views.py | 23 +++++++++++ ietf/meeting/urls.py | 1 + ietf/meeting/views.py | 39 ++++++++++++++----- .../meeting/remove_sessionpresentation.html | 31 +++++++++++++++ ietf/templates/meeting/session_details.html | 6 +++ 5 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 ietf/templates/meeting/remove_sessionpresentation.html diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 66badd226..46de15d76 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -1451,3 +1451,26 @@ class MaterialsTests(TestCase): self.assertEqual(sp.rev,u'01') self.assertEqual(sp.document.rev,u'01') + def test_remove_sessionpresentation(self): + session = SessionFactory(meeting__type_id='ietf') + doc = DocumentFactory(type_id='slides') + session.sessionpresentation_set.create(document=doc) + + url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':session.id,'name':'no-such-doc'}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':0,'name':doc.name}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':session.id,'name':doc.name}) + login_testing_unauthorized(self,"secretary",url) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + self.assertEqual(1,session.sessionpresentation_set.count()) + response = self.client.post(url,{'remove_session':''}) + self.assertEqual(response.status_code, 302) + self.assertEqual(0,session.sessionpresentation_set.count()) + self.assertEqual(2,doc.docevent_set.count()) diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 0107b6c05..295ddb410 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -15,6 +15,7 @@ safe_for_all_meeting_types = [ 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), url(r'^session/(?P\d+)/slides/%(name)s/order$' % settings.URL_REGEXPS, views.set_slide_order), + url(r'^session/(?P\d+)/doc/%(name)s/remove$' % settings.URL_REGEXPS, views.remove_sessionpresentation), ] diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 6bbe5ce0d..654bf275f 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -38,7 +38,7 @@ from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent from ietf.group.models import Group from ietf.group.utils import can_manage_materials from ietf.ietfauth.utils import role_required, has_role -from ietf.meeting.models import Meeting, Session, Schedule, Room, FloorPlan +from ietf.meeting.models import Meeting, Session, Schedule, Room, FloorPlan, SessionPresentation from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name from ietf.meeting.helpers import build_all_agenda_slices, get_wg_name_list from ietf.meeting.helpers import get_all_assignments_from_schedule @@ -1414,16 +1414,21 @@ def upload_session_slides(request, session_id, num, name): 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', - ) + if Document.objects.filter(name=name).exists(): + doc = Document.objects.get(name=name) + doc.rev = '%02d' % (int(doc.rev)+1) + doc.title = form.cleaned_data['title'] + else: + doc = Document.objects.create( + name = name, + type_id = 'slides', + title = title, + group = session.group, + rev = '00', + ) + doc.docalias_set.create(name=doc.name) 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: @@ -1451,6 +1456,22 @@ def upload_session_slides(request, session_id, num, name): 'form': form, }) +def remove_sessionpresentation(request, session_id, num, name): + sp = get_object_or_404(SessionPresentation,session_id=session_id,document__name=name) + session = sp.session + if not session.can_manage_materials(request.user): + return HttpResponseForbidden("You don't have permission to manage materials 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.") + if request.method == 'POST': + session.sessionpresentation_set.filter(pk=sp.pk).delete() + c = DocEvent(type="added_comment", doc=sp.document, by=request.user.person) + c.desc = "Removed from session: %s" % (session) + c.save() + return redirect('ietf.meeting.views.session_details', num=session.meeting.number, acronym=session.group.acronym) + + return render(request,'meeting/remove_sessionpresentation.html', {'sp': sp }) + def set_slide_order(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) diff --git a/ietf/templates/meeting/remove_sessionpresentation.html b/ietf/templates/meeting/remove_sessionpresentation.html new file mode 100644 index 000000000..43eba01ed --- /dev/null +++ b/ietf/templates/meeting/remove_sessionpresentation.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} + +{% load bootstrap3 %} + +{% block title %}Remove {{sp.document}} from session{% endblock %} + +{% block content %} + {% origin %} +

Confirm removing document from session {{sp.session}}

+ + {% if sp.session.is_material_submission_cutoff %} +

The deadline for submission corrections has passed. This may affect published proceedings.

+ {% endif %} + +

Document

+

{{sp.document.name}}{% if sp.rev %}-{{sp.rev}}{% else %} (current version){% endif %}

+

{{sp.document.title}}

+

Session

+

{{sp.session}}

+ +
+ {% csrf_token %} + {% buttons %} + + Cancel + {% endbuttons %} +
+ +{% endblock %} diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index b712b5520..d332b7077 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -95,6 +95,7 @@ {% if can_manage_materials %} Upload Revision + Remove {% endif %} @@ -121,6 +122,11 @@ {% endif %} {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) + {% if can_manage_materials %} + + Remove + + {% endif %} {% endfor %}