From 78ca39f237945af11cdbefa52b823771a5040033 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 17 Feb 2016 16:38:33 +0000 Subject: [PATCH 1/9] checkpoint: start of a view showing all sessions associated with a document - Legacy-Id: 10844 --- ietf/doc/urls_material.py | 1 + ietf/doc/views_material.py | 17 +++++++ ietf/group/info.py | 31 +------------ ietf/meeting/templatetags/session_filters.py | 12 +++++ ietf/meeting/utils.py | 35 +++++++++++++++ .../doc/material/all_presentations.html | 44 +++++++++++++++++++ .../doc/material/presentations-row.html | 39 ++++++++++++++++ 7 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 ietf/meeting/templatetags/session_filters.py create mode 100644 ietf/meeting/utils.py create mode 100644 ietf/templates/doc/material/all_presentations.html create mode 100644 ietf/templates/doc/material/presentations-row.html diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py index 40fb7fd6c..a760dd1eb 100644 --- a/ietf/doc/urls_material.py +++ b/ietf/doc/urls_material.py @@ -2,6 +2,7 @@ from django.conf.urls import patterns, url urlpatterns = patterns('ietf.doc.views_material', url(r'^(?Pstate|title|abstract|revise)/$', "edit_material", name="material_edit"), + url(r'^meetings/$', "all_presentations", name="all_presentations"), url(r'^sessions/$', "material_presentations", name="material_presentations"), (r'^sessions/(?P\d+)/edit/$', "edit_material_presentations"), (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/edit/$', "edit_material_presentations"), diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py index 2d6327ba3..b872b473c 100644 --- a/ietf/doc/views_material.py +++ b/ietf/doc/views_material.py @@ -18,6 +18,7 @@ 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 +from ietf.meeting.utils import group_sessions @login_required def choose_material_type(request, acronym): @@ -318,3 +319,19 @@ def material_presentations(request, name, acronym=None, date=None, seq=None, wee 'date': date, 'week_day': week_day, }) + +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', { + 'doc': doc, + 'future': future, + 'in_progress': in_progress, + 'past' : past, + }) diff --git a/ietf/group/info.py b/ietf/group/info.py index 6bfc97e89..ecb3fbbb4 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -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") @@ -737,35 +738,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"]) diff --git a/ietf/meeting/templatetags/session_filters.py b/ietf/meeting/templatetags/session_filters.py new file mode 100644 index 000000000..3a7ba0fbb --- /dev/null +++ b/ietf/meeting/templatetags/session_filters.py @@ -0,0 +1,12 @@ +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)" diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py new file mode 100644 index 000000000..1308ecbb6 --- /dev/null +++ b/ietf/meeting/utils.py @@ -0,0 +1,35 @@ +import datetime + +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 diff --git a/ietf/templates/doc/material/all_presentations.html b/ietf/templates/doc/material/all_presentations.html new file mode 100644 index 000000000..2af035eb9 --- /dev/null +++ b/ietf/templates/doc/material/all_presentations.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} + +{% block title %}Sessions linked to {{doc.name}}{% endblock %} + +{% block content %} + {% origin %} +

Sessions linked to {{doc.name}}{% if doc.title %}
{{doc.title}}{% endif %}

+ +{% if in_progress %} +
+
Meetings in progress
+
+ {% with sessions=in_progress %} + {% include "doc/material/presentations-row.html" %} + {% endwith %} +
+
+{% endif %} + +{% if future %} +
+
Future meetings
+
+ {% with sessions=future %} + {% include "doc/material/presentations-row.html" %} + {% endwith %} +
+
+{% endif %} + +{% if past %} +
+
Past meetings
+
+ {% with sessions=past %} + {% include "doc/material/presentations-row.html" %} + {% endwith %} +
+
+{% endif %} + +{% endblock content %} diff --git a/ietf/templates/doc/material/presentations-row.html b/ietf/templates/doc/material/presentations-row.html new file mode 100644 index 000000000..1e49b9619 --- /dev/null +++ b/ietf/templates/doc/material/presentations-row.html @@ -0,0 +1,39 @@ +{% load session_filters %} + + + + + + + + + + + + + {% for s in sessions %} + + + + + + + + {% endfor %} + +
RevisionMeetingSession
{{s|presented_versions:doc}}{% ifchanged s.meeting %}{% if s.meeting.type.slug == 'ietf' %}IETF{% endif %}{{s.meeting.number}}{% endifchanged %} + {% if s.name %}{{ s.name }}
{% 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 %} +{% comment %} + {% if show_request and s.meeting.type.slug == 'ietf' %} + {% if can_edit %} +
+ Edit Session Request + {% endif %} + {% endif %} +{% endcomment %} +
{% if s.agenda %}Agenda{% endif %}{% if s.minutes %}Minutes{% endif %}Materials
From d709af6ca29d5dadd44f874ded9a3887b939dea6 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Thu, 18 Feb 2016 04:07:17 +0000 Subject: [PATCH 2/9] checkpoint: refined document/session view with test. Trivial start on Document factories. - Legacy-Id: 10845 --- ietf/doc/factories.py | 22 +++++++ ietf/doc/tests.py | 66 +++++++++++++++++++ ietf/doc/views_material.py | 1 + ...0019_allow_null_sessionpresentation_rev.py | 20 ++++++ ietf/meeting/models.py | 9 ++- ietf/meeting/templatetags/session_filters.py | 5 ++ .../doc/material/all_presentations.html | 13 +++- .../doc/material/presentations-row.html | 25 +++---- 8 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 ietf/doc/factories.py create mode 100644 ietf/meeting/migrations/0019_allow_null_sessionpresentation_rev.py diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py new file mode 100644 index 000000000..06d41951b --- /dev/null +++ b/ietf/doc/factories.py @@ -0,0 +1,22 @@ +import factory + +from ietf.doc.models import Document + +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, + ) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 24ba04318..4b2d674f5 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -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,65 @@ 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') + + 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)) + SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today+datetime.timedelta(days=90)) + 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) + + url = urlreverse('ietf.doc.views_material.all_presentations', kwargs=dict(name=doc.name)) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + q = PyQuery(response.content) + self.assertTrue(q('#inprogressmeets')) + self.assertFalse(any([q(id) for id in ['#pastmeets','#futuremeets']])) + 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("#pastmeets a.btn-default:contains('Remove document')"))) + self.assertEqual(1,len(q("#pastmeets a.btn-warning:contains('Remove document')"))) + + group_chair = PersonFactory() + self.group.role_set.create(name_id='chair',person=group_chair,email=group_chair.email()) + self.client.login(username=group_chair.user.username,password='%s+password'%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("#pastmeets a.btn-default:contains('Remove document')"))) + self.assertTrue(q('#pastmeets')) + self.assertFalse(q("#pastmeets a.btn-warning:contains('Remove document')")) + + other_group = GroupFactory(type_id='wg',state_id='active') + other_chair = PersonFactory() + other_group.role_set.create(name_id='chair',person=other_chair,email=group_chair.email()) + self.client.login(username=other_chair.user.username,password='%s+password'%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 ['#pastmeets','#inprogressmeets']])) + self.assertFalse(q("#inprogressmeets a.btn:contains('Remove document')")) + self.assertFalse(q("#pastmeets a.btn:contains('Remove document')")) diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py index b872b473c..f4763fda6 100644 --- a/ietf/doc/views_material.py +++ b/ietf/doc/views_material.py @@ -330,6 +330,7 @@ def all_presentations(request, name): 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, diff --git a/ietf/meeting/migrations/0019_allow_null_sessionpresentation_rev.py b/ietf/meeting/migrations/0019_allow_null_sessionpresentation_rev.py new file mode 100644 index 000000000..b427f8db7 --- /dev/null +++ b/ietf/meeting/migrations/0019_allow_null_sessionpresentation_rev.py @@ -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, + ), + ] diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 50fbe2eae..59f74e7d7 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -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 diff --git a/ietf/meeting/templatetags/session_filters.py b/ietf/meeting/templatetags/session_filters.py index 3a7ba0fbb..2a3da7028 100644 --- a/ietf/meeting/templatetags/session_filters.py +++ b/ietf/meeting/templatetags/session_filters.py @@ -10,3 +10,8 @@ def presented_versions(session,doc): 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) + diff --git a/ietf/templates/doc/material/all_presentations.html b/ietf/templates/doc/material/all_presentations.html index 2af035eb9..05d465e35 100644 --- a/ietf/templates/doc/material/all_presentations.html +++ b/ietf/templates/doc/material/all_presentations.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2015, All Rights Reserved #} -{% load origin %} +{% load origin ietf_filters %} {% block title %}Sessions linked to {{doc.name}}{% endblock %} @@ -8,6 +8,17 @@ {% origin %}

Sessions linked to {{doc.name}}{% if doc.title %}
{{doc.title}}{% endif %}

+
+ {% comment TODO %} + Add doc to next session for this group + {% endcomment %} + {% if user|has_role:"Secretariat,Area Director,WG Chair,WG Secretary,RG Chair,RG Secretary,IRTF Chair,Team Chair" %} + Link to more sessions + {% else %} + {{user}} failed the has_role check + {% endif %} +
+ {% if in_progress %}
Meetings in progress
diff --git a/ietf/templates/doc/material/presentations-row.html b/ietf/templates/doc/material/presentations-row.html index 1e49b9619..042c29d2e 100644 --- a/ietf/templates/doc/material/presentations-row.html +++ b/ietf/templates/doc/material/presentations-row.html @@ -1,13 +1,17 @@ -{% load session_filters %} +{% load origin %} +{% load ietf_filters session_filters %} + + {% origin %} - - - + + + + @@ -22,18 +26,15 @@ {% else %} {{s.status}} {% endif %} -{% comment %} - {% if show_request and s.meeting.type.slug == 'ietf' %} - {% if can_edit %} -
- Edit Session Request - {% endif %} - {% endif %} -{% endcomment %} + {% endfor %}
Revision Meeting Session{% comment %}Agenda{% endcomment %}{% comment %}Minutes{% endcomment %}{% comment %}Materials{% endcomment %}{% comment %}Buttons{% endcomment %}
{% if s.agenda %}Agenda{% endif %} {% if s.minutes %}Minutes{% endif %} Materials + {% if user|has_role:"Secretariat" or s|can_manage_materials:user and not s.is_material_submission_cutoff %} + Remove document from session + {% endif %} +
From 859f2ead3dca021695f2296c7a28edfd9e4a7b7f Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 19 Feb 2016 04:20:57 +0000 Subject: [PATCH 3/9] checkpoint: purged the rest of the day/seq URLs. Streamlined workflows to focus on "current" versions as a default. Plumbed editing and deleting sessionpresentations from a document perspective. Started adding meat to document factories. - Legacy-Id: 10847 --- ietf/doc/factories.py | 25 ++- ietf/doc/tests.py | 132 ++++++++++++++-- ietf/doc/tests_material.py | 68 +------- ietf/doc/urls.py | 6 + ietf/doc/urls_material.py | 15 -- ietf/doc/views_doc.py | 62 ++++++++ ietf/doc/views_material.py | 147 ------------------ ietf/templates/doc/document_draft.html | 4 +- .../doc/edit_sessionpresentation.html | 28 ++++ .../material/edit_material_presentations.html | 23 --- .../doc/material/material_presentations.html | 35 ----- .../doc/material/presentations-row.html | 3 +- .../doc/remove_sessionpresentation.html | 31 ++++ 13 files changed, 276 insertions(+), 303 deletions(-) create mode 100644 ietf/templates/doc/edit_sessionpresentation.html delete mode 100644 ietf/templates/doc/material/edit_material_presentations.html delete mode 100644 ietf/templates/doc/material/material_presentations.html create mode 100644 ietf/templates/doc/remove_sessionpresentation.html diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py index 06d41951b..c435bfc41 100644 --- a/ietf/doc/factories.py +++ b/ietf/doc/factories.py @@ -1,6 +1,7 @@ import factory -from ietf.doc.models import Document +from ietf.doc.models import Document, DocEvent, NewRevisionDocEvent +from ietf.person.factories import PersonFactory class DocumentFactory(factory.DjangoModelFactory): class Meta: @@ -20,3 +21,25 @@ class DocumentFactory(factory.DjangoModelFactory): '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) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 4b2d674f5..0d4d50e7a 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -913,25 +913,32 @@ 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)) - SessionFactory.create(meeting__type_id='ietf',group=self.group,meeting__date=today+datetime.timedelta(days=90)) - SessionFactory.create(meeting__type_id='interim',group=self.group,meeting__date=today+datetime.timedelta(days=45)) + 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_material.all_presentations', kwargs=dict(name=doc.name)) response = self.client.get(url) self.assertEqual(response.status_code, 200) q = PyQuery(response.content) - self.assertTrue(q('#inprogressmeets')) - self.assertFalse(any([q(id) for id in ['#pastmeets','#futuremeets']])) + 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')")) @@ -944,29 +951,130 @@ class DocumentMeetingTests(TestCase): 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')"))) - group_chair = PersonFactory() - self.group.role_set.create(name_id='chair',person=group_chair,email=group_chair.email()) - self.client.login(username=group_chair.user.username,password='%s+password'%group_chair.user.username) + 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')")) - other_group = GroupFactory(type_id='wg',state_id='active') - other_chair = PersonFactory() - other_group.role_set.create(name_id='chair',person=other_chair,email=group_chair.email()) - self.client.login(username=other_chair.user.username,password='%s+password'%other_chair.user.username) + 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 ['#pastmeets','#inprogressmeets']])) + 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'))) + + # Simulate the cancel button + response = self.client.post(url,{}) + self.assertEqual(response.status_code, 302) + self.assertEqual(doc.sessionpresentation_set.get(pk=sp.pk).rev,None) + + self.assertEqual(1,doc.docevent_set.count()) + response = self.client.post(url,{'version':'00','save':''}) + response = self.client.post(url,{}) + 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_self(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) + + # Simulate the cancel button + response = self.client.post(url,{}) + self.assertEqual(response.status_code, 302) + self.assertTrue(doc.sessionpresentation_set.filter(pk=sp.pk).exists()) + + 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')"))) diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py index dd01d787f..fee6560c4 100644 --- a/ietf/doc/tests_material.py +++ b/ietf/doc/tests_material.py @@ -10,7 +10,7 @@ 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.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,8 +18,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): self.materials_dir = os.path.abspath("tmp-document-dir") @@ -173,67 +171,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) - - diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index 82bebc121..9d9880316 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -37,6 +37,11 @@ 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'^(?P\d+)/edit$', views_doc.edit_sessionpresentation), + url(r'^(?P\d+)/remove$', views_doc.remove_sessionpresentation), +] + urlpatterns = patterns('', (r'^/?$', views_search.search), url(r'^(?P[A-Za-z0-9\._\+\-]+)$', views_search.search_for_name, name="doc_search_for_name"), @@ -113,4 +118,5 @@ urlpatterns = patterns('', (r'^(?P[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')), (r'^(?P[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')), (r'^(?P[A-Za-z0-9._+-]+)/material/', include('ietf.doc.urls_material')), + url(r'^(?P[A-Za-z0-9._+-]+)/session/', include(session_patterns)), ) diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py index a760dd1eb..5b6eac4fc 100644 --- a/ietf/doc/urls_material.py +++ b/ietf/doc/urls_material.py @@ -3,20 +3,5 @@ from django.conf.urls import patterns, url urlpatterns = patterns('ietf.doc.views_material', url(r'^(?Pstate|title|abstract|revise)/$', "edit_material", name="material_edit"), url(r'^meetings/$', "all_presentations", name="all_presentations"), - url(r'^sessions/$', "material_presentations", name="material_presentations"), - (r'^sessions/(?P\d+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P[a-zA-Z]+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P[a-zA-Z]+)/(?P\d+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d{4}-\d{2}-\d{2}(-\d{4})?)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P\d+)/edit/$', "edit_material_presentations"), - (r'^sessions/(?P\d+)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d+)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P[a-zA-Z]+)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P[a-zA-Z]+)/(?P\d+)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d{4}-\d{2}-\d{2}(-\d{4})?)/$', "material_presentations"), - (r'^sessions/(?P[A-Za-z0-9_\-\+]+)/(?P\d{4}-\d{2}-\d{2}(-\d{4})?)/(?P\d+)/$', "material_presentations"), ) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index eca4b693a..b4b8cacbe 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -1091,3 +1091,65 @@ def email_aliases(request,name=''): return render(request,'doc/email_aliases.html',{'aliases':aliases,'ietf_domain':settings.IETF_DOMAIN,'doc':doc}) +class SessionPresentationForm(forms.Form): + + version = forms.ChoiceField(required=False, + label='Which version of this document will be discussed at this session?') + + def __init__(self, *args, **kwargs): + choices = kwargs.pop('choices') + super(SessionPresentationForm,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': + if 'save' in request.POST: + form = SessionPresentationForm(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_material.all_presentations', name=name) + else: + return redirect('ietf.doc.views_material.all_presentations', name=name) + else: + form = SessionPresentationForm(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': + if 'remove_session' in request.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_material.all_presentations', name=name) + + return render(request,'doc/remove_sessionpresentation.html', {'sp': sp }) diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py index f4763fda6..281b62651 100644 --- a/ietf/doc/views_material.py +++ b/ietf/doc/views_material.py @@ -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 from ietf.meeting.utils import group_sessions @login_required @@ -174,152 +173,6 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None): '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, - }) - def all_presentations(request, name): doc = get_object_or_404(Document, name=name) diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index 40a1a4918..e73b19682 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -250,13 +250,13 @@ On Agenda {% if not snapshot and can_edit_stream_info %} - {% doc_edit_button "material_presentations" name=doc.name %} + {% doc_edit_button "ietf.doc.views_material.all_presentations" name=doc.name %} {% endif %} {% 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 %} diff --git a/ietf/templates/doc/edit_sessionpresentation.html b/ietf/templates/doc/edit_sessionpresentation.html new file mode 100644 index 000000000..7e6e5b15c --- /dev/null +++ b/ietf/templates/doc/edit_sessionpresentation.html @@ -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 %} +

Change document revision for session
{{sp.document.name}}
{{sp.document.title}}
at {{sp.session}}

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

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

+ {% endif %} + +
+ {% csrf_token %} + + {% bootstrap_form form %} + + {% buttons %} + + + {% endbuttons %} + +
+{% endblock content %} diff --git a/ietf/templates/doc/material/edit_material_presentations.html b/ietf/templates/doc/material/edit_material_presentations.html deleted file mode 100644 index 596aa9862..000000000 --- a/ietf/templates/doc/material/edit_material_presentations.html +++ /dev/null @@ -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 %} -

Edit Upcoming Presentations
{{doc.title}}
{{doc.name}}
at {{session}}

- -
- {% csrf_token %} - - {% bootstrap_form form %} - - {% buttons %} - Back - - {% endbuttons %} -
-{% endblock content %} diff --git a/ietf/templates/doc/material/material_presentations.html b/ietf/templates/doc/material/material_presentations.html deleted file mode 100644 index b54cdaf0a..000000000 --- a/ietf/templates/doc/material/material_presentations.html +++ /dev/null @@ -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 %} -

Upcoming Presentations
{{doc.title}}
{{doc.name}}

- -
    - {% regroup sessions by has_presentation as is_scheduled_list %} - {% for is_scheduled in is_scheduled_list %} -
  • {{ is_scheduled.grouper|yesno:"Presentation Scheduled,Presentation Not Scheduled"}} -
      - {% regroup is_scheduled.list by group as group_list %} - {% for group in group_list %} - {% for session in group.list %} -
    • - {% if week_day %} - {{ session }} - {% elif date %} - {{ session }} - {% else %} - {{ session }} - {% endif %} - {% if session.versions %} (version{{session.versions|pluralize}} {{session.versions|join:','}}) {% endif %} -
    • - {% endfor %} - {% endfor %} -
    -
  • - {% endfor %} -
-{% endblock content %} diff --git a/ietf/templates/doc/material/presentations-row.html b/ietf/templates/doc/material/presentations-row.html index 042c29d2e..74bad9049 100644 --- a/ietf/templates/doc/material/presentations-row.html +++ b/ietf/templates/doc/material/presentations-row.html @@ -32,7 +32,8 @@ Materials {% if user|has_role:"Secretariat" or s|can_manage_materials:user and not s.is_material_submission_cutoff %} - Remove document from session + Remove document from session + Change revision {% endif %} {% endfor %} diff --git a/ietf/templates/doc/remove_sessionpresentation.html b/ietf/templates/doc/remove_sessionpresentation.html new file mode 100644 index 000000000..c3666c88c --- /dev/null +++ b/ietf/templates/doc/remove_sessionpresentation.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} + +{% load bootstrap3 %} + +{% block title %}Remove {{doc}} from session{% endblock %} + +{% block content %} + {% origin %} +

Confirm removing document from session

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

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

+ {% endif %} + +

Document

+

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

+

{{sp.document.title}}

+

Session

+

{{sp.session}}

+ +
+ {% csrf_token %} + {% buttons %} + + + {% endbuttons %} +
+ +{% endblock %} From fe17f6d87b313dac6725b23bea29bd65b5251f08 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 19 Feb 2016 23:13:23 +0000 Subject: [PATCH 4/9] checkpoint: Allows adding a document to upcoming sessions - Legacy-Id: 10849 --- ietf/doc/tests.py | 36 ++++-- ietf/doc/urls.py | 1 + ietf/doc/views_doc.py | 117 ++++++++++++++---- .../doc/add_sessionpresentation.html | 25 ++++ .../doc/edit_sessionpresentation.html | 2 +- .../doc/material/all_presentations.html | 5 +- .../doc/remove_sessionpresentation.html | 2 +- 7 files changed, 149 insertions(+), 39 deletions(-) create mode 100644 ietf/templates/doc/add_sessionpresentation.html diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 0d4d50e7a..362163ecd 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -1002,14 +1002,8 @@ class DocumentMeetingTests(TestCase): q = PyQuery(response.content) self.assertEqual(2,len(q('select#id_version option'))) - # Simulate the cancel button - response = self.client.post(url,{}) - self.assertEqual(response.status_code, 302) - self.assertEqual(doc.sessionpresentation_set.get(pk=sp.pk).rev,None) - self.assertEqual(1,doc.docevent_set.count()) response = self.client.post(url,{'version':'00','save':''}) - response = self.client.post(url,{}) self.assertEqual(response.status_code, 302) self.assertEqual(doc.sessionpresentation_set.get(pk=sp.pk).rev,'00') self.assertEqual(2,doc.docevent_set.count()) @@ -1029,7 +1023,7 @@ class DocumentMeetingTests(TestCase): q=PyQuery(response.content) self.assertEqual(1,len(q(".alert-warning:contains('may affect published proceedings')"))) - def test_remove_document_self(self): + def test_remove_document_session(self): doc = DocumentFactory.create() sp = doc.sessionpresentation_set.create(session=self.future,rev=None) @@ -1053,11 +1047,6 @@ class DocumentMeetingTests(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, 200) - # Simulate the cancel button - response = self.client.post(url,{}) - self.assertEqual(response.status_code, 302) - self.assertTrue(doc.sessionpresentation_set.filter(pk=sp.pk).exists()) - self.assertEqual(1,doc.docevent_set.count()) response = self.client.post(url,{'remove_session':''}) self.assertEqual(response.status_code, 302) @@ -1078,3 +1067,26 @@ class DocumentMeetingTests(TestCase): 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()) diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index 9d9880316..e4ffd7ccd 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -38,6 +38,7 @@ 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\d+)/edit$', views_doc.edit_sessionpresentation), url(r'^(?P\d+)/remove$', views_doc.remove_sessionpresentation), ] diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index b4b8cacbe..270007a70 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -60,6 +60,7 @@ 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 def render_document_top(request, doc, tab, name): tabs = [] @@ -1091,14 +1092,15 @@ def email_aliases(request,name=''): return render(request,'doc/email_aliases.html',{'aliases':aliases,'ietf_domain':settings.IETF_DOMAIN,'doc':doc}) -class SessionPresentationForm(forms.Form): +class VersionForm(forms.Form): - version = forms.ChoiceField(required=False, + # TODO is required=False correct here? + 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(SessionPresentationForm,self).__init__(*args,**kwargs) + super(VersionForm,self).__init__(*args,**kwargs) self.fields['version'].choices = choices def edit_sessionpresentation(request,name,session_id): @@ -1116,20 +1118,17 @@ def edit_sessionpresentation(request,name,session_id): initial = {'version' : sp.rev if sp.rev else 'current'} if request.method == 'POST': - if 'save' in request.POST: - form = SessionPresentationForm(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_material.all_presentations', name=name) - else: + 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_material.all_presentations', name=name) else: - form = SessionPresentationForm(choices=choices,initial=initial) + form = VersionForm(choices=choices,initial=initial) return render(request,'doc/edit_sessionpresentation.html', {'sp': sp, 'form': form }) @@ -1144,12 +1143,88 @@ def remove_sessionpresentation(request,name,session_id): raise Http404 if request.method == 'POST': - if 'remove_session' in request.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() - + 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_material.all_presentations', name=name) return render(request,'doc/remove_sessionpresentation.html', {'sp': sp }) + + +def get_upcoming_manageable_sessions(user): + + # TODO: Move this into meeting.models or utils, or maybe person.models or utils - it doesn't depend on doc + # 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 + + # Consider adding an argument that has some Qs to append to the queryset + + 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()] + + # Consider keeping this (put acronym=None back in argument list + #if acronym: + # refined_candidates = [ sess for sess in refined_candidates if sess.group.acronym==acronym] + + 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 + +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_material.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}) diff --git a/ietf/templates/doc/add_sessionpresentation.html b/ietf/templates/doc/add_sessionpresentation.html new file mode 100644 index 000000000..cb7aea5ce --- /dev/null +++ b/ietf/templates/doc/add_sessionpresentation.html @@ -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 %} +

Add document to session
{{doc.name}}
{{doc.title}}

+ +
+ {% csrf_token %} + + {% bootstrap_form session_form %} + {% bootstrap_form version_form %} + + {% buttons %} + + Cancel + {% endbuttons %} + +
+{% endblock content %} diff --git a/ietf/templates/doc/edit_sessionpresentation.html b/ietf/templates/doc/edit_sessionpresentation.html index 7e6e5b15c..379c7a93f 100644 --- a/ietf/templates/doc/edit_sessionpresentation.html +++ b/ietf/templates/doc/edit_sessionpresentation.html @@ -21,7 +21,7 @@ {% buttons %} - + Cancel {% endbuttons %} diff --git a/ietf/templates/doc/material/all_presentations.html b/ietf/templates/doc/material/all_presentations.html index 05d465e35..f81d883ee 100644 --- a/ietf/templates/doc/material/all_presentations.html +++ b/ietf/templates/doc/material/all_presentations.html @@ -9,11 +9,8 @@

Sessions linked to {{doc.name}}{% if doc.title %}
{{doc.title}}{% endif %}

- {% comment TODO %} - Add doc to next session for this group - {% endcomment %} {% if user|has_role:"Secretariat,Area Director,WG Chair,WG Secretary,RG Chair,RG Secretary,IRTF Chair,Team Chair" %} - Link to more sessions + Link to more sessions {% else %} {{user}} failed the has_role check {% endif %} diff --git a/ietf/templates/doc/remove_sessionpresentation.html b/ietf/templates/doc/remove_sessionpresentation.html index c3666c88c..d63a40a52 100644 --- a/ietf/templates/doc/remove_sessionpresentation.html +++ b/ietf/templates/doc/remove_sessionpresentation.html @@ -24,7 +24,7 @@ {% csrf_token %} {% buttons %} - + Cancel {% endbuttons %} From 1a2b885864e84b5ba78fec423d30ed144c0aad86 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 22 Feb 2016 17:03:46 +0000 Subject: [PATCH 5/9] checkpoint. Refactor to put session oriented utilites back in the meeting app. Minimize the use of the /materials url. Provide better access control to the remaining materials url functions. - Legacy-Id: 10850 --- ietf/doc/tests.py | 2 +- ietf/doc/tests_material.py | 3 + ietf/doc/urls.py | 1 + ietf/doc/urls_material.py | 1 - ietf/doc/views_doc.py | 62 +++++++------------ ietf/doc/views_material.py | 23 ++----- ietf/meeting/utils.py | 38 ++++++++++++ .../doc/add_sessionpresentation.html | 2 +- ietf/templates/doc/document_draft.html | 2 +- ietf/templates/doc/document_material.html | 2 +- .../doc/edit_sessionpresentation.html | 2 +- .../doc/material/all_presentations.html | 2 +- .../doc/remove_sessionpresentation.html | 2 +- 13 files changed, 75 insertions(+), 67 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 362163ecd..4fb350e4c 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -933,7 +933,7 @@ class DocumentMeetingTests(TestCase): doc.sessionpresentation_set.create(session=self.inprog,rev=None) doc.sessionpresentation_set.create(session=self.interim,rev=None) - url = urlreverse('ietf.doc.views_material.all_presentations', kwargs=dict(name=doc.name)) + 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) diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py index fee6560c4..f16858cb4 100644 --- a/ietf/doc/tests_material.py +++ b/ietf/doc/tests_material.py @@ -6,6 +6,8 @@ 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 @@ -18,6 +20,7 @@ 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 + class GroupMaterialTests(TestCase): def setUp(self): self.materials_dir = os.path.abspath("tmp-document-dir") diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index e4ffd7ccd..235a95c17 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -118,6 +118,7 @@ urlpatterns = patterns('', (r'^(?Pcharter-[A-Za-z0-9._+-]+)/', include('ietf.doc.urls_charter')), (r'^(?P[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')), (r'^(?P[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')), + url(r'^(?P[A-Za-z0-9._+-]+)/meetings$', 'ietf.doc.views_doc.all_presentations', name="all_presentations"), (r'^(?P[A-Za-z0-9._+-]+)/material/', include('ietf.doc.urls_material')), url(r'^(?P[A-Za-z0-9._+-]+)/session/', include(session_patterns)), ) diff --git a/ietf/doc/urls_material.py b/ietf/doc/urls_material.py index 5b6eac4fc..1f104336d 100644 --- a/ietf/doc/urls_material.py +++ b/ietf/doc/urls_material.py @@ -2,6 +2,5 @@ from django.conf.urls import patterns, url urlpatterns = patterns('ietf.doc.views_material', url(r'^(?Pstate|title|abstract|revise)/$', "edit_material", name="material_edit"), - url(r'^meetings/$', "all_presentations", name="all_presentations"), ) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 270007a70..f21693fce 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -61,6 +61,7 @@ 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 = [] @@ -1094,7 +1095,6 @@ def email_aliases(request,name=''): class VersionForm(forms.Form): - # TODO is required=False correct here? version = forms.ChoiceField(required=True, label='Which version of this document will be discussed at this session?') @@ -1126,7 +1126,7 @@ def edit_sessionpresentation(request,name,session_id): 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_material.all_presentations', name=name) + return redirect('ietf.doc.views_doc.all_presentations', name=name) else: form = VersionForm(choices=choices,initial=initial) @@ -1147,47 +1147,10 @@ def remove_sessionpresentation(request,name,session_id): 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_material.all_presentations', name=name) + return redirect('ietf.doc.views_doc.all_presentations', name=name) return render(request,'doc/remove_sessionpresentation.html', {'sp': sp }) - -def get_upcoming_manageable_sessions(user): - - # TODO: Move this into meeting.models or utils, or maybe person.models or utils - it doesn't depend on doc - # 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 - - # Consider adding an argument that has some Qs to append to the queryset - - 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()] - - # Consider keeping this (put acronym=None back in argument list - #if acronym: - # refined_candidates = [ sess for sess in refined_candidates if sess.group.acronym==acronym] - - 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 - class SessionChooserForm(forms.Form): session = forms.ChoiceField(label="Which session should this document be added to?",required=True) @@ -1221,10 +1184,27 @@ def add_sessionpresentation(request,name): 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_material.all_presentations', name=name) + 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, + }) diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py index 281b62651..ea258d98c 100644 --- a/ietf/doc/views_material.py +++ b/ietf/doc/views_material.py @@ -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.utils import group_sessions @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,20 +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 "", }) - -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, - }) diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index 1308ecbb6..4a48ca746 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -1,5 +1,8 @@ import datetime +from ietf.meeting.models import Session +from ietf.group.utils import can_manage_materials + def group_sessions(sessions): def sort_key(session): @@ -33,3 +36,38 @@ def group_sessions(sessions): 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 diff --git a/ietf/templates/doc/add_sessionpresentation.html b/ietf/templates/doc/add_sessionpresentation.html index cb7aea5ce..72d9f0195 100644 --- a/ietf/templates/doc/add_sessionpresentation.html +++ b/ietf/templates/doc/add_sessionpresentation.html @@ -18,7 +18,7 @@ {% buttons %} - Cancel + Cancel {% endbuttons %} diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index e73b19682..cc53f7929 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -250,7 +250,7 @@ On Agenda {% if not snapshot and can_edit_stream_info %} - {% doc_edit_button "ietf.doc.views_material.all_presentations" name=doc.name %} + {% doc_edit_button "ietf.doc.views_doc.all_presentations" name=doc.name %} {% endif %} diff --git a/ietf/templates/doc/document_material.html b/ietf/templates/doc/document_material.html index b1f733409..2d2d56636 100644 --- a/ietf/templates/doc/document_material.html +++ b/ietf/templates/doc/document_material.html @@ -93,7 +93,7 @@ On agenda {% if not snapshot and can_manage_material %} - {% doc_edit_button "material_presentations" name=doc.name %} + {% doc_edit_button "all_presentations" name=doc.name %} {% endif %} diff --git a/ietf/templates/doc/edit_sessionpresentation.html b/ietf/templates/doc/edit_sessionpresentation.html index 379c7a93f..549078960 100644 --- a/ietf/templates/doc/edit_sessionpresentation.html +++ b/ietf/templates/doc/edit_sessionpresentation.html @@ -21,7 +21,7 @@ {% buttons %} - Cancel + Cancel {% endbuttons %} diff --git a/ietf/templates/doc/material/all_presentations.html b/ietf/templates/doc/material/all_presentations.html index f81d883ee..80577dade 100644 --- a/ietf/templates/doc/material/all_presentations.html +++ b/ietf/templates/doc/material/all_presentations.html @@ -6,7 +6,7 @@ {% block content %} {% origin %} -

Sessions linked to {{doc.name}}{% if doc.title %}
{{doc.title}}{% endif %}

+

Sessions linked to {{doc.name}}{% if doc.title %}
{{doc.title}}{% endif %}

{% if user|has_role:"Secretariat,Area Director,WG Chair,WG Secretary,RG Chair,RG Secretary,IRTF Chair,Team Chair" %} diff --git a/ietf/templates/doc/remove_sessionpresentation.html b/ietf/templates/doc/remove_sessionpresentation.html index d63a40a52..919fcc224 100644 --- a/ietf/templates/doc/remove_sessionpresentation.html +++ b/ietf/templates/doc/remove_sessionpresentation.html @@ -24,7 +24,7 @@ {% csrf_token %} {% buttons %} - Cancel + Cancel {% endbuttons %} From 4a122257427f4190d803737ecbe3b0f244f89aee Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Mon, 22 Feb 2016 19:56:14 +0000 Subject: [PATCH 6/9] Found (manually) and fixed a place where a sessionpresentation object's rev=None was exploding a template. FactoryBoy starts to really shine. - Legacy-Id: 10851 --- ietf/meeting/factories.py | 11 ++++++++- ietf/meeting/tests_views.py | 27 +++++++++++++++------ ietf/templates/meeting/session_details.html | 7 +++++- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/ietf/meeting/factories.py b/ietf/meeting/factories.py index 469c62219..247fe8fbe 100644 --- a/ietf/meeting/factories.py +++ b/ietf/meeting/factories.py @@ -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 diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 0e349bb6d..f5855ce99 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -13,6 +13,9 @@ 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.group.factories import GroupFactory +from ietf.meeting.factories import SessionFactory, SessionPresentationFactory + class MeetingTests(TestCase): def setUp(self): self.materials_dir = os.path.abspath(settings.TEST_MATERIALS_DIR) @@ -156,13 +159,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 +326,20 @@ 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)) + diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index ade616a2a..fc012d784 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -33,7 +33,12 @@ {% if pres.document.type_id != 'bluesheets' and pres.document.type_id != 'recording' %} - {{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 %} + {{pres.document.title}} ({{ pres.document.name }}-{{ pres.rev }}) From 114ba0ad13b894339eb6463f9e08786cdc3d6bc2 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 23 Feb 2016 04:00:16 +0000 Subject: [PATCH 7/9] winding up: added ability to add drafts to a session while looking at the session. - Legacy-Id: 10853 --- ietf/meeting/tests_views.py | 49 ++++++++++++++- ietf/meeting/urls.py | 1 + ietf/meeting/views.py | 48 ++++++++++++++- .../templates/meeting/add_session_drafts.html | 60 +++++++++++++++++++ ietf/templates/meeting/session_details.html | 7 ++- 5 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 ietf/templates/meeting/add_session_drafts.html diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index f5855ce99..9264c6ff8 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -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,8 +15,10 @@ 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): @@ -338,8 +342,51 @@ class SessionDetailsTests(TestCase): 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)) + 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')"))) + diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 380b26241..9c8ef3af8 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -8,6 +8,7 @@ from ietf.meeting import ajax safe_for_all_meeting_types = [ url(r'^session/(?P[A-Za-z0-9_\-\+]+)/$', views.session_details), + url(r'^session/(?P\d+)/drafts$', views.add_session_drafts), ] type_ietf_only_patterns = [ diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 7f8296667..a67040835 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -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) @@ -874,3 +876,45 @@ 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')] + + 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, + 'already_linked': session.sessionpresentation_set.filter(document__type_id='draft'), + 'form': form, + }) diff --git a/ietf/templates/meeting/add_session_drafts.html b/ietf/templates/meeting/add_session_drafts.html new file mode 100644 index 000000000..3fa968f00 --- /dev/null +++ b/ietf/templates/meeting/add_session_drafts.html @@ -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 %} + + +{% endblock %} + +{% block content %} + {% origin %} + +

Add drafts to {{ session.meeting }} : {{ session.group.acronym }}

+ {% comment %} TODO: put the session name here or calculate the number at the meeting {% endcomment %} + + {% if session.is_material_submission_cutoff %} +
The deadline for submission corrections has passed. This may affect published proceedings.
+ {% endif %} + +
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.
+
+
Drafts already linked to this sesssion
+
+ + + + + + {% for sp in already_linked %} + + + + + {% endfor %} +
RevisionDocument
{% if sp.rev %}-{{sp.rev}}{% else %}(current){% endif %}{{sp.document.title}} ({{sp.document.name}})
+
+
+ +
+
Additional drafts to link to this session
+
+
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + Cancel + {% endbuttons %} +
+
+
+ +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/meeting/session_details.html b/ietf/templates/meeting/session_details.html index fc012d784..c67e7e554 100644 --- a/ietf/templates/meeting/session_details.html +++ b/ietf/templates/meeting/session_details.html @@ -15,7 +15,10 @@ {% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
- Upload/Edit Materials + Upload/Edit materials + + + Link additional drafts to session {% if not type_counter.agenda %} This session does not yet have an agenda @@ -38,7 +41,7 @@ {% else %} {% url 'doc_view' name=pres.document.name as url %} {% endif %} - {{pres.document.title}} ({{ pres.document.name }}-{{ pres.rev }}) + {{pres.document.title}} ({{ pres.document.name }}{% if pres.rev %}-{{ pres.rev }}{% endif %}) From bd0b166ca026ebd515fef0d272aab9ac7be33f39 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 23 Feb 2016 16:30:54 +0000 Subject: [PATCH 8/9] Show which session the user is adding drafts to when there was more than one sesison at a meeting. - Legacy-Id: 10854 --- ietf/doc/tests_material.py | 1 - ietf/meeting/views.py | 14 ++++++++++++-- ietf/templates/meeting/add_session_drafts.html | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ietf/doc/tests_material.py b/ietf/doc/tests_material.py index f16858cb4..3d84ff315 100644 --- a/ietf/doc/tests_material.py +++ b/ietf/doc/tests_material.py @@ -12,7 +12,6 @@ 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 diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index a67040835..d01ee3e32 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -828,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']) @@ -842,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 @@ -901,6 +905,11 @@ def add_session_drafts(request, session_id, num): 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(): @@ -915,6 +924,7 @@ def add_session_drafts(request, session_id, num): 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, }) diff --git a/ietf/templates/meeting/add_session_drafts.html b/ietf/templates/meeting/add_session_drafts.html index 3fa968f00..4b4b2ca2b 100644 --- a/ietf/templates/meeting/add_session_drafts.html +++ b/ietf/templates/meeting/add_session_drafts.html @@ -12,7 +12,7 @@ {% block content %} {% origin %} -

Add drafts to {{ session.meeting }} : {{ session.group.acronym }}

+

Add drafts to {{ session.meeting }} {% if session_number %}: Session {{session_number}}{% endif %} : {{ session.group.acronym }}{% if session.name %} : {{session.name}}{% endif %}

{% comment %} TODO: put the session name here or calculate the number at the meeting {% endcomment %} {% if session.is_material_submission_cutoff %} From a39f207df3d04b16f31ecf9ee3cc3c4a2dde1320 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 23 Feb 2016 16:36:41 +0000 Subject: [PATCH 9/9] Add the meetings tab to groups of type team - Legacy-Id: 10855 --- ietf/group/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/group/info.py b/ietf/group/info.py index ecb3fbbb4..624f8ef3a 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -336,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)))