Merged in ^/branch/proceedings/6.33.1.dev0 from rjsparks@nostrum.com and rcross@amsl.com. This provides new ietf meeting proceedings pages.

- Legacy-Id: 12028
This commit is contained in:
Henrik Levkowetz 2016-09-23 17:33:43 +00:00
commit 8c34e9d78d
33 changed files with 931 additions and 1031 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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',)},
),
]

View file

@ -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:

View file

@ -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

View file

@ -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())

View file

@ -11,6 +11,11 @@ safe_for_all_meeting_types = [
url(r'^session/(?P<acronym>[-a-z0-9]+)/?$', views.session_details),
url(r'^session/(?P<session_id>\d+)/drafts$', views.add_session_drafts),
url(r'^session/(?P<session_id>\d+)/bluesheets$', views.upload_session_bluesheets),
url(r'^session/(?P<session_id>\d+)/minutes$', views.upload_session_minutes),
url(r'^session/(?P<session_id>\d+)/agenda$', views.upload_session_agenda),
url(r'^session/(?P<session_id>\d+)/slides(?:/%(name)s)?$' % settings.URL_REGEXPS, views.upload_session_slides),
url(r'^session/(?P<session_id>\d+)/slides/%(name)s/order$' % settings.URL_REGEXPS, views.set_slide_order),
url(r'^session/(?P<session_id>\d+)/doc/%(name)s/remove$' % settings.URL_REGEXPS, views.remove_sessionpresentation),
]

View file

@ -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)

View file

@ -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

View file

@ -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='<div id="id_file_help">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.<br><br>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.</div>')
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

View file

@ -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)

View file

@ -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<meeting_num>\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<meeting_num>\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/(?P<acronym>[-a-z0-9]+)/$',
'build', name='proceedings_build'),
url(r'^delete/(?P<slide_id>[A-Za-z0-9._\-\+]+)/$', 'delete_material', name='proceedings_delete_material'),
url(r'^edit-slide/(?P<slide_id>[A-Za-z0-9._\-\+]+)/$', 'edit_slide', name='proceedings_edit_slide'),
url(r'^move-slide/(?P<slide_id>[A-Za-z0-9._\-\+]+)/(?P<direction>(up|down))/$',
'move_slide', name='proceedings_move_slide'),
url(r'^process-pdfs/(?P<meeting_num>\d{1,3})/$', 'process_pdfs', name='proceedings_process_pdfs'),
url(r'^progress-report/(?P<meeting_num>\d{1,3})/$', 'progress_report', name='proceedings_progress_report'),
url(r'^replace-slide/(?P<slide_id>[A-Za-z0-9._\-\+]+)/$', 'replace_slide', name='proceedings_replace_slide'),
url(r'^(?P<meeting_num>\d{1,3})/$', 'select', name='proceedings_select'),
url(r'^(?P<meeting_num>\d{1,3})/recording/$', 'recording', name='proceedings_recording'),
url(r'^(?P<meeting_num>\d{1,3})/recording/edit/(?P<name>[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<meeting_num>\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/(?P<session_id>\d{1,6})/$',
'upload_unified', name='proceedings_upload_unified'),
url(r'^(?P<meeting_num>\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/%(acronym)s/$' % settings.URL_REGEXPS,
'upload_unified', name='proceedings_upload_unified'),
url(r'^(?P<num>\d{1,3}|interim-\d{4}-[A-Za-z0-9_\-\+]+)/%(acronym)s/$' % settings.URL_REGEXPS,
OldUploadRedirect.as_view()),
)

View file

@ -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, {}),
)

View file

@ -1,6 +0,0 @@
<div class="button-group">
<ul>
<li><button type="submit" name="submit" value="Submit">Submit</button></li>
<li><button type="submit" name="submit" value="Back">Back</button></li>
</ul>
</div> <!-- button-group -->

View file

@ -1,25 +0,0 @@
<h2>Activies Log</h2>
<div class="inline-related last-related">
<table class="full-width">
<thead>
<tr>
<th>Date</th>
<th>Time</th>
<th>Document</th>
<th>Action</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for entry in docevents %}
<tr class="{% cycle 'row1' 'row2' %}">
<td>{{ entry.time|date:"M d, Y" }}</td>
<td>{{ entry.time|date:"H:i" }}</td>
<td>{{ entry.doc }}</td>
<td>{{ entry.type }}</td>
<td>{{ entry.by }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> <!-- inline-related -->

View file

@ -1,25 +0,0 @@
{% load ams_filters %}
<table id="slides" class="center sortable" cellspacing="0">
<thead>
<tr>
<th>Slide</th>
<th>Filename</th>
<th>Edit</th>
<th>Replace</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for slide in materials.slides %}
<tr id="slide_{{ forloop.counter }}" class="{% cycle 'row1' 'row2' %}">
<td><span class="ui-icon ui-icon-arrowthick-2-n-s"></span><a href="{{ slide.get_absolute_url }}" target="_blank">{{ slide.title }}</a></td>
<td>{{ slide.external_url }}{% if slide.external_url|is_ppt %}<span class="required"> *</span>{% endif %}</td>
<td><a href="{% url "proceedings_edit_slide" slide_id=slide.pk %}">Edit</a></td>
<td><a href="{% url "proceedings_replace_slide" slide_id=slide.pk %}">Replace</a></td>
<td><a href="{% url "proceedings_delete_material" slide_id=slide.name %}">Delete</a></td>
<td class="hidden">{{ slide.pk }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -1,3 +1,5 @@
<li><a href="https://www.ietf.org/instructions/meeting_materials_tool.html" target="_blank">Instructions</a>.</li>
<li>If you require assistance in using this tool, or wish to report a bug, then please send a message to <a href="mailto:ietf-action@ietf.org">ietf-action@ietf.org</a>.</li>
<li>To submit your materials via email, please send agendas to <a href="mailto:agenda@ietf.org">agenda@ietf.org</a> and minutes/presentation slides to <a href="mailto:proceedings@ietf.org">proceedings@ietf.org</a>.</li>
<li><bold>Note:</bold> Normal session materials materials management is now performed using the {% if meeting.number %}<a href="{% url 'ietf.meeting.views.materials' num=meeting.number %}">{% endif %}materials page{% if meeting.number %}</a>{% endif %}

View file

@ -1,38 +0,0 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% block title %}Edit Slide{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% static 'secr/js/utils.js' %}"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
{% if meeting.type_id == "interim" %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; <a href="{% url "proceedings_upload_unified" meeting_num=meeting.number acronym=group.acronym %}">{{ meeting }}</a>
&raquo; {{ slide.title }}
{% else %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; <a href="{% url "proceedings_select" meeting_num=meeting.number %}">Select</a>
&raquo; <a href="{% url "proceedings_upload_unified" meeting_num=meeting.number acronym=group.acronym%}">{{ group.acronym }}</a>
&raquo; {{ slide.title }}
{% endif %}
{% endblock %}
{% block content %}
<div class="module interim-container">
<h2>Working Group - {{ group.acronym }}</h2>
<p><h3>Edit Slide:</h3></p>
<form enctype="multipart/form-data" action="." method="post">{% csrf_token %}
<table>
{{ form.as_table }}
</table>
{% include "includes/buttons_save_cancel.html" %}
</form>
</div> <!-- module -->
{% endblock %}

View file

@ -54,7 +54,7 @@
<tr class = "{% cycle 'row1' 'row2' %}">
<td>{{ meeting.group.acronym }}</td>
<td><a href="{% url "proceedings_upload_unified" meeting_num=meeting.number acronym=meeting.group.acronym %}">{{ meeting.date }}</a></td>
<td><a href="{% url "ietf.meeting.views.session_details" num=meeting.number acronym=meeting.group.acronym %}">{{ meeting.date }}</a></td>
</tr>
{% endfor %}
</tbody>

View file

@ -1,38 +0,0 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% block title %}Replace Slide{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% static 'secr/js/utils.js' %}"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
{% if meeting.type_id == "interim" %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; <a href="{% url "proceedings_upload_unified" meeting_num=meeting.number acronym=group.acronym %}">{{ meeting }}</a>
&raquo; {{ slide.title }}
{% else %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; <a href="{% url "proceedings_select" meeting_num=meeting.number %}">Select</a>
&raquo; <a href="{% url "proceedings_upload_unified" meeting_num=meeting.number acronym=group.acronym %}">{{ group.acronym }}</a>
&raquo; {{ slide.title }}
{% endif %}
{% endblock %}
{% block content %}
<div class="module interim-container">
<h2>Working Group - {{ group.acronym }}</h2>
<p><h3>Replace Slide:</h3></p>
<form enctype="multipart/form-data" action="." method="post">{% csrf_token %}
<table>
{{ form.as_table }}
</table>
{% include "includes/buttons_save_cancel.html" %}
</form>
</div> <!-- module -->
{% endblock %}

View file

@ -29,7 +29,7 @@
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; {{ meeting.number }} - Select Group
&raquo; {{ meeting.number }}
{% endblock %}
{% block instructions %}
@ -39,49 +39,7 @@
{% block content %}
<div class="module interim-container">
<h2>IETF {{ meeting.number }} - Upload Material</span></h2>
<div class="inline-related">
<h3><b>Select Group</b></h3>
<p>The list below includes those working groups and approved BOFs that are scheduled to
meet at IETF {{meeting.number }}. You must request a session slot in order for your group(s) to appear on
this list.</p>
<p>- To request a session slot for a working group, please use the
<a href="{% url "sessions" %}">IETF Meeting Session Request Tool</a>.
<br>
- To request a session slot for a BOF, please send a message to <a href="mailto:ietf-action@ietf.org">agenda@ietf.org</a>.
Additional information is available at
<a href="https://www.ietf.org/instructions/MTG-SLOTS.html">"Requesting Meeting Slots at IETF Meetings."</a>
<br>
- To upload meeting materials for a scheduled session, please select the session name below.</p>
<form class="internal-form" action="" method="post">{% csrf_token %}
<label for="id_group">Working Groups</label>
{{ group_form.group }}
<input type="submit" name="submit" value="Select" />
</form>
{% if irtf_form %}
<form class="internal-form" action="" method="post">{% csrf_token %}
<label for="id_group">IRTF Groups</label>
{{ irtf_form.group }}
<input type="submit" name="submit" value="Select" />
</form>
{% endif %}
{% if training_form %}
<form class="internal-form" action="" method="post">{% csrf_token %}
<label for="id_group">Training / Other</label>
{{ training_form.group }}
<input type="submit" name="submit" value="Select" />
</form>
{% endif %}
{% if plenary_form %}
<form class="internal-form" action="" method="post">{% csrf_token %}
<label for="id_group">Plenaries</label>
{{ plenary_form.group }}
<input type="submit" name="submit" value="Select" />
</form>
{% endif %}
</div> <!-- inline-related -->
{% if user|has_role:"Secretariat" %}
<div class="inline-related">
<h2>Secretariat Only Functions</h2>
<div id="private-functions">
@ -90,7 +48,6 @@
</div> <!-- private-functions -->
</div> <!-- inline-group -->
{% endif %}
</div> <!-- module -->

View file

@ -1,34 +0,0 @@
{% extends "base_site.html" %}
{% load staticfiles %}
{% block title %}Proceedings - Upload Presentations {% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% static 'secr/js/utils.js' %}"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../../">Proceedings</a>
&raquo; <a href="../../">{{ meeting.meeting_num }}</a>
&raquo; <a href="../../convert">Convert Material</a>
&raquo; Upload Presentation
{% endblock %}
{% block content %}
<div class="module">
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
<h2>Proceedings - Upload (HTML-Zipped) Presentation : <font color="RED">{{ slide.group_name }}</font></h2>
<p>For Presentation : {{ slide.slide_name }}</p>
<table>
{{ upload_presentation.as_table }}
</table>
<div class="button-group">
<ul>
<li><input type="submit" value="Upload" class="standard" /></li>
<!--li><input type="submit" value="Cancel" class="standard" /></li-->
</ul>
</div> <!-- button-group -->
</form>
</div> <!-- module -->
{% endblock %}

View file

@ -1,116 +0,0 @@
{% extends "base_site.html" %}
{% load ietf_filters %}
{% load staticfiles %}
{% block title %}Proceedings{% endblock %}
{% block extrastyle %}{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'secr/css/jquery-ui-1.11.4.custom.css' %}" />
{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% static 'secr/js/jquery-ui-1.11.4.custom.min.js' %}"></script>
<script type="text/javascript" src="{% static 'secr/js/utils.js' %}"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
{% if meeting.type_id == "interim" %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; {{ meeting }}
{% else %}
&raquo; <a href="{% url "proceedings" %}">Proceedings</a>
&raquo; <a href="{% url "proceedings_select" meeting_num=meeting.number %}">{{ meeting }}</a>
&raquo; {% if session_name %}{{ session_name }}{% else %}{{ group.acronym }}{% endif %}
{% endif %}
{% endblock %}
{% block content %}
<div class="module interim-container">
<h2>{{ meeting }} - Upload Material - {% if session_name %}{{ session_name }}{% else %}Group: {{ group.acronym }}{% endif %}
</h2>
<table class="center">
<thead>
<tr>
<th>Type</th>
<th>Filename</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr>
{% if proceedings_url %}
<td><a href="{{ proceedings_url|safe }}" target="_blank">Proceedings</a></td>
<td>proceedings.html</td>
{% else %}
<td>Proceedings</td>
<td>Proceedings not yet generated</td>
{% endif %}
<td></td>
</tr>
<tr>
{% if materials.minutes %}
<td><a href="{{ materials.minutes.get_absolute_url }}" target="_blank">Minutes</td>
<td>{{ materials.minutes.external_url }}</td>
<td><a href="{% url "proceedings_delete_material" slide_id=materials.minutes.name %}">Delete</a></td>
{% else %}
<td>Minutes</td>
<td>(not uploaded)</td>
<td></td>
{% endif %}
</tr>
<tr>
{% if materials.agenda %}
<td><a href="{{ materials.agenda.get_absolute_url }}" target="_blank">Agenda</a></td>
<td>{{ materials.agenda.external_url }}</td>
<td><a href="{% url "proceedings_delete_material" slide_id=materials.agenda.name %}">Delete</a></td>
{% else %}
<td>Agenda</td>
<td>(not uploaded)</td>
<td></td>
{% endif %}
</tr>
{% if materials.bluesheets %}
{% for item in materials.bluesheets %}
<tr>
<td><a href="{{ item.get_absolute_url }}" target="_blank">Bluesheet</a></td>
<td>{{ item.external_url }}</td>
<td>{% if user|has_role:"Secretariat" %}<a href="{% url "proceedings_delete_material" slide_id=item.name %}">Delete</a>{% endif %}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<div class="inline-related">
<h2>Slides&nbsp;&nbsp;<span class="highlight">NEW! Drag and drop to re-order slides</span></h2>
{% include "includes/slides.html" %}
<p>(<span class="required"> *</span> - Waiting to be converted to PDF format.)</p>
</div> <!-- inline-group -->
<div class="inline-related">
<h2>Upload Materials</h2>
<form id="upload_materials_form" enctype="multipart/form-data" action="." method="post">{% csrf_token %}
<table class="center" id="proceedings-upload-table">
{{ form.as_table }}
</table>
{% include "includes/buttons_submit_back.html" %}
</form>
</div> <!-- inline-group -->
</div> <!-- module -->
{% if docevents %}
<br>
<div class="module interim-container">
{% include "includes/docevents.html" %}
</div>
{% endif %}
{% endblock %}
{% block footer-extras %}
{% include "includes/upload_footer.html" %}
{% endblock %}

View file

@ -15,9 +15,7 @@
<td>
{% ifchanged s.meeting %}
{% if s.meeting.type.slug == 'ietf' %}
<a href="{% url 'ietf.meeting.views.agenda' num=s.meeting.number %}">
IETF {{s.meeting.number}}
</a>
IETF {{s.meeting.number}}
{% else %}
{{s.meeting.number}}
{% endif %}

View file

@ -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 %}
<a class="button btn-default btn-sm" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">Edit</a>
{% with gt=session.group.type_id %}
<a class="button btn-default btn-sm" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}{% if gt == 'wg' or gt == 'rg' or gt == 'ag' %}{% else %}#session_{{session.pk}}{% endif %}">Edit</a>
{% endwith %}
{% endif %}

View file

@ -27,7 +27,9 @@
{% endif %}
<p>
<a class="btn btn-default" href="{% url "proceedings" %}">Meeting materials manager</a>
{% if user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url "proceedings" %}">Secretariat proceedings functions</a>
{% endif %}
<a class="btn btn-default" href="/meeting/{{meeting_num}}/requests">Meeting requests/conflicts</a>
</p>

View file

@ -23,9 +23,6 @@
{% endif %}
</h1>
<p class="alert alert-info">
<b>This page is under construction</b>
</p>
{% if meeting.number|add:0 <= 96 %}
<p class="alert alert-info">
<b>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 <a href="https://www.ietf.org/proceedings/{{meeting.number}}">https://www.ietf.org/proceedings/{{meeting.number}}</a></b>

View file

@ -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 %}
<h1>Confirm removing document from session {{sp.session}}</h1>
{% if sp.session.is_material_submission_cutoff %}
<p class="alert alert-warning">The deadline for submission corrections has passed. This may affect published proceedings.</p>
{% endif %}
<h2>Document</h2>
<p><strong>{{sp.document.name}}{% if sp.rev %}-{{sp.rev}}{% else %} (current version){% endif %}</strong></p>
<p>{{sp.document.title}}</p>
<h2>Session</h2>
<p>{{sp.session}}</p>
<form method="post">
{% csrf_token %}
{% buttons %}
<button type="submit" class="btn btn-{% if sp.session.is_material_submission_cutoff %}warning{% else %}primary{% endif %}" name="remove_session">Remove document from session</button>
<a class="btn btn-default href="{% url 'ietf.meeting.views.session_details' num=sp.session.meeting.number acronym=sp.session.group.acronym%}">Cancel</a>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -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 %}
<h1>{{ meeting }} : {{ acronym }}</h1>
{% for session in sessions %}
<h2>{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ session.time }}{% if session.name %} : {{ session.name }}{% endif %}</h2>
<h2 class="anchor-target" id="session_{{session.pk}}">{% if sessions|length > 1 %}Session {{ forloop.counter }} : {% endif %}{{ session.time }}{% if session.name %} : {{ session.name }}{% endif %}</h2>
{% if session.agenda_note %}<h3>{{session.agenda_note}}</h3>{% endif %}
{% if can_manage_materials %}
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
<div class="buttonlist">
{% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
{% endif %}
<a class="btn btn-default" href="{% url 'ietf.secr.proceedings.views.upload_unified' meeting_num=session.meeting.number acronym=session.group.acronym %}">
Upload/Edit materials
</a>
<a class="btn btn-default" href="{% url 'ietf.meeting.views.add_session_drafts' session_id=session.pk num=session.meeting.number %}">
Link additional drafts to session
</a>
{% if user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number %}">Upload Bluesheets</a>
{% endif %}
{% if not type_counter.agenda %}
<span class="label label-warning">This session does not yet have an agenda</span>
{% endif %}
</div>
{% endif %}
{% endif %}
{% if can_manage_materials %}
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
<div class="buttonlist">
{% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
{% endif %}
</div>
{% if not session.type_counter.agenda %}
<span class="label label-warning">This session does not yet have an agenda</span>
{% endif %}
{% endif %}
{% endif %}
{% if session.filtered_artifacts %}
<div class="panel panel-default">
<div class="panel-heading">Artifacts</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_artifacts %}
<tr>
<td>
{% 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 %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %})
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">Agenda, Minutes, and Bluesheets</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_artifacts %}
<tr>
{% 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 %}
<td>
<a href="{{pres.document.get_absolute_url}}">{{pres.document.title}}</a>
<a href="{{url}}">({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %})</a>
</td>
{% if user|has_role:"Secretariat" or can_manage_materials %}
<td class="col-md-2">
{% 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" %}
<a class="btn btn-default btn-sm pull-right" href="{{upload_url}}">Upload Revision</a>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
{% if not session.type_counter.agenda %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number %}">Upload Agenda</a>
{% endif %}
{% if not session.type_counter.minutes %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number %}">Upload Minutes</a>
{% endif %}
{% endif %}
{% if session.filtered_slides %}
<div class="panel panel-default">
<div class="panel-heading">Slides</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_slides %}
<tr>
<td>
{% 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 %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %})
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% if user|has_role:"Secretariat" and not session.type_counter.bluesheets %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_bluesheets' session_id=session.pk num=session.meeting.number %}">Upload Bluesheets</a>
{% endif %}
{% if session.filtered_drafts %}
<div class="panel panel-default">
<div class="panel-heading">Drafts</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_drafts %}
<tr>
<td>
{% 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 %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %})
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" data-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
<div class="panel-body">
<table class="table table-condensed table-striped" id="slides">
<tbody>
{% for pres in session.filtered_slides %}
<tr data-order="{{pres.order}}" data-url="{% url 'ietf.meeting.views.set_slide_order' session_id=session.pk num=session.meeting.number name=pres.document.name %}">
{% 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 %}
<td>
<a href="{{pres.document.get_absolute_url}}">{{pres.document.title}} </a>
<a href="{{url}}">({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) </a>
</td>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Upload Revision</a>
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% if can_manage_materials %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upload_session_slides' session_id=session.pk num=session.meeting.number %}">Upload New Slides</a>
{% endif %}
</div>
{% if can_manage_materials %}
<div class="panel-footer small">Drag-and-drop to reorder slides</div>
{% endif %}
</div>
<div class="panel panel-default">
<div class="panel-heading">Drafts
</div>
<div class="panel-body">
<table class="table table-condensed table-striped">
{% for pres in session.filtered_drafts %}
<tr>
<td>
{% 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 %}
<a href="{{url}}">{{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %})</a>
</td>
{% if can_manage_materials %}
<td class="col-md-2">
<a class="btn btn-default btn-sm pull-right" href="{% url 'ietf.meeting.views.remove_sessionpresentation' session_id=session.pk num=session.meeting.number name=pres.document.name %}">Remove</a>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if can_manage_materials %}
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.add_session_drafts' session_id=session.pk num=session.meeting.number %}">
Link additional drafts to session
</a>
{% endif %}
</div>
</div>
{% 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 %}
<script type="text/javascript" src="{% static 'jquery/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'secr/js/jquery-ui-1.11.4.custom.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery.cookie/jquery.cookie.js' %}"></script>
<script type="text/javascript">
$.ajaxSetup({
crossDomain: false,
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
}
}
});
$(document).ready(function() {
var rowWidthHelper = function (e, tr) {
var $originals = tr.children();
var $helper = tr.clone();
$helper.children().each(function(index)
{
$(this).width($originals.eq(index).width())
});
return $helper;
};
$("#slides tbody").sortable({
helper: rowWidthHelper,
stop: function(event,ui) {adjustDatabase("#slides")}
}).disableSelection();
});
function adjustDatabase(tableID) {
$(tableID + " tr").each(function() {
count = $(this).parent().children().index($(this)) + 1;
old_order = $(this).attr("data-order");
if ( count != old_order ) {
$(this).attr("data-order", count);
$.post($(this).attr("data-url"),{'order':count});
}
});
}
</script>
{% endif %}
{% endblock %}

View file

@ -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 %}
<h1>{% if agenda_sp %}Revise{% else %}Upload{% endif %} Agenda for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
{% if session_number %}<h2> Session {{session_number}} : {{session.official_timeslotassignment.timeslot.time|date:"D M-d-Y Hi"}}</h2>{% endif %}
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Upload</button>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -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 %}
<h1>Upload Bluesheets for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
<h1>{% if bluesheet_sp %}Revise{% else %}Upload{% endif %} Bluesheets for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
{% if session_number %}<h2> Session {{session_number}} : {{session.official_timeslotassignment.timeslot.time|date:"D M-d-Y Hi"}}</h2>{% endif %}
{% if bluesheet_sp %}
<div class="alert alert-warning">
Bluesheets have alrady been uploaded for this session.
See <a href="{% url 'ietf.doc.views_doc.document_main' name=bluesheet_sp.document.name %}">{{bluesheet_sp.document.name}}-{{bluesheet_sp.document.rev}}</a>.
Continue with this upload to provide an updated version of that document.
</div>
{% endif %}
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}

View file

@ -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 %}
<h1>{% if minutes_sp %}Revise{% else %}Upload{% endif %} Minutes for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
{% if session_number %}<h2> Session {{session_number}} : {{session.official_timeslotassignment.timeslot.time|date:"D M-d-Y Hi"}}</h2>{% endif %}
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Upload</button>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -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 %}
<h1>{% if slides_sp %}Revise{% else %}Upload New{% endif %} Slides for {{ session.meeting }} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
{% if session_number %}<h2> Session {{session_number}} : {{session.official_timeslotassignment.timeslot.time|date:"D M-d-Y Hi"}}</h2>{% endif %}
{% if slides_sp %}<h3>{{slides_sp.document.name}}</h3>{% endif %}
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Upload</button>
{% endbuttons %}
</form>
{% endblock %}