Merged in [10856] from rjsparks@nostrum.com:

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.
 - Legacy-Id: 10865
Note: SVN reference [10856] has been migrated to Git commit e8d40c8402
This commit is contained in:
Henrik Levkowetz 2016-02-28 19:23:44 +00:00
commit 1fe0883767
27 changed files with 870 additions and 334 deletions

45
ietf/doc/factories.py Normal file
View 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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View 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 %}

View file

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

View file

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

View 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 %}

View 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 %}

View file

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

View file

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

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

View 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 %}

View 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 %}

View file

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