diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 898b89134..6d8e2dc09 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -60,7 +60,7 @@ class DocumentInfo(models.Model): abstract = models.TextField(blank=True) rev = models.CharField(verbose_name="revision", max_length=16, blank=True) pages = models.IntegerField(blank=True, null=True) - order = models.IntegerField(default=1, blank=True) + order = models.IntegerField(default=1, blank=True) # This is probably obviated by SessionPresentaion.order intended_std_level = models.ForeignKey(IntendedStdLevelName, verbose_name="Intended standardization level", blank=True, null=True) std_level = models.ForeignKey(StdLevelName, verbose_name="Standardization level", blank=True, null=True) ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True) diff --git a/ietf/meeting/factories.py b/ietf/meeting/factories.py index 66386ad90..94c5a21f5 100644 --- a/ietf/meeting/factories.py +++ b/ietf/meeting/factories.py @@ -2,7 +2,6 @@ import factory import random import datetime -from django.db.models import Max from django.core.files.base import ContentFile from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot, SessionPresentation, FloorPlan @@ -31,7 +30,8 @@ class MeetingFactory(factory.DjangoModelFactory): def number(self,n): if self.type_id == 'ietf': if Meeting.objects.filter(type='ietf').exists(): - return '%02d'%(int(Meeting.objects.filter(type='ietf').aggregate(Max('number'))['number__max'])+1) + so_far = max([int(x.number) for x in Meeting.objects.filter(type='ietf')]) + return '%02d'%(so_far+1) else: return '%02d'%(n+80) else: diff --git a/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py b/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py new file mode 100644 index 000000000..c823c62d5 --- /dev/null +++ b/ietf/meeting/migrations/0037_change_meta_options_on_sessionpresentation.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('meeting', '0036_add_order_to_sessionpresentation'), + ] + + operations = [ + migrations.AlterModelOptions( + name='sessionpresentation', + options={'ordering': ('order',)}, + ), + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 27e1ee4f6..af12e2162 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -963,6 +963,7 @@ class SessionPresentation(models.Model): class Meta: db_table = 'meeting_session_materials' + ordering = ('order',) def __unicode__(self): return u"%s -> %s-%s" % (self.session, self.document.name, self.rev) @@ -1005,7 +1006,7 @@ class Session(models.Model): for d in l: d.meeting_related = lambda: True else: - l = self.materials.filter(type=material_type).exclude(states__type=material_type, states__slug='deleted').order_by("order") + l = self.materials.filter(type=material_type).exclude(states__type=material_type, states__slug='deleted').order_by('sessionpresentation__order') if only_one: if l: diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 5714b2dab..3ce762a8e 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 @@ -43,15 +46,23 @@ def condition_data(): @skipIf(skip_selenium, skip_message) class ScheduleEditTests(StaticLiveServerTestCase): - def setUp(self): + @classmethod + def setUpClass(cls): set_coverage_checking(False) - condition_data() + super(ScheduleEditTests, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(ScheduleEditTests, cls).tearDownClass() + set_coverage_checking(True) + + def setUp(self): self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH) self.driver.set_window_size(1024,768) + condition_data() def tearDown(self): self.driver.close() - set_coverage_checking(True) def debugSnapshot(self,filename='debug_this.png'): self.driver.execute_script("document.body.bgColor = 'white';") @@ -88,6 +99,57 @@ 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): + @classmethod + def setUpClass(cls): + set_coverage_checking(False) + super(SlideReorderTests, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(SlideReorderTests, cls).tearDownClass() + set_coverage_checking(True) + + def setUp(self): + self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH) + self.driver.set_window_size(1024,768) + 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) + + def tearDown(self): + self.driver.close() + + 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/tests_views.py b/ietf/meeting/tests_views.py index 3811f2dd4..87da4149c 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -260,7 +260,6 @@ class MeetingTests(TestCase): self.write_materials_files(meeting, session) url = urlreverse("ietf.meeting.views.proceedings", kwargs=dict(num=meeting.number)) - login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) @@ -1271,7 +1270,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 +1283,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 +1301,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 +1316,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 +1324,152 @@ 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') + + 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 045821e50..295ddb410 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -11,6 +11,11 @@ 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), + 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 3a6fa6a47..c87c4203c 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 @@ -28,14 +28,17 @@ from django.forms import ModelForm from django.template.loader import render_to_string from django.utils.functional import curry from django.views.decorators.cache import cache_page +from django.utils.text import slugify from django.views.decorators.csrf import ensure_csrf_cookie +from django.views.generic import RedirectView +from django.template.defaultfilters import filesizeformat from ietf.doc.fields import SearchableDocumentsField 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 @@ -52,7 +55,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 @@ -1031,10 +1033,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) @@ -1053,10 +1054,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)) @@ -1065,7 +1063,6 @@ def session_details(request, num, acronym ): 'meeting' :meeting , 'acronym' :acronym, 'can_manage_materials' : can_manage, - 'type_counter': type_counter, }) class SessionDraftsForm(forms.Form): @@ -1139,6 +1136,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': @@ -1163,7 +1162,7 @@ def upload_session_bluesheets(request, session_id, num): session.sessionpresentation_set.create(document=doc,rev='00') filename = '%s-%s%s'% ( doc.name, doc.rev, ext) doc.external_url = filename - e = NewRevisionDocEvent.objects.create(doc=doc, by=Person.objects.get(name='(System)'),type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) + e = NewRevisionDocEvent.objects.create(doc=doc,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev) doc.save_with_history([e]) handle_upload_file(file, filename, session.meeting, 'bluesheets') return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym) @@ -1177,6 +1176,348 @@ 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=False,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) + 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')) + 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, + }) + +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) + 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): @@ -1621,7 +1962,6 @@ def floor_plan(request, num=None, floor=None, ): "floors": floors, }) -@role_required('Secretariat') def proceedings(request, num=None): meeting = get_meeting(num) @@ -1689,3 +2029,7 @@ def proceedings_overview(request, num=None): 'meeting': meeting, '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 3b852f145..93323613d 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,16 @@ 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 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') + if not has_role(request.user,'Secretariat'): + return HttpResponseRedirect(reverse('ietf.meeting.views.materials', kwargs={'num':meeting_num})) 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 +409,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 +423,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/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

- 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/includes/upload_footer.html b/ietf/secr/templates/includes/upload_footer.html index dfd49ed43..eff80b591 100755 --- a/ietf/secr/templates/includes/upload_footer.html +++ b/ietf/secr/templates/includes/upload_footer.html @@ -1,3 +1,5 @@
  • Instructions.
  • If you require assistance in using this tool, or wish to report a bug, then please send a message to ietf-action@ietf.org.
  • To submit your materials via email, please send agendas to agenda@ietf.org and minutes/presentation slides to proceedings@ietf.org.
  • +
  • Note: Normal session materials materials management is now performed using the {% if meeting.number %}{% endif %}materials page{% if meeting.number %}{% endif %} + 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 %} diff --git a/ietf/templates/group/meetings-row.html b/ietf/templates/group/meetings-row.html index 042faeb43..1da9b420a 100644 --- a/ietf/templates/group/meetings-row.html +++ b/ietf/templates/group/meetings-row.html @@ -15,9 +15,7 @@ {% ifchanged s.meeting %} {% if s.meeting.type.slug == 'ietf' %} - - IETF {{s.meeting.number}} - + IETF {{s.meeting.number}} {% else %} {{s.meeting.number}} {% endif %} diff --git a/ietf/templates/meeting/edit_materials_button.html b/ietf/templates/meeting/edit_materials_button.html index ee4c124c0..f65338b7b 100644 --- a/ietf/templates/meeting/edit_materials_button.html +++ b/ietf/templates/meeting/edit_materials_button.html @@ -1,4 +1,6 @@ {% load ietf_filters session_filters %} {% if user|has_role:"Secretariat" or session|can_manage_materials:user and not session.is_material_submission_cutoff %} -Edit +{% with gt=session.group.type_id %} +Edit +{% endwith %} {% endif %} diff --git a/ietf/templates/meeting/materials.html b/ietf/templates/meeting/materials.html index 6aee1a313..c00f6b24b 100644 --- a/ietf/templates/meeting/materials.html +++ b/ietf/templates/meeting/materials.html @@ -27,7 +27,9 @@ {% endif %}

    - Meeting materials manager + {% if user|has_role:"Secretariat" %} + Secretariat proceedings functions + {% endif %} Meeting requests/conflicts

    diff --git a/ietf/templates/meeting/proceedings.html b/ietf/templates/meeting/proceedings.html index 3e93543a9..ea6e6eb4b 100644 --- a/ietf/templates/meeting/proceedings.html +++ b/ietf/templates/meeting/proceedings.html @@ -23,9 +23,6 @@ {% endif %} -

    - This page is under construction -

    {% if meeting.number|add:0 <= 96 %}

    These are not the official proceedings for IETF{{meeting.number}}. This page shows what would be generated by the new automatic proceedings generator for that meeting. The official proceedings are located at https://www.ietf.org/proceedings/{{meeting.number}} 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 a6f4f3abf..de893fa77 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -1,106 +1,196 @@ {% 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 }}

    {% 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 %} - {% 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 %}) - -
    -
    -
    +
    +
    Agenda, Minutes, and Bluesheets
    +
    + + {% 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 + Remove +
    + {% if can_manage_materials %} + Upload New Slides {% endif %} - +
    + {% if can_manage_materials %} + + {% endif %} +
    +
    +
    Drafts +
    +
    + + {% for pres in session.filtered_drafts %} + + + {% if can_manage_materials %} + + {% 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 %}) + + Remove +
    + {% if can_manage_materials %} + + Link additional drafts to session + + {% endif %} +
    +
    {% 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 %} 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 %}