Show all sessions associated with a document.
Remove the day/seq urls from the materials tree. Allow sessionpresentation.rev to be None, meaning "current version". Streamlined workflows to focus on current versions of a draft as a default. Allow adding and editing sessionpresentations when looking at a specific document. Allow adding drafts when looking at a specific session. Add the meeting tab to 'team' groups. Refactored several utility classes and expanded on factories. Fixes #1908 and #1910. Commit ready for merge. - Legacy-Id: 10856
This commit is contained in:
commit
e8d40c8402
45
ietf/doc/factories.py
Normal file
45
ietf/doc/factories.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import factory
|
||||
|
||||
from ietf.doc.models import Document, DocEvent, NewRevisionDocEvent
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
||||
class DocumentFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Document
|
||||
|
||||
type_id = 'draft'
|
||||
title = factory.Faker('sentence',nb_words=6)
|
||||
rev = '00'
|
||||
group = None
|
||||
|
||||
@factory.lazy_attribute_sequence
|
||||
def name(self, n):
|
||||
return '%s-%s-%s-%s%d'%(
|
||||
self.type_id,
|
||||
'bogusperson',
|
||||
self.group.acronym if self.group else 'netherwhere',
|
||||
'musings',
|
||||
n,
|
||||
)
|
||||
|
||||
newrevisiondocevent = factory.RelatedFactory('ietf.doc.factories.NewRevisionDocEventFactory','doc')
|
||||
|
||||
class DocEventFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = DocEvent
|
||||
|
||||
type = 'added_comment'
|
||||
by = factory.SubFactory(PersonFactory)
|
||||
doc = factory.SubFactory(DocumentFactory)
|
||||
desc = factory.Faker('sentence',nb_words=6)
|
||||
|
||||
class NewRevisionDocEventFactory(DocEventFactory):
|
||||
class Meta:
|
||||
model = NewRevisionDocEvent
|
||||
|
||||
type = 'new_revision'
|
||||
rev = '00'
|
||||
|
||||
@factory.lazy_attribute
|
||||
def desc(self):
|
||||
return 'New version available %s-%s'%(self.doc.name,self.rev)
|
|
@ -20,10 +20,14 @@ import debug # pyflakes:ignore
|
|||
from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
|
||||
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent,
|
||||
save_document_in_history )
|
||||
from ietf.doc.factories import DocumentFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.meeting.factories import SessionFactory
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Person
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent
|
||||
|
@ -904,3 +908,185 @@ expand-draft-ietf-ames-test.all@virtual.ietf.org ames-author@example.ames, ames
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue('draft-ietf-mars-test.all@ietf.org' in unicontent(r))
|
||||
self.assertTrue('ballot_saved' in unicontent(r))
|
||||
|
||||
class DocumentMeetingTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.group = GroupFactory(type_id='wg',state_id='active')
|
||||
self.group_chair = PersonFactory()
|
||||
self.group.role_set.create(name_id='chair',person=self.group_chair,email=self.group_chair.email())
|
||||
|
||||
self.other_group = GroupFactory(type_id='wg',state_id='active')
|
||||
self.other_chair = PersonFactory()
|
||||
self.other_group.role_set.create(name_id='chair',person=self.other_chair,email=self.other_chair.email())
|
||||
|
||||
today = datetime.date.today()
|
||||
cut_days = settings.MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS
|
||||
self.past_cutoff = SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today-datetime.timedelta(days=1+cut_days))
|
||||
self.past = SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today-datetime.timedelta(days=cut_days/2))
|
||||
self.inprog = SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today-datetime.timedelta(days=1))
|
||||
self.future = SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today+datetime.timedelta(days=90))
|
||||
self.interim = SessionFactory.create(meeting__type_id='interim',group=self.group,meeting__date=today+datetime.timedelta(days=45))
|
||||
|
||||
def test_view_document_meetings(self):
|
||||
doc = DocumentFactory.create()
|
||||
doc.sessionpresentation_set.create(session=self.inprog,rev=None)
|
||||
doc.sessionpresentation_set.create(session=self.interim,rev=None)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.all_presentations', kwargs=dict(name=doc.name))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(all([q(id) for id in ['#inprogressmeets','#futuremeets']]))
|
||||
self.assertFalse(any([q(id) for id in ['#pastmeets',]]))
|
||||
self.assertFalse(q('#addsessionsbutton'))
|
||||
self.assertFalse(q("a.btn:contains('Remove document')"))
|
||||
|
||||
doc.sessionpresentation_set.create(session=self.past_cutoff,rev=None)
|
||||
doc.sessionpresentation_set.create(session=self.past,rev=None)
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q('#addsessionsbutton'))
|
||||
self.assertEqual(1,len(q("#inprogressmeets a.btn-default:contains('Remove document')")))
|
||||
self.assertEqual(1,len(q("#futuremeets a.btn-default:contains('Remove document')")))
|
||||
self.assertEqual(1,len(q("#pastmeets a.btn-default:contains('Remove document')")))
|
||||
self.assertEqual(1,len(q("#pastmeets a.btn-warning:contains('Remove document')")))
|
||||
|
||||
self.client.login(username=self.group_chair.user.username,password='%s+password'%self.group_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q('#addsessionsbutton'))
|
||||
self.assertEqual(1,len(q("#inprogressmeets a.btn-default:contains('Remove document')")))
|
||||
self.assertEqual(1,len(q("#futuremeets a.btn-default:contains('Remove document')")))
|
||||
self.assertEqual(1,len(q("#pastmeets a.btn-default:contains('Remove document')")))
|
||||
self.assertTrue(q('#pastmeets'))
|
||||
self.assertFalse(q("#pastmeets a.btn-warning:contains('Remove document')"))
|
||||
|
||||
self.client.login(username=self.other_chair.user.username,password='%s+password'%self.other_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q('#addsessionsbutton'))
|
||||
self.assertTrue(all([q(id) for id in ['#futuremeets','#pastmeets','#inprogressmeets']]))
|
||||
self.assertFalse(q("#inprogressmeets a.btn:contains('Remove document')"))
|
||||
self.assertFalse(q("#futuremeets a.btn:contains('Remove document')"))
|
||||
self.assertFalse(q("#pastmeets a.btn:contains('Remove document')"))
|
||||
|
||||
def test_edit_document_session(self):
|
||||
doc = DocumentFactory.create()
|
||||
sp = doc.sessionpresentation_set.create(session=self.future,rev=None)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.edit_sessionpresentation',kwargs=dict(name='no-such-doc',session_id=sp.session_id))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.edit_sessionpresentation',kwargs=dict(name=doc.name,session_id=0))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.edit_sessionpresentation',kwargs=dict(name=doc.name,session_id=sp.session_id))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username=self.other_chair.user.username,password='%s+password'%self.other_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username=self.group_chair.user.username,password='%s+password'%self.group_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual(2,len(q('select#id_version option')))
|
||||
|
||||
self.assertEqual(1,doc.docevent_set.count())
|
||||
response = self.client.post(url,{'version':'00','save':''})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(doc.sessionpresentation_set.get(pk=sp.pk).rev,'00')
|
||||
self.assertEqual(2,doc.docevent_set.count())
|
||||
|
||||
def test_edit_document_session_after_proceedings_closed(self):
|
||||
doc = DocumentFactory.create()
|
||||
sp = doc.sessionpresentation_set.create(session=self.past_cutoff,rev=None)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.edit_sessionpresentation',kwargs=dict(name=doc.name,session_id=sp.session_id))
|
||||
self.client.login(username=self.group_chair.user.username,password='%s+password'%self.group_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username='secretary',password='secretary+password')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q=PyQuery(response.content)
|
||||
self.assertEqual(1,len(q(".alert-warning:contains('may affect published proceedings')")))
|
||||
|
||||
def test_remove_document_session(self):
|
||||
doc = DocumentFactory.create()
|
||||
sp = doc.sessionpresentation_set.create(session=self.future,rev=None)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.remove_sessionpresentation',kwargs=dict(name='no-such-doc',session_id=sp.session_id))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.remove_sessionpresentation',kwargs=dict(name=doc.name,session_id=0))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.remove_sessionpresentation',kwargs=dict(name=doc.name,session_id=sp.session_id))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username=self.other_chair.user.username,password='%s+password'%self.other_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username=self.group_chair.user.username,password='%s+password'%self.group_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.assertEqual(1,doc.docevent_set.count())
|
||||
response = self.client.post(url,{'remove_session':''})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertFalse(doc.sessionpresentation_set.filter(pk=sp.pk).exists())
|
||||
self.assertEqual(2,doc.docevent_set.count())
|
||||
|
||||
def test_remove_document_session_after_proceedings_closed(self):
|
||||
doc = DocumentFactory.create()
|
||||
sp = doc.sessionpresentation_set.create(session=self.past_cutoff,rev=None)
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.remove_sessionpresentation',kwargs=dict(name=doc.name,session_id=sp.session_id))
|
||||
self.client.login(username=self.group_chair.user.username,password='%s+password'%self.group_chair.user.username)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.client.login(username='secretary',password='secretary+password')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
q=PyQuery(response.content)
|
||||
self.assertEqual(1,len(q(".alert-warning:contains('may affect published proceedings')")))
|
||||
|
||||
def test_add_document_session(self):
|
||||
doc = DocumentFactory.create()
|
||||
|
||||
url = urlreverse('ietf.doc.views_doc.add_sessionpresentation',kwargs=dict(name=doc.name))
|
||||
login_testing_unauthorized(self,self.group_chair.user.username,url)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,200)
|
||||
|
||||
response = self.client.post(url,{'session':0,'version':'current'})
|
||||
self.assertEqual(response.status_code,200)
|
||||
q=PyQuery(response.content)
|
||||
self.assertTrue(q('.form-group.has-error'))
|
||||
|
||||
response = self.client.post(url,{'session':self.future.pk,'version':'bogus version'})
|
||||
self.assertEqual(response.status_code,200)
|
||||
q=PyQuery(response.content)
|
||||
self.assertTrue(q('.form-group.has-error'))
|
||||
|
||||
self.assertEqual(1,doc.docevent_set.count())
|
||||
response = self.client.post(url,{'session':self.future.pk,'version':'current'})
|
||||
self.assertEqual(response.status_code,302)
|
||||
self.assertEqual(2,doc.docevent_set.count())
|
||||
|
|
|
@ -6,11 +6,12 @@ import datetime
|
|||
from StringIO import StringIO
|
||||
from pyquery import PyQuery
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
|
||||
from ietf.doc.views_material import material_presentations, edit_material_presentations
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.name.models import SessionStatusName
|
||||
|
@ -18,7 +19,6 @@ from ietf.person.models import Person
|
|||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
|
||||
from ietf.utils.test_data import make_test_data
|
||||
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
|
||||
class GroupMaterialTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -173,67 +173,3 @@ class GroupMaterialTests(TestCase):
|
|||
with open(os.path.join(doc.get_file_path(), doc.name + "-" + doc.rev + ".txt")) as f:
|
||||
self.assertEqual(f.read(), content)
|
||||
|
||||
def test_material_presentations(self):
|
||||
doc = self.create_slides()
|
||||
meeting = make_meeting_test_data()
|
||||
meeting.session_set.filter(group__acronym='mars').update(group=doc.group)
|
||||
|
||||
url = urlreverse(material_presentations,kwargs=dict(name=doc.name))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
url = urlreverse(material_presentations,kwargs=dict(name=doc.name,seq=1))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
when = meeting.agenda.assignments.filter(session__group__acronym='testteam').first().timeslot.time
|
||||
mdw = when.date().isoformat()
|
||||
dow = ['mon','tue','wed','thu','fri','sat','sun'][when.weekday()]
|
||||
|
||||
for kw in [ dict(),
|
||||
dict(seq=1),
|
||||
dict(week_day=dow),
|
||||
dict(week_day=dow,seq=1),
|
||||
dict(date=mdw),
|
||||
dict(date=mdw,seq=1),
|
||||
dict(date=mdw+'-0930'),
|
||||
dict(date=mdw+'-0930',seq=1),
|
||||
]:
|
||||
kw['name'] = doc.name
|
||||
kw['acronym'] = 'testteam'
|
||||
url = urlreverse(material_presentations,kwargs=kw)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_edit_material_presentations(self):
|
||||
doc = self.create_slides()
|
||||
meeting = make_meeting_test_data()
|
||||
meeting.session_set.filter(group__acronym='mars').update(group=doc.group)
|
||||
|
||||
session = meeting.agenda.assignments.filter(session__group__acronym='testteam').first().session
|
||||
|
||||
url = urlreverse(edit_material_presentations,kwargs=dict(name=doc.name,acronym='testteam',seq=1))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
self.assertEqual(doc.sessionpresentation_set.count(),0)
|
||||
|
||||
# add the materials to a session
|
||||
r = self.client.post(url, dict(action="Save",version="00"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(doc.sessionpresentation_set.first().session , session)
|
||||
|
||||
# change the version
|
||||
r = self.client.post(url, dict(action="Save",version="01"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(doc.sessionpresentation_set.first().session , session)
|
||||
|
||||
# take the slides back off that meeting
|
||||
r = self.client.post(url, dict(action="Save",version="notpresented"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(doc.sessionpresentation_set.count(),0)
|
||||
|
||||
|
||||
|
|
|
@ -37,6 +37,12 @@ from ietf.doc import views_search, views_draft, views_ballot
|
|||
from ietf.doc import views_status_change
|
||||
from ietf.doc import views_doc
|
||||
|
||||
session_patterns = [
|
||||
url(r'^add$', views_doc.add_sessionpresentation),
|
||||
url(r'^(?P<session_id>\d+)/edit$', views_doc.edit_sessionpresentation),
|
||||
url(r'^(?P<session_id>\d+)/remove$', views_doc.remove_sessionpresentation),
|
||||
]
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^/?$', views_search.search),
|
||||
url(r'^(?P<name>[A-Za-z0-9\._\+\-]+)$', views_search.search_for_name, name="doc_search_for_name"),
|
||||
|
@ -112,5 +118,7 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<name>charter-[A-Za-z0-9._+-]+)/', include('ietf.doc.urls_charter')),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/meetings$', 'ietf.doc.views_doc.all_presentations', name="all_presentations"),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/material/', include('ietf.doc.urls_material')),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/session/', include(session_patterns)),
|
||||
)
|
||||
|
|
|
@ -2,20 +2,5 @@ from django.conf.urls import patterns, url
|
|||
|
||||
urlpatterns = patterns('ietf.doc.views_material',
|
||||
url(r'^(?P<action>state|title|abstract|revise)/$', "edit_material", name="material_edit"),
|
||||
url(r'^sessions/$', "material_presentations", name="material_presentations"),
|
||||
(r'^sessions/(?P<seq>\d+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/(?P<seq>\d+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/edit/$', "edit_material_presentations"),
|
||||
(r'^sessions/(?P<seq>\d+)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<seq>\d+)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<week_day>[a-zA-Z]+)/(?P<seq>\d+)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/$', "material_presentations"),
|
||||
(r'^sessions/(?P<acronym>[A-Za-z0-9_\-\+]+)/(?P<date>\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P<seq>\d+)/$', "material_presentations"),
|
||||
)
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ from ietf.utils.history import find_history_active_at
|
|||
from ietf.doc.forms import TelechatForm, NotifyForm
|
||||
from ietf.doc.mails import email_comment
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions
|
||||
|
||||
def render_document_top(request, doc, tab, name):
|
||||
tabs = []
|
||||
|
@ -1091,3 +1093,118 @@ def email_aliases(request,name=''):
|
|||
|
||||
return render(request,'doc/email_aliases.html',{'aliases':aliases,'ietf_domain':settings.IETF_DOMAIN,'doc':doc})
|
||||
|
||||
class VersionForm(forms.Form):
|
||||
|
||||
version = forms.ChoiceField(required=True,
|
||||
label='Which version of this document will be discussed at this session?')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
choices = kwargs.pop('choices')
|
||||
super(VersionForm,self).__init__(*args,**kwargs)
|
||||
self.fields['version'].choices = choices
|
||||
|
||||
def edit_sessionpresentation(request,name,session_id):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
sp = get_object_or_404(doc.sessionpresentation_set, session_id=session_id)
|
||||
|
||||
if not sp.session.can_manage_materials(request.user):
|
||||
raise Http404
|
||||
|
||||
if sp.session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
|
||||
raise Http404
|
||||
|
||||
choices = [(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)]
|
||||
choices.insert(0,('current','Current at the time of the session'))
|
||||
initial = {'version' : sp.rev if sp.rev else 'current'}
|
||||
|
||||
if request.method == 'POST':
|
||||
form = VersionForm(request.POST,choices=choices)
|
||||
if form.is_valid():
|
||||
new_selection = form.cleaned_data['version']
|
||||
if initial['version'] != new_selection:
|
||||
doc.sessionpresentation_set.filter(pk=sp.pk).update(rev=None if new_selection=='current' else new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Revision for session %s changed to %s" % (sp.session,new_selection)
|
||||
c.save()
|
||||
return redirect('ietf.doc.views_doc.all_presentations', name=name)
|
||||
else:
|
||||
form = VersionForm(choices=choices,initial=initial)
|
||||
|
||||
return render(request,'doc/edit_sessionpresentation.html', {'sp': sp, 'form': form })
|
||||
|
||||
def remove_sessionpresentation(request,name,session_id):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
sp = get_object_or_404(doc.sessionpresentation_set, session_id=session_id)
|
||||
|
||||
if not sp.session.can_manage_materials(request.user):
|
||||
raise Http404
|
||||
|
||||
if sp.session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
|
||||
raise Http404
|
||||
|
||||
if request.method == 'POST':
|
||||
doc.sessionpresentation_set.filter(pk=sp.pk).delete()
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Removed from session: %s" % (sp.session)
|
||||
c.save()
|
||||
return redirect('ietf.doc.views_doc.all_presentations', name=name)
|
||||
|
||||
return render(request,'doc/remove_sessionpresentation.html', {'sp': sp })
|
||||
|
||||
class SessionChooserForm(forms.Form):
|
||||
session = forms.ChoiceField(label="Which session should this document be added to?",required=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
choices = kwargs.pop('choices')
|
||||
super(SessionChooserForm,self).__init__(*args,**kwargs)
|
||||
self.fields['session'].choices = choices
|
||||
|
||||
@role_required("Secretariat","Area Director","WG Chair","WG Secretary","RG Chair","RG Secretary","IRTF Chair","Team Chair")
|
||||
def add_sessionpresentation(request,name):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
version_choices = [(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)]
|
||||
version_choices.insert(0,('current','Current at the time of the session'))
|
||||
|
||||
sessions = get_upcoming_manageable_sessions(request.user)
|
||||
sessions = sort_sessions([s for s in sessions if not s.sessionpresentation_set.filter(document=doc).exists()])
|
||||
if doc.group:
|
||||
sessions = sorted(sessions,key=lambda x:0 if x.group==doc.group else 1)
|
||||
|
||||
session_choices = [(s.pk,unicode(s)) for s in sessions]
|
||||
|
||||
if request.method == 'POST':
|
||||
version_form = VersionForm(request.POST,choices=version_choices)
|
||||
session_form = SessionChooserForm(request.POST,choices=session_choices)
|
||||
if version_form.is_valid() and session_form.is_valid():
|
||||
session_id = session_form.cleaned_data['session']
|
||||
version = version_form.cleaned_data['version']
|
||||
rev = None if version=='current' else version
|
||||
doc.sessionpresentation_set.create(session_id=session_id,rev=rev)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "%s to session: %s" % ('Added -%s'%rev if rev else 'Added', Session.objects.get(pk=session_id))
|
||||
c.save()
|
||||
return redirect('ietf.doc.views_doc.all_presentations', name=name)
|
||||
|
||||
else:
|
||||
version_form = VersionForm(choices=version_choices,initial={'version':'current'})
|
||||
session_form = SessionChooserForm(choices=session_choices)
|
||||
|
||||
return render(request,'doc/add_sessionpresentation.html',{'doc':doc,'version_form':version_form,'session_form':session_form})
|
||||
|
||||
def all_presentations(request, name):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
|
||||
sessions = doc.session_set.filter(status__in=['sched','schedw','appr','canceled'],
|
||||
type__in=['session','plenary','other'])
|
||||
|
||||
future, in_progress, past = group_sessions(sessions)
|
||||
|
||||
return render(request, 'doc/material/all_presentations.html', {
|
||||
'user': request.user,
|
||||
'doc': doc,
|
||||
'future': future,
|
||||
'in_progress': in_progress,
|
||||
'past' : past,
|
||||
})
|
||||
|
|
|
@ -17,7 +17,6 @@ from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
|
|||
from ietf.doc.utils import add_state_change_event, check_common_doc_name_rules
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_materials
|
||||
from ietf.meeting.models import Session
|
||||
|
||||
@login_required
|
||||
def choose_material_type(request, acronym):
|
||||
|
@ -92,10 +91,15 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
|
|||
|
||||
doc = None
|
||||
document_type = get_object_or_404(DocTypeName, slug=doc_type)
|
||||
if document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types):
|
||||
raise Http404
|
||||
else:
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
group = doc.group
|
||||
document_type = doc.type
|
||||
if document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types):
|
||||
raise Http404
|
||||
|
||||
|
||||
if not can_manage_materials(request.user, group):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
@ -172,149 +176,3 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
|
|||
'document_type': document_type,
|
||||
'doc_name': doc.name if doc else "",
|
||||
})
|
||||
|
||||
class MaterialVersionForm(forms.Form):
|
||||
|
||||
version = forms.ChoiceField(required=False,
|
||||
label='Which version of this document will be presented at this session')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
choices = kwargs.pop('choices')
|
||||
super(MaterialVersionForm,self).__init__(*args,**kwargs)
|
||||
self.fields['version'].choices = choices
|
||||
|
||||
def get_upcoming_manageable_sessions(user, doc, acronym=None, date=None, seq=None, week_day = None):
|
||||
|
||||
# Find all the sessions for meetings that haven't ended that the user could affect
|
||||
# This motif is also in Document.future_presentations - it would be nice to consolodate it somehow
|
||||
|
||||
candidate_sessions = Session.objects.exclude(status__in=['canceled','disappr','notmeet','deleted']).filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
|
||||
refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
|
||||
|
||||
if acronym:
|
||||
refined_candidates = [ sess for sess in refined_candidates if sess.group.acronym==acronym]
|
||||
|
||||
if date:
|
||||
if len(date)==15:
|
||||
start = datetime.datetime.strptime(date,"%Y-%m-%d-%H%M")
|
||||
refined_candidates = [ sess for sess in refined_candidates if sess.timeslotassignments.filter(schedule=sess.meeting.agenda,timeslot__time=start) ]
|
||||
else:
|
||||
start = datetime.datetime.strptime(date,"%Y-%m-%d").date()
|
||||
end = start+datetime.timedelta(days=1)
|
||||
refined_candidates = [ sess for sess in refined_candidates if sess.timeslotassignments.filter(schedule=sess.meeting.agenda,timeslot__time__range=(start,end)) ]
|
||||
|
||||
if week_day:
|
||||
try:
|
||||
dow = ['sun','mon','tue','wed','thu','fri','sat'].index(week_day.lower()[:3]) + 1
|
||||
except ValueError:
|
||||
raise Http404
|
||||
refined_candidates = [ sess for sess in refined_candidates if sess.timeslotassignments.filter(schedule=sess.meeting.agenda,timeslot__time__week_day=dow) ]
|
||||
|
||||
changeable_sessions = [ sess for sess in refined_candidates if can_manage_materials(user, sess.group) ]
|
||||
|
||||
if not changeable_sessions:
|
||||
raise Http404
|
||||
|
||||
for sess in changeable_sessions:
|
||||
sess.has_presentation = bool(sess.sessionpresentation_set.filter(document=doc))
|
||||
if sess.has_presentation:
|
||||
sess.version = sess.sessionpresentation_set.get(document=doc).rev
|
||||
|
||||
# Since Python 2.2 sorts are stable, so this series results in a list sorted first by whether
|
||||
# the session has any presentations, then by the meeting 'number', then by session's group
|
||||
# acronym, then by scheduled time (or the time of the session request if the session isn't
|
||||
# scheduled).
|
||||
|
||||
def time_sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
else:
|
||||
return session.requested
|
||||
|
||||
time_sorted = sorted(changeable_sessions,key=time_sort_key)
|
||||
acronym_sorted = sorted(time_sorted,key=lambda x: x.group.acronym)
|
||||
meeting_sorted = sorted(acronym_sorted,key=lambda x: x.meeting.number)
|
||||
sorted_sessions = sorted(meeting_sorted,key=lambda x: '0' if x.has_presentation else '1')
|
||||
|
||||
if seq:
|
||||
iseq = int(seq) - 1
|
||||
if not iseq in range(0,len(sorted_sessions)):
|
||||
raise Http404
|
||||
else:
|
||||
sorted_sessions = [sorted_sessions[iseq]]
|
||||
|
||||
return sorted_sessions
|
||||
|
||||
@login_required
|
||||
def edit_material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
|
||||
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
group = doc.group
|
||||
|
||||
if not can_manage_materials(request.user,group):
|
||||
raise Http404
|
||||
|
||||
sorted_sessions = get_upcoming_manageable_sessions(request.user, doc, acronym, date, seq, week_day)
|
||||
|
||||
if len(sorted_sessions)!=1:
|
||||
raise Http404
|
||||
|
||||
session = sorted_sessions[0]
|
||||
choices = [('notpresented','Not Presented')]
|
||||
choices.extend([(x,x) for x in doc.docevent_set.filter(type='new_revision').values_list('newrevisiondocevent__rev',flat=True)])
|
||||
initial = {'version' : session.version if hasattr(session,'version') else 'notpresented'}
|
||||
|
||||
if request.method == 'POST':
|
||||
form = MaterialVersionForm(request.POST,choices=choices)
|
||||
if form.is_valid():
|
||||
new_selection = form.cleaned_data['version']
|
||||
if initial['version'] != new_selection:
|
||||
if initial['version'] == 'notpresented':
|
||||
doc.sessionpresentation_set.create(session=session,rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Added version %s to session: %s" % (new_selection,session)
|
||||
c.save()
|
||||
elif new_selection == 'notpresented':
|
||||
doc.sessionpresentation_set.filter(session=session).delete()
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Removed from session: %s" % (session)
|
||||
c.save()
|
||||
else:
|
||||
doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Revision for session %s changed to %s" % (session,new_selection)
|
||||
c.save()
|
||||
return redirect('doc_view',name=doc.name)
|
||||
else:
|
||||
form = MaterialVersionForm(choices=choices,initial=initial)
|
||||
|
||||
return render(request, 'doc/material/edit_material_presentations.html', {
|
||||
'session': session,
|
||||
'doc': doc,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def material_presentations(request, name, acronym=None, date=None, seq=None, week_day=None):
|
||||
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
|
||||
group = doc.group
|
||||
|
||||
if not can_manage_materials(request.user,group):
|
||||
raise Http404
|
||||
|
||||
sorted_sessions = get_upcoming_manageable_sessions(request.user, doc, acronym, date, seq, week_day)
|
||||
|
||||
#for index,session in enumerate(sorted_sessions):
|
||||
# session.sequence = index+1
|
||||
|
||||
return render(request, 'doc/material/material_presentations.html', {
|
||||
'sessions' : sorted_sessions,
|
||||
'doc': doc,
|
||||
'date': date,
|
||||
'week_day': week_day,
|
||||
})
|
||||
|
|
|
@ -60,6 +60,7 @@ from ietf.utils.pipe import pipe
|
|||
from ietf.settings import MAILING_LIST_INFO_URL
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.meeting.utils import group_sessions
|
||||
|
||||
def roles(group, role_name):
|
||||
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")
|
||||
|
@ -335,7 +336,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
|||
entries.append(("About", urlreverse("group_about", kwargs=kwargs)))
|
||||
if group.features.has_materials and get_group_materials(group).exists():
|
||||
entries.append(("Materials", urlreverse("ietf.group.info.materials", kwargs=kwargs)))
|
||||
if group.type_id in ('rg','wg'):
|
||||
if group.type_id in ('rg','wg','team'):
|
||||
entries.append(("Meetings", urlreverse("ietf.group.info.meetings", kwargs=kwargs)))
|
||||
entries.append(("Email expansions", urlreverse("ietf.group.info.email", kwargs=kwargs)))
|
||||
entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs)))
|
||||
|
@ -740,35 +741,7 @@ def meetings(request, acronym=None, group_type=None):
|
|||
meeting__date__gt=four_years_ago,
|
||||
type__in=['session','plenary','other'])
|
||||
|
||||
def sort_key(session):
|
||||
if session.meeting.type.slug=='ietf':
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
elif session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
else:
|
||||
return session.requested
|
||||
else:
|
||||
# TODO: use timeslots for interims once they have them
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
|
||||
for s in sessions:
|
||||
s.time=sort_key(s)
|
||||
|
||||
sessions = sorted(sessions,key=lambda s:s.time,reverse=True)
|
||||
|
||||
today = datetime.date.today()
|
||||
future = []
|
||||
in_progress = []
|
||||
past = []
|
||||
for s in sessions:
|
||||
if s.meeting.date > today:
|
||||
future.append(s)
|
||||
elif s.meeting.end_date() >= today:
|
||||
in_progress.append(s)
|
||||
else:
|
||||
past.append(s)
|
||||
future, in_progress, past = group_sessions(sessions)
|
||||
|
||||
can_edit = has_role(request.user,["Secretariat","Area Director"]) or group.has_role(request.user,["Chair","Secretary"])
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import datetime
|
|||
|
||||
from django.db.models import Max
|
||||
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot, SessionPresentation
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
||||
|
@ -96,4 +96,13 @@ class TimeSlotFactory(factory.DjangoModelFactory):
|
|||
def duration(self):
|
||||
return datetime.timedelta(minutes=30+random.randrange(9)*15)
|
||||
|
||||
class SessionPresentationFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = SessionPresentation
|
||||
|
||||
session = factory.SubFactory(SessionFactory)
|
||||
document = factory.SubFactory('ietf.doc.factories.DocumentFactory')
|
||||
@factory.lazy_attribute
|
||||
def rev(self):
|
||||
return self.document.rev
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0018_auto_20160207_0537'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sessionpresentation',
|
||||
name='rev',
|
||||
field=models.CharField(max_length=16, null=True, verbose_name=b'revision', blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -22,6 +22,7 @@ from django.utils.text import slugify
|
|||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_materials
|
||||
from ietf.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName, RoomResourceName
|
||||
from ietf.person.models import Person
|
||||
|
||||
|
@ -887,7 +888,7 @@ class Constraint(models.Model):
|
|||
class SessionPresentation(models.Model):
|
||||
session = models.ForeignKey('Session')
|
||||
document = models.ForeignKey(Document)
|
||||
rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
|
||||
rev = models.CharField(verbose_name="revision", max_length=16, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'meeting_session_materials'
|
||||
|
@ -957,6 +958,12 @@ class Session(models.Model):
|
|||
def drafts(self):
|
||||
return list(self.materials.filter(type='draft'))
|
||||
|
||||
def can_manage_materials(self, user):
|
||||
return can_manage_materials(user,self.group)
|
||||
|
||||
def is_material_submission_cutoff(self):
|
||||
return datetime.date.today() > self.meeting.get_submission_correction_date()
|
||||
|
||||
def __unicode__(self):
|
||||
if self.meeting.type_id == "interim":
|
||||
return self.meeting.number
|
||||
|
|
17
ietf/meeting/templatetags/session_filters.py
Normal file
17
ietf/meeting/templatetags/session_filters.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def presented_versions(session,doc):
|
||||
sp = session.sessionpresentation_set.filter(document=doc)
|
||||
if not sp:
|
||||
return "Document not in session"
|
||||
else:
|
||||
rev = sp.first().rev
|
||||
return rev if rev else "(current)"
|
||||
|
||||
@register.filter
|
||||
def can_manage_materials(session,user):
|
||||
return session.can_manage_materials(user)
|
||||
|
|
@ -3,6 +3,8 @@ import shutil
|
|||
import datetime
|
||||
import urlparse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -13,6 +15,11 @@ from ietf.meeting.models import Session, TimeSlot
|
|||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
|
||||
|
||||
from ietf.person.factories import PersonFactory
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.meeting.factories import SessionFactory, SessionPresentationFactory
|
||||
from ietf.doc.factories import DocumentFactory
|
||||
|
||||
class MeetingTests(TestCase):
|
||||
def setUp(self):
|
||||
self.materials_dir = os.path.abspath(settings.TEST_MATERIALS_DIR)
|
||||
|
@ -156,13 +163,6 @@ class MeetingTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']]))
|
||||
|
||||
def test_session_details(self):
|
||||
meeting = make_meeting_test_data()
|
||||
url = urlreverse("ietf.meeting.views.session_details", kwargs=dict(num=meeting.number, acronym="mars"))
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(all([x in unicontent(r) for x in ('slides','agenda','minutes')]))
|
||||
self.assertFalse('deleted' in unicontent(r))
|
||||
|
||||
def test_materials(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
|
@ -330,3 +330,63 @@ class EditTests(TestCase):
|
|||
ames_slot_qs.update(time=mars_ends + datetime.timedelta(seconds=10 * 60))
|
||||
self.assertTrue(mars_slot.slot_to_the_right)
|
||||
self.assertTrue(mars_scheduled.slot_to_the_right)
|
||||
|
||||
class SessionDetailsTests(TestCase):
|
||||
|
||||
def test_session_details(self):
|
||||
|
||||
group = GroupFactory.create(type_id='wg',state_id='active')
|
||||
session = SessionFactory.create(meeting__type_id='ietf',group=group, meeting__date=datetime.date.today()+datetime.timedelta(days=90))
|
||||
SessionPresentationFactory.create(session=session,document__type_id='draft',rev=None)
|
||||
SessionPresentationFactory.create(session=session,document__type_id='minutes')
|
||||
SessionPresentationFactory.create(session=session,document__type_id='slides')
|
||||
SessionPresentationFactory.create(session=session,document__type_id='agenda')
|
||||
|
||||
url = urlreverse('ietf.meeting.views.session_details', kwargs=dict(num=session.meeting.number, acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(all([x in unicontent(r) for x in ('slides','agenda','minutes','draft')]))
|
||||
self.assertFalse('deleted' in unicontent(r))
|
||||
|
||||
def test_add_session_drafts(self):
|
||||
group = GroupFactory.create(type_id='wg',state_id='active')
|
||||
group_chair = PersonFactory.create()
|
||||
group.role_set.create(name_id='chair',person = group_chair, email = group_chair.email())
|
||||
session = SessionFactory.create(meeting__type_id='ietf',group=group, meeting__date=datetime.date.today()+datetime.timedelta(days=90))
|
||||
SessionPresentationFactory.create(session=session,document__type_id='draft',rev=None)
|
||||
old_draft = session.sessionpresentation_set.filter(document__type='draft').first().document
|
||||
new_draft = DocumentFactory(type_id='draft')
|
||||
|
||||
url = urlreverse('ietf.meeting.views.add_session_drafts', kwargs=dict(num=session.meeting.number, session_id=session.pk))
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
self.client.login(username="plain",password="plain+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
self.client.login(username=group_chair.user.username, password='%s+password'%group_chair.user.username)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(old_draft.name in unicontent(r))
|
||||
|
||||
r = self.client.post(url,dict(drafts=[new_draft.name,old_draft.name]))
|
||||
self.assertTrue(r.status_code, 200)
|
||||
q=PyQuery(r.content)
|
||||
self.assertTrue(q('form .alert-danger:contains("Already linked:")'))
|
||||
|
||||
self.assertEqual(1,session.sessionpresentation_set.count())
|
||||
r = self.client.post(url,dict(drafts=[new_draft.name,]))
|
||||
self.assertTrue(r.status_code, 302)
|
||||
self.assertEqual(2,session.sessionpresentation_set.count())
|
||||
|
||||
session.meeting.date -= datetime.timedelta(days=180)
|
||||
session.meeting.save()
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,404)
|
||||
self.client.login(username='secretary',password='secretary+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(1,len(q(".alert-warning:contains('may affect published proceedings')")))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from ietf.meeting import ajax
|
|||
|
||||
safe_for_all_meeting_types = [
|
||||
url(r'^session/(?P<acronym>[A-Za-z0-9_\-\+]+)/$', views.session_details),
|
||||
url(r'^session/(?P<session_id>\d+)/drafts$', views.add_session_drafts),
|
||||
]
|
||||
|
||||
type_ietf_only_patterns = [
|
||||
|
|
73
ietf/meeting/utils.py
Normal file
73
ietf/meeting/utils.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
import datetime
|
||||
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.group.utils import can_manage_materials
|
||||
|
||||
def group_sessions(sessions):
|
||||
|
||||
def sort_key(session):
|
||||
if session.meeting.type.slug=='ietf':
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
elif session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
else:
|
||||
return session.requested
|
||||
else:
|
||||
# TODO: use timeslots for interims once they have them
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
|
||||
for s in sessions:
|
||||
s.time=sort_key(s)
|
||||
|
||||
sessions = sorted(sessions,key=lambda s:s.time,reverse=True)
|
||||
|
||||
today = datetime.date.today()
|
||||
future = []
|
||||
in_progress = []
|
||||
past = []
|
||||
for s in sessions:
|
||||
if s.meeting.date > today:
|
||||
future.append(s)
|
||||
elif s.meeting.end_date() >= today:
|
||||
in_progress.append(s)
|
||||
else:
|
||||
past.append(s)
|
||||
|
||||
return future, in_progress, past
|
||||
|
||||
def get_upcoming_manageable_sessions(user):
|
||||
""" Find all the sessions for meetings that haven't ended that the user could affect """
|
||||
|
||||
# Consider adding an argument that has some Qs to append to the queryset
|
||||
# to allow filtering to a particular group, etc. if we start seeing a lot of code
|
||||
# that calls this function and then immediately starts whittling down the returned list
|
||||
|
||||
# Note the days=15 - this allows this function to find meetings in progress that last up to two weeks.
|
||||
# This notion of searching by end-of-meeting is also present in Document.future_presentations.
|
||||
# It would be nice to make it easier to use querysets to talk about meeting endings wthout a heuristic like this one
|
||||
|
||||
candidate_sessions = Session.objects.exclude(status__in=['canceled','disappr','notmeet','deleted']).filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
|
||||
refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
|
||||
|
||||
return [ sess for sess in refined_candidates if can_manage_materials(user, sess.group) ]
|
||||
|
||||
def sort_sessions(sessions):
|
||||
|
||||
# Python sorts are stable since version 2,2, so this series results in a list sorted first
|
||||
# by the meeting 'number', then by session's group acronym, then by scheduled time
|
||||
# (or the time of the session request if the session isn't scheduled).
|
||||
|
||||
def time_sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
else:
|
||||
return session.requested
|
||||
|
||||
time_sorted = sorted(sessions,key=time_sort_key)
|
||||
acronym_sorted = sorted(time_sorted,key=lambda x: x.group.acronym)
|
||||
meeting_sorted = sorted(acronym_sorted,key=lambda x: x.meeting.number)
|
||||
|
||||
return meeting_sorted
|
|
@ -13,7 +13,7 @@ import json
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from django import forms
|
||||
from django.shortcuts import render, redirect
|
||||
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
|
||||
|
@ -23,7 +23,7 @@ from django.forms.models import modelform_factory
|
|||
from django.forms import ModelForm
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
|
||||
from ietf.doc.models import Document, State
|
||||
from ietf.doc.models import Document, State, DocEvent
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_materials
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
|
@ -39,6 +39,8 @@ from ietf.meeting.helpers import convert_draft_to_pdf
|
|||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.pdf import pdf_pages
|
||||
|
||||
from ietf.doc.fields import SearchableDocumentsField
|
||||
|
||||
def materials(request, meeting_num=None):
|
||||
meeting = get_meeting(meeting_num)
|
||||
|
||||
|
@ -826,7 +828,7 @@ def meeting_requests(request, num=None):
|
|||
{"meeting": meeting, "sessions":sessions,
|
||||
"groups_not_meeting": groups_not_meeting})
|
||||
|
||||
def session_details(request, num, acronym ):
|
||||
def get_sessions(num, acronym):
|
||||
meeting = get_meeting(num=num,type_in=None)
|
||||
sessions = Session.objects.filter(meeting=meeting,group__acronym=acronym,type__in=['session','plenary','other'])
|
||||
|
||||
|
@ -840,7 +842,11 @@ def session_details(request, num, acronym ):
|
|||
else:
|
||||
return session.requested
|
||||
|
||||
sessions = sorted(sessions,key=sort_key)
|
||||
return sorted(sessions,key=sort_key)
|
||||
|
||||
def session_details(request, num, acronym ):
|
||||
meeting = get_meeting(num=num,type_in=None)
|
||||
sessions = get_sessions(num, acronym)
|
||||
|
||||
if not sessions:
|
||||
raise Http404
|
||||
|
@ -874,3 +880,51 @@ def session_details(request, num, acronym ):
|
|||
'can_manage_materials' : can_manage,
|
||||
'type_counter': type_counter,
|
||||
})
|
||||
|
||||
class SessionDraftsForm(forms.Form):
|
||||
drafts = SearchableDocumentsField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.already_linked = kwargs.pop('already_linked')
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
selected = self.cleaned_data['drafts']
|
||||
problems = set(selected).intersection(set(self.already_linked))
|
||||
if problems:
|
||||
raise forms.ValidationError("Already linked: %s" % ', '.join([d.name for d in problems]))
|
||||
return self.cleaned_data
|
||||
|
||||
def add_session_drafts(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):
|
||||
raise Http404
|
||||
if session.is_material_submission_cutoff() and not has_role(request.user, "Secretariat"):
|
||||
raise Http404
|
||||
|
||||
already_linked = [sp.document for sp in session.sessionpresentation_set.filter(document__type_id='draft')]
|
||||
|
||||
session_number = None
|
||||
sessions = get_sessions(session.meeting.number,session.group.acronym)
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(session)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SessionDraftsForm(request.POST,already_linked=already_linked)
|
||||
if form.is_valid():
|
||||
for draft in form.cleaned_data['drafts']:
|
||||
session.sessionpresentation_set.create(document=draft,rev=None)
|
||||
c = DocEvent(type="added_comment", doc=draft, by=request.user.person)
|
||||
c.desc = "Added to session: %s" % session
|
||||
c.save()
|
||||
return redirect('ietf.meeting.views.session_details', num=session.meeting.number, acronym=session.group.acronym)
|
||||
else:
|
||||
form = SessionDraftsForm(already_linked=already_linked)
|
||||
|
||||
return render(request, "meeting/add_session_drafts.html",
|
||||
{ 'session': session,
|
||||
'session_number': session_number,
|
||||
'already_linked': session.sessionpresentation_set.filter(document__type_id='draft'),
|
||||
'form': form,
|
||||
})
|
||||
|
|
25
ietf/templates/doc/add_sessionpresentation.html
Normal file
25
ietf/templates/doc/add_sessionpresentation.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Add document to session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Add document to session<br><small>{{doc.name}}<br>{{doc.title}}</small></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form session_form %}
|
||||
{% bootstrap_form version_form %}
|
||||
|
||||
{% buttons %}
|
||||
<button class="btn btn-primary" type="submit" name="save">Save</button>
|
||||
<a class="btn btn-default" href="{% url 'ietf.doc.views_doc.all_presentations' name=doc.name %}">Cancel</a>
|
||||
{% endbuttons %}
|
||||
|
||||
</form>
|
||||
{% endblock content %}
|
|
@ -250,13 +250,13 @@
|
|||
<th>On Agenda</th>
|
||||
<td class="edit">
|
||||
{% if not snapshot and can_edit_stream_info %}
|
||||
{% doc_edit_button "material_presentations" name=doc.name %}
|
||||
{% doc_edit_button "ietf.doc.views_doc.all_presentations" name=doc.name %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if presentations %}
|
||||
{% for pres in presentations %}{{ pres.session.short_name }} at {{ pres.session.meeting }} {% if pres.rev != doc.rev %}(version -{{ pres.rev }}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
{% for pres in presentations %}{{ pres.session.short_name }} at {{ pres.session.meeting }} {% if pres.rev and pres.rev != doc.rev %}(version -{{ pres.rev }}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<th>On agenda</th>
|
||||
<td class="edit">
|
||||
{% if not snapshot and can_manage_material %}
|
||||
{% doc_edit_button "material_presentations" name=doc.name %}
|
||||
{% doc_edit_button "all_presentations" name=doc.name %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
|
|
28
ietf/templates/doc/edit_sessionpresentation.html
Normal file
28
ietf/templates/doc/edit_sessionpresentation.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Change revision for session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Change document revision for session<br><small>{{sp.document.name}}<br>{{sp.document.title}}<br>at {{sp.session}}</small></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 %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button class="btn btn-primary" type="submit" name="save">Save</button>
|
||||
<a class="btn btn-default" href="{% url 'ietf.doc.views_doc.all_presentations' name=sp.document.name %}">Cancel</a>
|
||||
{% endbuttons %}
|
||||
|
||||
</form>
|
||||
{% endblock content %}
|
52
ietf/templates/doc/material/all_presentations.html
Normal file
52
ietf/templates/doc/material/all_presentations.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin ietf_filters %}
|
||||
|
||||
{% block title %}Sessions linked to {{doc.name}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Sessions linked to <a href="{% url 'doc_view' name=doc.name %}">{{doc.name}}</a>{% if doc.title %}<br><small>{{doc.title}}</small>{% endif %}</h1>
|
||||
|
||||
<div class="buttonlist" >
|
||||
{% if user|has_role:"Secretariat,Area Director,WG Chair,WG Secretary,RG Chair,RG Secretary,IRTF Chair,Team Chair" %}
|
||||
<a class="btn btn-default" id="addsessionsbutton" href="{% url 'ietf.doc.views_doc.add_sessionpresentation' name=doc.name %}">Link to more sessions</a>
|
||||
{% else %}
|
||||
{{user}} failed the has_role check
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if in_progress %}
|
||||
<div class="panel panel-default" id="inprogressmeets">
|
||||
<div class="panel-heading">Meetings in progress</div>
|
||||
<div class="panel-body">
|
||||
{% with sessions=in_progress %}
|
||||
{% include "doc/material/presentations-row.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if future %}
|
||||
<div class="panel panel-default" id="futuremeets">
|
||||
<div class="panel-heading">Future meetings</div>
|
||||
<div class="panel-body">
|
||||
{% with sessions=future %}
|
||||
{% include "doc/material/presentations-row.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if past %}
|
||||
<div class="panel panel-default" id="pastmeets">
|
||||
<div class="panel-heading">Past meetings</div>
|
||||
<div class="panel-body">
|
||||
{% with sessions=past %}
|
||||
{% include "doc/material/presentations-row.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock content %}
|
|
@ -1,23 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Edit Upcoming Presentations{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Edit Upcoming Presentations<br><small>{{doc.title}}<br>{{doc.name}}<br>at {{session}}</small></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock content %}
|
|
@ -1,35 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% block title %}Upcoming Presentations{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Upcoming Presentations<br><small>{{doc.title}}<br>{{doc.name}}</small></h1>
|
||||
|
||||
<ul>
|
||||
{% regroup sessions by has_presentation as is_scheduled_list %}
|
||||
{% for is_scheduled in is_scheduled_list %}
|
||||
<li> {{ is_scheduled.grouper|yesno:"Presentation Scheduled,Presentation Not Scheduled"}}
|
||||
<ul>
|
||||
{% regroup is_scheduled.list by group as group_list %}
|
||||
{% for group in group_list %}
|
||||
{% for session in group.list %}
|
||||
<li>
|
||||
{% if week_day %}
|
||||
<a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter week_day=week_day %}">{{ session }}</a>
|
||||
{% elif date %}
|
||||
<a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter date=date %}">{{ session }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.doc.views_material.edit_material_presentations' name=doc.name acronym=group.grouper.acronym seq=forloop.counter %}">{{ session }}</a>
|
||||
{% endif %}
|
||||
{% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
41
ietf/templates/doc/material/presentations-row.html
Normal file
41
ietf/templates/doc/material/presentations-row.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% load origin %}
|
||||
{% load ietf_filters session_filters %}
|
||||
|
||||
{% origin %}
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">Revision</th>
|
||||
<th class="col-md-2">Meeting</th>
|
||||
<th class="col-md-2">Session</th>
|
||||
<th class="col-md-1">{% comment %}Agenda{% endcomment %}</th>
|
||||
<th class="col-md-1">{% comment %}Minutes{% endcomment %}</th>
|
||||
<th class="col-md-1">{% comment %}Materials{% endcomment %}</th>
|
||||
<th class="col-md-4">{% comment %}Buttons{% endcomment %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in sessions %}
|
||||
<tr>
|
||||
<td>{{s|presented_versions:doc}}</td>
|
||||
<td>{% ifchanged s.meeting %}{% if s.meeting.type.slug == 'ietf' %}IETF{% endif %}{{s.meeting.number}}{% endifchanged %}</td>
|
||||
<td>
|
||||
{% if s.name %}{{ s.name }}<br>{% endif %}
|
||||
{% if s.status.slug == "sched" %}
|
||||
{% if s.meeting.type.slug == 'ietf' %}{{s.time|date:"D M d, Y Hi"}}{% else %}{{s.time|date:"D M d, Y"}}{% endif %}
|
||||
{% else %}
|
||||
{{s.status}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if s.agenda %}<a href="{{ s.agenda.get_absolute_url }}">Agenda</a>{% endif %}</td>
|
||||
<td>{% if s.minutes %}<a href="{{ s.minutes.get_absolute_url }}">Minutes</a>{% endif %}</td>
|
||||
<td><a href="{% url 'ietf.meeting.views.session_details' num=s.meeting.number acronym=s.group.acronym %}">Materials</a></td>
|
||||
<td>
|
||||
{% if user|has_role:"Secretariat" or s|can_manage_materials:user and not s.is_material_submission_cutoff %}
|
||||
<a class="btn btn-{% if s.is_material_submission_cutoff %}warning{% else %}default{% endif %} btn-xs" href="{% url 'ietf.doc.views_doc.remove_sessionpresentation' name=doc.name session_id=s.pk %}">Remove document from session</a>
|
||||
<a class="btn btn-{% if s.is_material_submission_cutoff %}warning{% else %}default{% endif %} btn-xs" href="{% url 'ietf.doc.views_doc.edit_sessionpresentation' name=doc.name session_id=s.pk %}">Change revision</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
31
ietf/templates/doc/remove_sessionpresentation.html
Normal file
31
ietf/templates/doc/remove_sessionpresentation.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Remove {{doc}} from session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Confirm removing document from 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.doc.views_doc.all_presentations' name=sp.document.name %}">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
60
ietf/templates/meeting/add_session_drafts.html
Normal file
60
ietf/templates/meeting/add_session_drafts.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin staticfiles bootstrap3 %}
|
||||
|
||||
{% block title %}Add drafts to {{ session.meeting }} : {{ session.group.acronym }}{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
|
||||
<h1>Add drafts to {{ session.meeting }} {% if session_number %}: Session {{session_number}}{% endif %} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}</h1>
|
||||
{% comment %} TODO: put the session name here or calculate the number at the meeting {% endcomment %}
|
||||
|
||||
{% if session.is_material_submission_cutoff %}
|
||||
<div class="alert alert-warning">The deadline for submission corrections has passed. This may affect published proceedings.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert alert-info">This form will link additional drafts to this session with a revision of "Current at time of presentation". For more fine grained control of versions, or to remove a draft from a session, adjust the sessions associated with a draft from the draft's main page.</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Drafts already linked to this sesssion</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-contensed table-striped">
|
||||
<tr>
|
||||
<th class="col-md-1">Revision</th>
|
||||
<th>Document</th>
|
||||
</tr>
|
||||
{% for sp in already_linked %}
|
||||
<tr>
|
||||
<td>{% if sp.rev %}-{{sp.rev}}{% else %}(current){% endif %}</td>
|
||||
<td>{{sp.document.title}} ({{sp.document.name}})</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Additional drafts to link to this session</div>
|
||||
<div class="panel-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<button class="btn btn-{% if session.is_material_submission_cutoff %}warning{% else %}primary{% endif %}" type="submit">Save</button>
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'select2/select2.min.js' %}"></script>
|
||||
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
|
||||
{% endblock %}
|
|
@ -15,7 +15,10 @@
|
|||
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
|
||||
<div class="buttonlist">
|
||||
<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
|
||||
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 not type_counter.agenda %}
|
||||
<span class="label label-warning">This session does not yet have an agenda</span>
|
||||
|
@ -33,7 +36,12 @@
|
|||
{% if pres.document.type_id != 'bluesheets' and pres.document.type_id != 'recording' %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'doc_view' name=pres.document.name rev=pres.rev%}">{{pres.document.title}} ({{ pres.document.name }}-{{ pres.rev }})
|
||||
{% 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>
|
||||
|
|
Loading…
Reference in a new issue