From e4cbf9a45ddc729cf465b88b434b07a042b16053 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Sat, 9 Mar 2013 23:38:42 +0000 Subject: [PATCH 01/26] fix bug where DocEvent record created when editing draft data did not have a description - Legacy-Id: 5539 --- ietf/secr/drafts/forms.py | 4 ++-- ietf/secr/drafts/views.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ietf/secr/drafts/forms.py b/ietf/secr/drafts/forms.py index e2b764ac2..aa0ae81fc 100644 --- a/ietf/secr/drafts/forms.py +++ b/ietf/secr/drafts/forms.py @@ -146,8 +146,8 @@ class EditModelForm(forms.ModelForm): self.fields['title'].widget=forms.Textarea() self.fields['rev'].widget.attrs['size'] = 2 self.fields['abstract'].widget.attrs['cols'] = 72 - self.initial['state'] = self.instance.get_state() - self.initial['iesg_state'] = self.instance.get_state('draft-iesg') + self.initial['state'] = self.instance.get_state().pk + self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk if self.instance.shepherd: self.initial['shepherd'] = "%s - (%s)" % (self.instance.shepherd.name, self.instance.shepherd.id) diff --git a/ietf/secr/drafts/views.py b/ietf/secr/drafts/views.py index ac68fa568..b1c096d2d 100644 --- a/ietf/secr/drafts/views.py +++ b/ietf/secr/drafts/views.py @@ -797,7 +797,8 @@ def edit(request, id): save_document_in_history(draft) DocEvent.objects.create(type='changed_document', by=request.user.get_profile(), - doc=draft) + doc=draft, + desc='Changed field(s): %s' % ','.join(form.changed_data)) # see EditModelForm.save() for detailed logic form.save() From f04d424e6aa8d50095c0465b2a0f37bbaacf0a8a Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Wed, 13 Mar 2013 20:23:29 +0000 Subject: [PATCH 02/26] fix bug with draft edit if no substate set - Legacy-Id: 5567 --- ietf/secr/drafts/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ietf/secr/drafts/forms.py b/ietf/secr/drafts/forms.py index aa0ae81fc..a17232290 100644 --- a/ietf/secr/drafts/forms.py +++ b/ietf/secr/drafts/forms.py @@ -147,7 +147,8 @@ class EditModelForm(forms.ModelForm): self.fields['rev'].widget.attrs['size'] = 2 self.fields['abstract'].widget.attrs['cols'] = 72 self.initial['state'] = self.instance.get_state().pk - self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk + if self.instance.get_state('draft-iesg'): + self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk if self.instance.shepherd: self.initial['shepherd'] = "%s - (%s)" % (self.instance.shepherd.name, self.instance.shepherd.id) From 17dbec6d0cc7516a3714c9d409773ebddfb88b43 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Fri, 29 Mar 2013 15:45:08 +0000 Subject: [PATCH 03/26] add IAB executive director to announcement access list - Legacy-Id: 5603 --- ietf/secr/announcement/forms.py | 7 ++++++- ietf/secr/announcement/views.py | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ietf/secr/announcement/forms.py b/ietf/secr/announcement/forms.py index 64293c324..c3f9da825 100644 --- a/ietf/secr/announcement/forms.py +++ b/ietf/secr/announcement/forms.py @@ -32,7 +32,8 @@ FROM_LIST = ('IETF Secretariat ', 'The IETF Trust ', 'RSOC Chair ', 'ISOC Board of Trustees ', - 'RFC Series Editor ') + 'RFC Series Editor ', + 'IAB Executive Director ') TO_LIST = ('IETF Announcement List ', 'I-D Announcement List ', @@ -106,6 +107,10 @@ def get_from_choices(user): group__acronym='rse', name="chair"): f = (FROM_LIST[15],) + elif Role.objects.filter(person=person, + group__acronym='iab', + name='execdir'): + f = (FROM_LIST[6],FROM_LIST[16]) return zip(f,f) def get_to_choices(): diff --git a/ietf/secr/announcement/views.py b/ietf/secr/announcement/views.py index 8d6ba084c..fb4360208 100644 --- a/ietf/secr/announcement/views.py +++ b/ietf/secr/announcement/views.py @@ -34,6 +34,10 @@ def check_access(user): group__type="ietf", person=person): return True + if Role.objects.filter(person=person, + group__acronym='iab', + name='execdir'): + return True return False From dd189eb5fe99876ca11e61639e16074a415c8a3c Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Wed, 3 Apr 2013 18:08:05 +0000 Subject: [PATCH 04/26] add missing import of IntegrityError - Legacy-Id: 5605 --- ietf/secr/rolodex/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ietf/secr/rolodex/views.py b/ietf/secr/rolodex/views.py index 86b669755..076a51286 100644 --- a/ietf/secr/rolodex/views.py +++ b/ietf/secr/rolodex/views.py @@ -1,5 +1,6 @@ from django.contrib import messages from django.core.urlresolvers import reverse +from django.db import IntegrityError from django.db.models import Q from django.forms.formsets import formset_factory from django.forms.models import inlineformset_factory, modelformset_factory From f8dbd6e055467ef758568d023cd10c81417c58c0 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Fri, 19 Apr 2013 18:23:22 +0000 Subject: [PATCH 05/26] changes to support new Status Changes section in IESG agenda - Legacy-Id: 5675 --- ietf/secr/telechat/views.py | 2 +- ietf/secr/templates/includes/sessions_footer.html | 2 +- ietf/secr/templates/telechat/base_telechat.html | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ietf/secr/telechat/views.py b/ietf/secr/telechat/views.py index 0de92237a..78a36967a 100644 --- a/ietf/secr/telechat/views.py +++ b/ietf/secr/telechat/views.py @@ -95,7 +95,7 @@ def get_section_header(file,agenda): ''' h1 = {'2':'Protocol Actions','3':'Document Actions','4':'Working Group Actions'} h2a = {'1':'WG Submissions','2':'Individual Submissions'} - h2b = {'1':'WG Submissions','2':'Individual Submissions via AD','3':'IRTF and Independent Submission Stream Documents'} + h2b = {'1':'WG Submissions','2':'Individual Submissions via AD','3':'Status Changes','4':'IRTF and Independent Submission Stream Documents'} h2c = {'1':'WG Creation','2':'WG Chartering'} h3a = {'1':'New Item','2':'Returning Item','3':'For Action'} h3b = {'1':'Proposed for IETF Review','2':'Proposed for Approval'} diff --git a/ietf/secr/templates/includes/sessions_footer.html b/ietf/secr/templates/includes/sessions_footer.html index 9f10c7caa..5f0bbd0d3 100755 --- a/ietf/secr/templates/includes/sessions_footer.html +++ b/ietf/secr/templates/includes/sessions_footer.html @@ -1,4 +1,4 @@
  • Instructions.
  • -
  • IETF Meeting Materials Management Tool.
  • +
  • IETF Meeting Materials Management Tool.
  • If you require assistance in using this tool, or wish to report a bug, then please send a message to ietf-action@ietf.org.
  • To submit your request via email, please send your request to agenda@ietf.org.
  • diff --git a/ietf/secr/templates/telechat/base_telechat.html b/ietf/secr/templates/telechat/base_telechat.html index 771a4cc5a..f48a1652e 100644 --- a/ietf/secr/templates/telechat/base_telechat.html +++ b/ietf/secr/templates/telechat/base_telechat.html @@ -51,13 +51,19 @@
  • 3.2.3 For Action
  • {% with agenda.docs.s323 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
  • -
  • 3.3 Independent Submissions via RFC Editor
  • +
  • 3.3 Status Changes
  • 3.3.1 New Item
  • {% with agenda.docs.s331 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
  • 3.3.2 Returning Item
  • {% with agenda.docs.s332 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} -
  • 3.3.3 For Action
  • - {% with agenda.docs.s333 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} +
  • +
  • 3.4 Independent Submissions via RFC Editor
  • +
  • 3.4.1 New Item
  • + {% with agenda.docs.s341 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} +
  • 3.4.2 Returning Item
  • + {% with agenda.docs.s342 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} +
  • 3.4.3 For Action
  • + {% with agenda.docs.s343 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
  • 4 Working Group Actions
  • 4.1 WG Creation
  • From adb916465301f31411c2131ecbe147bf8e1e9494 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Tue, 7 May 2013 21:31:54 +0000 Subject: [PATCH 06/26] add Status Changes to telechat agenda section 2 - Legacy-Id: 5706 --- ietf/secr/telechat/views.py | 2 +- ietf/secr/templates/telechat/base_telechat.html | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ietf/secr/telechat/views.py b/ietf/secr/telechat/views.py index 78a36967a..e2ca4580a 100644 --- a/ietf/secr/telechat/views.py +++ b/ietf/secr/telechat/views.py @@ -94,7 +94,7 @@ def get_section_header(file,agenda): agenda section header as a string for use in the doc template ''' h1 = {'2':'Protocol Actions','3':'Document Actions','4':'Working Group Actions'} - h2a = {'1':'WG Submissions','2':'Individual Submissions'} + h2a = {'1':'WG Submissions','2':'Individual Submissions','3':'Status Changes'} h2b = {'1':'WG Submissions','2':'Individual Submissions via AD','3':'Status Changes','4':'IRTF and Independent Submission Stream Documents'} h2c = {'1':'WG Creation','2':'WG Chartering'} h3a = {'1':'New Item','2':'Returning Item','3':'For Action'} diff --git a/ietf/secr/templates/telechat/base_telechat.html b/ietf/secr/templates/telechat/base_telechat.html index f48a1652e..cecad39b8 100644 --- a/ietf/secr/templates/telechat/base_telechat.html +++ b/ietf/secr/templates/telechat/base_telechat.html @@ -34,6 +34,12 @@
  • 2.2.3 For Action
  • {% with agenda.docs.s223 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
  • +
  • 2.3 Status Changes
  • +
  • 2.3.1 New Item
  • + {% with agenda.docs.s231 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} +
  • 2.3.2 Returning Item
  • + {% with agenda.docs.232 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %} +
  • 3 Document Actions
  • 3.1 WG Submissions
  • 3.1.1 New Item
  • From c9e2955d75eac63403d580e8d69fb6f8080a2f7e Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Tue, 28 May 2013 17:48:07 +0000 Subject: [PATCH 07/26] modifications to support agenda tool db changes - Legacy-Id: 5762 --- ietf/secr/meetings/forms.py | 2 +- ietf/secr/meetings/views.py | 201 +++++++++++++++------------- ietf/secr/proceedings/proc_utils.py | 4 +- ietf/secr/sreq/views.py | 10 +- ietf/secr/utils/meeting.py | 30 ++++- 5 files changed, 143 insertions(+), 104 deletions(-) diff --git a/ietf/secr/meetings/forms.py b/ietf/secr/meetings/forms.py index 16aea5cf3..84c81f2a9 100644 --- a/ietf/secr/meetings/forms.py +++ b/ietf/secr/meetings/forms.py @@ -90,7 +90,7 @@ class TimeChoiceField(forms.ChoiceField): class MeetingModelForm(forms.ModelForm): class Meta: model = Meeting - exclude = ('type') + exclude = ('type','agenda') def clean_number(self): number = self.cleaned_data['number'] diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index dfe14a2e3..43aa932ad 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -12,7 +12,7 @@ from django.utils.functional import curry from django.utils import simplejson from ietf.utils.mail import send_mail -from ietf.meeting.models import Meeting, Session, Room, TimeSlot +from ietf.meeting.models import Meeting, Session, Room, TimeSlot, Schedule, ScheduledSession from ietf.group.models import Group from ietf.name.models import SessionStatusName, TimeSlotTypeName from ietf.person.models import Person @@ -21,7 +21,7 @@ from ietf.secr.proceedings.views import build_choices, handle_upload_file from ietf.secr.sreq.forms import GroupSelectForm from ietf.secr.sreq.views import get_initial_session, session_conflicts_as_string from ietf.secr.utils.mail import get_cc_list -from ietf.secr.utils.meeting import get_upload_root +from ietf.secr.utils.meeting import get_upload_root, get_session, get_timeslot from forms import * @@ -31,7 +31,7 @@ import datetime # -------------------------------------------------- # Helper Functions # -------------------------------------------------- -def build_timeslots(meeting,room=None): +def build_timeslots(request,meeting,room=None): ''' This function takes a Meeting object and an optional room argument. If room isn't passed we pre-create the full set of timeslot records using the last meeting as a template. @@ -62,14 +62,14 @@ def build_timeslots(meeting,room=None): for t in timeslots: new_time = t.time + delta for room in rooms: - TimeSlot.objects.create(type_id='session', + ts = TimeSlot.objects.create(type_id='session', meeting=meeting, name=t.name, time=new_time, location=room, duration=t.duration) -def build_nonsession(meeting): +def build_nonsession(request, meeting): ''' This function takes a meeting object and creates non-session records for a new meeting, based on the last meeting @@ -77,6 +77,8 @@ def build_nonsession(meeting): last_meeting = get_last_meeting(meeting) delta = meeting.date - last_meeting.date system = Person.objects.get(name='(system)') + schedule = get_schedule(request, meeting) + for slot in TimeSlot.objects.filter(meeting=last_meeting,type__in=('break','reg','other','plenary')): new_time = slot.time + delta session = None @@ -84,29 +86,51 @@ def build_nonsession(meeting): if slot.type.slug in ('other','plenary'): session = Session(meeting=meeting, name=slot.name, - short=slot.session.short, - group=slot.session.group, + short=get_session(slot).short, + group=get_session(slot).group, requested_by=system, status_id='sched') session.save() - TimeSlot.objects.create(type=slot.type, + ts = TimeSlot.objects.create(type=slot.type, meeting=meeting, - session=session, name=slot.name, time=new_time, duration=slot.duration, show_location=slot.show_location) + + ScheduledSession.create(schedule=schedule, + session=session, + timeslot=ts) def get_last_meeting(meeting): last_number = int(meeting.number) - 1 return Meeting.objects.get(number=last_number) + +def get_schedule(request,meeting): + ''' + This function takes a Request object and Meeting object and returns the Official + Schdeule for the meeting. It will create the Schedule if it doesn't already + exist. Call this function after creating a new meeting. + ''' + schedule = meeting.agenda + if schedule is None: + name = "mtg:%s" % meeting.number + person = request.user.get_profile() + schedule, created = Schedule.objects.get_or_create(meeting=meeting, + name=name, + defaults={'owner':person, + 'visible':True, + 'public':True}) + meeting.agenda = schedule + meeting.save() + return schedule -def is_combined(session): +def is_combined(session, meeting): ''' Check to see if this session is using two combined timeslots ''' - if session.timeslot_set.count() > 1: + if session.scheduledsession_set.filter(schedule=meeting.agenda).count() > 1: return True else: return False @@ -125,7 +149,8 @@ def make_directories(meeting): def send_notification(request, sessions): ''' - This view generates notifications for schedule sessions + This view generates notifications for schedule sessions. It takes an HttpRequest object and + a query set, sessions, consisting of one or more sessions scheduled for a particular group. ''' session_info_template = '''{0} Session {1} ({2}) {3}, {4} {5} @@ -145,7 +170,7 @@ def send_notification(request, sessions): # easier to populate template from timeslot perspective. assuming one-to-one timeslot-session count = 0 session_info = '' - data = [ (s,s.timeslot_set.all()[0]) for s in sessions ] + data = [ (s,get_timeslot(s)) for s in sessions ] for s,t in data: count += 1 session_info += session_info_template.format(group.acronym, @@ -174,18 +199,18 @@ def send_notification(request, sessions): def sort_groups(meeting): ''' Similar to sreq.views.sort_groups - Takes a Django User object and a Meeting object - Returns a tuple scheduled_groups, unscheduled groups. + Takes a Meeting object and returns a tuple scheduled_groups, unscheduled groups. ''' scheduled_groups = [] unscheduled_groups = [] - #sessions = Session.objects.filter(meeting=meeting,status__in=('schedw','apprw','appr','sched','notmeet','canceled')) sessions = Session.objects.filter(meeting=meeting,status__in=('schedw','apprw','appr','sched','canceled')) - groups_with_sessions = [ s.group for s in sessions ] + groups_with_sessions = [ s.group for s in sessions if s.group.type.slug in ('wg','rg','ag') ] gset = set(groups_with_sessions) sorted_groups_with_sessions = sorted(gset, key = lambda instance: instance.acronym) - slots = TimeSlot.objects.filter(meeting=meeting,session__isnull=False) - groups_with_timeslots = [ s.session.group for s in slots ] + slots = TimeSlot.objects.filter(meeting=meeting, + scheduledsession__schedule=meeting.agenda, + scheduledsession__session__isnull=False) + groups_with_timeslots = [ get_session(s).group for s in slots ] for group in sorted_groups_with_sessions: if group in groups_with_timeslots: scheduled_groups.append(group) @@ -237,6 +262,9 @@ def add(request): if form.is_valid(): meeting = form.save() + #Create initial Official schedule + get_schedule(request,meeting) + #Create Physical new meeting directory and subdirectories make_directories(meeting) @@ -371,7 +399,7 @@ def non_session(request, meeting_id): # if the Break/Registration records don't exist yet (new meeting) create them if not TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other')): - build_nonsession(meeting) + build_nonsession(request, meeting) slots = TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other','plenary')).order_by('-type__name','time') @@ -388,6 +416,14 @@ def non_session(request, meeting_id): t = meeting.date + datetime.timedelta(days=int(day)) new_time = datetime.datetime(t.year,t.month,t.day,time.hour,time.minute) + # create TimeSlot object + timeslot = TimeSlot.objects.create(type=form.cleaned_data['type'], + meeting=meeting, + name=name, + time=new_time, + duration=duration, + show_location=form.cleaned_data['show_location']) + # create a dummy Session object to hold materials # NOTE: we're setting group to none here, but the set_room page will force user # to pick a legitimate group @@ -401,15 +437,11 @@ def non_session(request, meeting_id): status_id='sched') session.save() - # create TimeSlot object - TimeSlot.objects.create(type=form.cleaned_data['type'], - meeting=meeting, - session=session, - name=name, - time=new_time, - duration=duration, - show_location=form.cleaned_data['show_location']) - + # create association + ScheduledSession.objects.create(timeslot=timeslot, + session=session, + schedule=meeting.agenda) + messages.success(request, 'Non-Sessions updated successfully') url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id}) return HttpResponseRedirect(url) @@ -434,13 +466,14 @@ def non_session_delete(request, meeting_id, slot_id): ''' slot = get_object_or_404(TimeSlot, id=slot_id) if slot.type_id in ('other','plenary'): - if slot.session.materials.exclude(states__slug='deleted'): + session = get_session(slot) + if session and session.materials.exclude(states__slug='deleted'): messages.error(request, 'Materials have already been uploaded for "%s". You must delete those before deleting the timeslot.' % slot.name) url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id}) return HttpResponseRedirect(url) else: - slot.session.delete() + slot.sessions.all().delete() slot.delete() messages.success(request, 'Non-Session timeslot deleted successfully') @@ -453,14 +486,15 @@ def non_session_edit(request, meeting_id, slot_id): ''' meeting = get_object_or_404(Meeting, number=meeting_id) slot = get_object_or_404(TimeSlot, id=slot_id) - + session = get_session(slot) + if request.method == 'POST': button_text = request.POST.get('submit', '') if button_text == 'Cancel': url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id}) return HttpResponseRedirect(url) - form = NonSessionEditForm(request.POST,meeting=meeting, session=slot.session) + form = NonSessionEditForm(request.POST,meeting=meeting, session=session) if form.is_valid(): location = form.cleaned_data['location'] group = form.cleaned_data['group'] @@ -470,7 +504,6 @@ def non_session_edit(request, meeting_id, slot_id): slot.name = name slot.save() # save group to session object - session = slot.session session.group = group session.name = name session.short = short @@ -484,10 +517,10 @@ def non_session_edit(request, meeting_id, slot_id): # we need to pass the session to the form in order to disallow changing # of group after materials have been uploaded initial = {'location':slot.location, - 'group':slot.session.group, - 'name':slot.session.name, - 'short':slot.session.short} - form = NonSessionEditForm(meeting=meeting,session=slot.session,initial=initial) + 'group':session.group, + 'name':session.name, + 'short':session.short} + form = NonSessionEditForm(meeting=meeting,session=session,initial=initial) return render_to_response('meetings/non_session_edit.html', { 'meeting': meeting, @@ -508,10 +541,10 @@ def remove_session(request, meeting_id, acronym): now = datetime.datetime.now() for session in sessions: - timeslot = session.timeslot_set.all()[0] - timeslot.session = None - timeslot.modified = now - timeslot.save() + ss = session.official_scheduledsession() + ss.session = None + ss.modified = now + ss.save() session.status_id = 'canceled' session.modified = now session.save() @@ -543,14 +576,14 @@ def rooms(request, meeting_id): # if we are creating rooms for the first time create full set of timeslots if first_time: - build_timeslots(meeting) + build_timeslots(request, meeting) # otherwise if we're modifying rooms else: # add timeslots for new rooms, deleting rooms automatically deletes timeslots for form in formset.forms[formset.initial_form_count():]: if form.instance.pk: - build_timeslots(meeting,room=form.instance) + build_timeslots(request, meeting,room=form.instance) messages.success(request, 'Meeting Rooms changed successfully') url = reverse('meetings_rooms', kwargs={'meeting_id':meeting_id}) @@ -580,14 +613,16 @@ def schedule(request, meeting_id, acronym): for s in sessions: d = {'session':s.id, 'note':s.agenda_note} - qs = s.timeslot_set.all() - if qs: - d['room'] = qs[0].location.id - d['day'] = qs[0].time.isoweekday() % 7 + 1 # adjust to django week_day - d['time'] = qs[0].time.strftime('%H%M') + + timeslot = get_timeslot(s) + + if timeslot: + d['room'] = timeslot.location.id + d['day'] = timeslot.time.isoweekday() % 7 + 1 # adjust to django week_day + d['time'] = timeslot.time.strftime('%H%M') else: d['day'] = 2 - if is_combined(s): + if is_combined(s,meeting): d['combine'] = True initial.append(d) @@ -617,49 +652,28 @@ def schedule(request, meeting_id, acronym): day = form.cleaned_data['day'] combine = form.cleaned_data.get('combine',None) session = Session.objects.get(id=id) - if session.timeslot_set.all(): - initial_timeslot = session.timeslot_set.all()[0] - else: - initial_timeslot = None - + initial_timeslot = get_timeslot(session) + # find new timeslot new_day = meeting.date + datetime.timedelta(days=int(day)-1) hour = datetime.time(int(time[:2]),int(time[2:])) new_time = datetime.datetime.combine(new_day,hour) - qs = TimeSlot.objects.filter(meeting=meeting,time=new_time,location=room) - if qs.filter(session=None): - timeslot = qs.filter(session=None)[0] - else: - # we need to create another, identical timeslot - timeslot = TimeSlot.objects.create(meeting=qs[0].meeting, - type=qs[0].type, - name=qs[0].name, - time=qs[0].time, - duration=qs[0].duration, - location=qs[0].location, - show_location=qs[0].show_location, - modified=now) - messages.warning(request, 'WARNING: There are now two sessions scheduled for the timeslot: %s' % timeslot) + timeslot = TimeSlot.objects.filter(meeting=meeting,time=new_time,location=room)[0] if any(x in form.changed_data for x in ('day','time','room')): - # clear the old timeslot + # clear the old association if initial_timeslot: - # if the initial timeslot is one of multiple we should delete it - tqs = TimeSlot.objects.filter(meeting=meeting, - type='session', - time=initial_timeslot.time, - location=initial_timeslot.location) - if tqs.count() > 1: - initial_timeslot.delete() - else: - initial_timeslot.session = None - initial_timeslot.modified = now - initial_timeslot.save() + initial_timeslot.scheduledsession_set.filter(schedule=meeting.agenda,session=session).delete() + if timeslot: - timeslot.session = session - timeslot.modified = now - timeslot.save() + # create association + ScheduledSession.objects.create(schedule=meeting.agenda, + session=session, + timeslot=timeslot) + if timeslot.sessions.all().count() > 1: + messages.warning(request, 'WARNING: There are now two sessions scheduled for the timeslot: %s' % timeslot) session.status_id = 'sched' + else: session.status_id = 'schedw' @@ -675,11 +689,12 @@ def schedule(request, meeting_id, acronym): if 'combine' in form.changed_data: next_slot = get_next_slot(timeslot) if combine: - next_slot.session = session + ScheduledSession.objects.create(schedule=meeting.agenda, + session=session, + timeslot=next_slot) else: - next_slot.session = None - next_slot.modified = now - next_slot.save() + next_slot.scheduledsession_set.filter(schedule=meeting.agenda,session=session).delete() + # --------------------------------------- # notify. dont send if Tutorial, BOF or indicated on form @@ -687,7 +702,7 @@ def schedule(request, meeting_id, acronym): if (has_changed and not extra_form.cleaned_data.get('no_notify',False) and group.state.slug != 'bof' - and session.timeslot_set.all()): # and the session is scheduled, else skip + and get_timeslot(session)): # and the session is scheduled, else skip send_notification(request, sessions) notification_message = "Notification sent." @@ -711,7 +726,7 @@ def schedule(request, meeting_id, acronym): 'formset': formset}, RequestContext(request, {}), ) - + def select_group(request, meeting_id): ''' In this view the user can select the group to schedule. Only those groups that have @@ -791,7 +806,6 @@ def times(request, meeting_id): new_time = datetime.datetime(t.year,t.month,t.day,time.hour,time.minute) # don't allow creation of timeslots with same start time as existing timeslots - # assert False, (new_time, time_seen) if new_time in time_seen: messages.error(request, 'There is already a timeslot for %s. To change you must delete the old one first.' % new_time.strftime('%a %H:%M')) url = reverse('meetings_times', kwargs={'meeting_id':meeting_id}) @@ -829,7 +843,10 @@ def times_delete(request, meeting_id, time): parts = [ int(x) for x in time.split(':') ] dtime = datetime.datetime(*parts) - if Session.objects.filter(timeslot__time=dtime,timeslot__meeting=meeting): + qs = meeting.agenda.scheduledsession_set.filter(timeslot__time=dtime, + session__isnull=False) + + if qs: messages.error(request, 'ERROR deleting timeslot. There is one or more sessions scheduled for this timeslot.') url = reverse('meetings_times', kwargs={'meeting_id':meeting_id}) return HttpResponseRedirect(url) diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py index 191fe0bce..48de03baf 100644 --- a/ietf/secr/proceedings/proc_utils.py +++ b/ietf/secr/proceedings/proc_utils.py @@ -14,7 +14,7 @@ from itertools import chain from ietf.secr.proceedings.models import Registration from ietf.secr.utils.document import get_rfc_num from ietf.secr.utils.group import groups_by_session -from ietf.secr.utils.meeting import get_upload_root, get_proceedings_path, get_material +from ietf.secr.utils.meeting import get_upload_root, get_proceedings_path, get_material, get_session, get_timeslot from models import InterimMeeting # proxy model from urllib2 import urlopen @@ -32,7 +32,7 @@ def mycomp(timeslot): This takes a timeslot object and returns a key to sort by the area acronym or None ''' try: - group = timeslot.session.group + group = get_session(timeslot).group key = '%s:%s' % (group.parent.acronym, group.acronym) except AttributeError: key = None diff --git a/ietf/secr/sreq/views.py b/ietf/secr/sreq/views.py index 4cb6fae20..2b8363676 100644 --- a/ietf/secr/sreq/views.py +++ b/ietf/secr/sreq/views.py @@ -202,14 +202,8 @@ def cancel(request, acronym): session.status_id = 'deleted' session.save() - # clear timeslot assignment if already scheduled - if session.timeslot_set.all(): - timeslot = session.timeslot_set.all()[0] - timeslot.session = None - timeslot.save() - - # log activity - #add_session_activity(group,'Session was cancelled',meeting,user) + # clear schedule assignments if already scheduled + session.scheduledsession_set.all().delete() # send notifitcation to_email = SESSION_REQUEST_EMAIL diff --git a/ietf/secr/utils/meeting.py b/ietf/secr/utils/meeting.py index f458e382b..eff298597 100644 --- a/ietf/secr/utils/meeting.py +++ b/ietf/secr/utils/meeting.py @@ -1,5 +1,5 @@ from django.conf import settings -from ietf.meeting.models import Meeting +from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot import os @@ -28,6 +28,34 @@ def get_proceedings_path(meeting, group): path = os.path.join(get_upload_root(meeting),'%s.html' % group.acronym) return path +def get_session(timeslot, schedule=None): + ''' + Helper function to get the session given a timeslot, assume Official schedule if one isn't + provided. Replaces "timeslot.session" + ''' + # todo, doesn't account for shared timeslot + if not schedule: + schedule = timeslot.meeting.agenda + qs = timeslot.sessions.filter(scheduledsession__schedule=schedule) #.exclude(states__slug='deleted') + if qs: + return qs[0] + else: + return None + +def get_timeslot(session, schedule=None): + ''' + Helper function to get the timeslot associated with a session. Created for Agenda Tool + db schema changes. Use this function in place of session.timeslot_set.all()[0]. Don't specify + schedule to use the meeting "official" schedule. + ''' + if not schedule: + schedule = session.meeting.agenda + ss = session.scheduledsession_set.filter(schedule=schedule) + if ss: + return ss[0].timeslot + else: + return None + def get_upload_root(meeting): path = '' if meeting.type.slug == 'ietf': From a667b0b95f362a497a026e8069864097ee626abd Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Tue, 28 May 2013 17:56:28 +0000 Subject: [PATCH 08/26] version update - Legacy-Id: 5763 --- ietf/secr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ietf/secr/__init__.py b/ietf/secr/__init__.py index 64b3f0372..9a6a62f58 100644 --- a/ietf/secr/__init__.py +++ b/ietf/secr/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.33" +__version__ = "1.40" __date__ = "$Date: 2011/07/26 14:29:17 $" From 613746fceb105a5d9e29aaddc5e4f2790b658ab1 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Thu, 30 May 2013 20:11:47 +0000 Subject: [PATCH 09/26] changes, timeslot should always have scheduled session - Legacy-Id: 5766 --- ietf/secr/meetings/views.py | 76 +++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py index 43aa932ad..d78538a43 100644 --- a/ietf/secr/meetings/views.py +++ b/ietf/secr/meetings/views.py @@ -31,6 +31,30 @@ import datetime # -------------------------------------------------- # Helper Functions # -------------------------------------------------- +def assign(session,timeslot,meeting): + ''' + Robust function to assign a session to a timeslot + ''' + qs = timeslot.scheduledsession_set.filter(schedule=meeting.agenda) + # this should never happen, but just in case + if not qs: + ScheduledSession.objects.create(schedule=meeting.agenda, + session=session, + timeslot=timeslot) + else: + # find the first unassigned scheduled session or create a new one + for ss in qs: + if not ss.session: + ss.session = session + ss.save() + break + else: + ScheduledSession.objects.create(schedule=meeting.agenda, + session=session, + timeslot=timeslot) + session.status_id = 'sched' + session.save() + def build_timeslots(request,meeting,room=None): ''' This function takes a Meeting object and an optional room argument. If room isn't passed we @@ -38,6 +62,7 @@ def build_timeslots(request,meeting,room=None): If room is passed pre-create timeslots for the new room. Call this after saving new rooms or adding a room. ''' + schedule = get_schedule(request,meeting) slots = meeting.timeslot_set.filter(type='session') if room: rooms = [room] @@ -68,6 +93,7 @@ def build_timeslots(request,meeting,room=None): time=new_time, location=room, duration=t.duration) + ScheduledSession.objects.create(schedule=schedule,timeslot=ts) def build_nonsession(request, meeting): ''' @@ -98,10 +124,7 @@ def build_nonsession(request, meeting): time=new_time, duration=slot.duration, show_location=slot.show_location) - - ScheduledSession.create(schedule=schedule, - session=session, - timeslot=ts) + ScheduledSession.create(schedule=schedule,session=session,timeslot=ts) def get_last_meeting(meeting): last_number = int(meeting.number) - 1 @@ -207,10 +230,8 @@ def sort_groups(meeting): groups_with_sessions = [ s.group for s in sessions if s.group.type.slug in ('wg','rg','ag') ] gset = set(groups_with_sessions) sorted_groups_with_sessions = sorted(gset, key = lambda instance: instance.acronym) - slots = TimeSlot.objects.filter(meeting=meeting, - scheduledsession__schedule=meeting.agenda, - scheduledsession__session__isnull=False) - groups_with_timeslots = [ get_session(s).group for s in slots ] + scheduled_sessions = ScheduledSession.objects.filter(schedule=meeting.agenda,session__isnull=False) + groups_with_timeslots = [ x.session.group for x in scheduled_sessions ] for group in sorted_groups_with_sessions: if group in groups_with_timeslots: scheduled_groups.append(group) @@ -621,7 +642,7 @@ def schedule(request, meeting_id, acronym): d['day'] = timeslot.time.isoweekday() % 7 + 1 # adjust to django week_day d['time'] = timeslot.time.strftime('%H%M') else: - d['day'] = 2 + d['day'] = 2 # default if is_combined(s,meeting): d['combine'] = True initial.append(d) @@ -663,17 +684,15 @@ def schedule(request, meeting_id, acronym): if any(x in form.changed_data for x in ('day','time','room')): # clear the old association if initial_timeslot: - initial_timeslot.scheduledsession_set.filter(schedule=meeting.agenda,session=session).delete() - + # get SS record(s) and unschedule by removing the session reference + for ss in session.scheduledsession_set.filter(schedule=meeting.agenda): + ss.session = None + ss.save() + if timeslot: - # create association - ScheduledSession.objects.create(schedule=meeting.agenda, - session=session, - timeslot=timeslot) + assign(session,timeslot,meeting) if timeslot.sessions.all().count() > 1: - messages.warning(request, 'WARNING: There are now two sessions scheduled for the timeslot: %s' % timeslot) - session.status_id = 'sched' - + messages.warning(request, 'WARNING: There are now multiple sessions scheduled for the timeslot: %s' % timeslot) else: session.status_id = 'schedw' @@ -689,11 +708,11 @@ def schedule(request, meeting_id, acronym): if 'combine' in form.changed_data: next_slot = get_next_slot(timeslot) if combine: - ScheduledSession.objects.create(schedule=meeting.agenda, - session=session, - timeslot=next_slot) + assign(session,next_slot,meeting) else: - next_slot.scheduledsession_set.filter(schedule=meeting.agenda,session=session).delete() + for ss in next_slot.scheduledsession_set.filter(schedule=meeting.agenda,session=session): + ss.session = None + ss.save() # --------------------------------------- @@ -812,12 +831,13 @@ def times(request, meeting_id): return HttpResponseRedirect(url) for room in meeting.room_set.all(): - TimeSlot.objects.create(type_id='session', - meeting=meeting, - name=name, - time=new_time, - location=room, - duration=duration) + ts = TimeSlot.objects.create(type_id='session', + meeting=meeting, + name=name, + time=new_time, + location=room, + duration=duration) + ScheduledSession.objects.create(schedule=meeting.agenda,timeslot=ts) messages.success(request, 'Timeslots created') url = reverse('meetings_times', kwargs={'meeting_id':meeting_id}) From 45ec7a97a1e0e660839b72f684804ee319abcb28 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Mon, 24 Jun 2013 22:52:13 +0000 Subject: [PATCH 10/26] TICKET:928 use correct role in session request notification email - Legacy-Id: 5802 --- ietf/secr/sreq/views.py | 166 ++++++++++-------- ietf/secr/telechat/views.py | 9 +- .../sreq/session_approval_notification.txt | 2 +- .../sreq/session_cancel_notification.txt | 2 +- .../sreq/session_request_notification.txt | 2 +- 5 files changed, 96 insertions(+), 85 deletions(-) diff --git a/ietf/secr/sreq/views.py b/ietf/secr/sreq/views.py index 2b8363676..7deb6daee 100644 --- a/ietf/secr/sreq/views.py +++ b/ietf/secr/sreq/views.py @@ -15,7 +15,7 @@ from ietf.ietfauth.decorators import has_role from ietf.utils.mail import send_mail from ietf.meeting.models import Meeting, Session, Constraint -from ietf.group.models import Group, Role +from ietf.group.models import Group, Role from ietf.name.models import SessionStatusName, ConstraintName from forms import * @@ -50,7 +50,7 @@ def get_initial_session(sessions): initial = {} # even if there are three sessions requested, the old form has 2 in this field initial['num_session'] = sessions.count() if sessions.count() <= 2 else 2 - + # accessing these foreign key fields throw errors if they are unset so we # need to catch these initial['length_session1'] = str(sessions[0].requested_duration.seconds) @@ -65,7 +65,7 @@ def get_initial_session(sessions): initial['conflict3'] = ' '.join([ c.target.acronym for c in conflicts.filter(name__slug='conflic3') ]) initial['comments'] = sessions[0].comments return initial - + def get_lock_message(): ''' Returns the message to display to non-secretariat users when the tool is locked. @@ -84,6 +84,19 @@ def get_meeting(): ''' return Meeting.objects.filter(type='ietf').order_by('-date')[0] +def get_requester_text(person,group): + ''' + This function takes a Person object and a Group object and returns the text to use in the + session request notification email, ie. Joe Smith, a Chair of the ancp working group + ''' + roles = group.role_set.filter(name__in=('chair','secr'),person=person) + if roles: + return '%s, a %s of the %s working group' % (person, roles[0].name, group.acronym) + if group.parent.role_set.filter(name='ad',person=person): + return '%s, a %s Area Director' % (person, group.parent.acronym.upper()) + if person.role_set.filter(name='secr',group__acronym='secretariat'): + return '%s, on behalf of the %s working group' % (person, group.acronym) + def save_conflicts(group, meeting, conflicts, name): ''' This function takes a Group, Meeting a string which is a list of Groups acronyms (conflicts), @@ -93,7 +106,7 @@ def save_conflicts(group, meeting, conflicts, name): acronyms = conflicts.replace(',',' ').split() for acronym in acronyms: target = Group.objects.get(acronym=acronym) - + constraint = Constraint(source=group, target=target, meeting=meeting, @@ -111,7 +124,7 @@ def send_notification(group,meeting,login,session,action): from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org') subject = '%s - New Meeting Session Request for IETF %s' % (group.acronym, meeting.number) template = 'sreq/session_request_notification.txt' - + # send email context = {} context['session'] = session @@ -119,12 +132,13 @@ def send_notification(group,meeting,login,session,action): context['meeting'] = meeting context['login'] = login context['header'] = 'A new' - + context['requester'] = get_requester_text(login,group) + # update overrides if action == 'update': subject = '%s - Update to a Meeting Session Request for IETF %s' % (group.acronym, meeting.number) context['header'] = 'An update to a' - + # if third session requested approval is required # change headers TO=ADs, CC=session-request, submitter and cochairs if session.get('length_session3',None): @@ -164,11 +178,11 @@ def approve(request, acronym): meeting = get_meeting() group = get_object_or_404(Group, acronym=acronym) session = Session.objects.get(meeting=meeting,group=group,status='apprw') - + if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.get_profile()): session.status = SessionStatusName.objects.get(slug='appr') session.save() - + messages.success(request, 'Third session approved') url = reverse('sessions_view', kwargs={'acronym':acronym}) return HttpResponseRedirect(url) @@ -184,27 +198,27 @@ def cancel(request, acronym): This view cancels a session request and sends a notification. To cancel, or withdraw the request set status = deleted. "canceled" status is used by the secretariat. - + NOTE: this function can also be called after a session has been - scheduled during the period when the session request tool is + scheduled during the period when the session request tool is reopened. In this case be sure to clear the timeslot assignment as well. ''' meeting = get_meeting() group = get_object_or_404(Group, acronym=acronym) sessions = Session.objects.filter(meeting=meeting,group=group).order_by('id') login = request.user.get_profile() - + # delete conflicts Constraint.objects.filter(meeting=meeting,source=group).delete() - + # mark sessions as deleted for session in sessions: session.status_id = 'deleted' session.save() - + # clear schedule assignments if already scheduled session.scheduledsession_set.all().delete() - + # send notifitcation to_email = SESSION_REQUEST_EMAIL cc_list = get_cc_list(group, login) @@ -214,7 +228,7 @@ def cancel(request, acronym): {'login':login, 'group':group, 'meeting':meeting}, cc=cc_list) - + messages.success(request, 'The %s Session Request has been canceled' % group.acronym) url = reverse('sessions') return HttpResponseRedirect(url) @@ -231,23 +245,23 @@ def confirm(request, acronym): meeting = get_meeting() group = get_object_or_404(Group,acronym=acronym) login = request.user.get_profile() - + if request.method == 'POST': # clear http session data del request.session['session_form'] - + button_text = request.POST.get('submit', '') if button_text == 'Cancel': messages.success(request, 'Session Request has been canceled') url = reverse('sessions') return HttpResponseRedirect(url) - + # delete any existing session records with status = canceled or notmeet Session.objects.filter(group=group,meeting=meeting,status__in=('canceled','notmeet')).delete() - + # create new session records count = 0 - # lenth_session2 and length_session3 fields might be disabled by javascript and so + # lenth_session2 and length_session3 fields might be disabled by javascript and so # wouldn't appear in form data for duration in (form.get('length_session1',None),form.get('length_session2',None),form.get('length_session3',None)): count += 1 @@ -262,30 +276,30 @@ def confirm(request, acronym): comments=form['comments'], status=SessionStatusName.objects.get(slug=slug)) new_session.save() - + # write constraint records save_conflicts(group,meeting,form['conflict1'],'conflict') save_conflicts(group,meeting,form['conflict2'],'conflic2') save_conflicts(group,meeting,form['conflict3'],'conflic3') - + # deprecated in new schema # log activity #add_session_activity(group,'New session was requested',meeting,user) - + # clear not meeting Session.objects.filter(group=group,meeting=meeting,status='notmeet').delete() - + # send notification send_notification(group,meeting,login,form,'new') - + status_text = 'IETF Agenda to be scheduled' messages.success(request, 'Your request has been sent to %s' % status_text) url = reverse('sessions') return HttpResponseRedirect(url) - + # GET logic session_conflicts = session_conflicts_as_string(group, meeting) - + return render_to_response('sreq/confirm.html', { 'session': form, 'group': group, @@ -294,7 +308,7 @@ def confirm(request, acronym): ) @check_permissions -def edit(request, acronym): +def edit(request, acronym): ''' This view allows the user to edit details of the session request ''' @@ -305,13 +319,13 @@ def edit(request, acronym): initial = get_initial_session(sessions) session_conflicts = session_conflicts_as_string(group, meeting) login = request.user.get_profile() - + if request.method == 'POST': button_text = request.POST.get('submit', '') if button_text == 'Cancel': url = reverse('sessions_view', kwargs={'acronym':acronym}) return HttpResponseRedirect(url) - + form = SessionForm(request.POST,initial=initial) if form.is_valid(): if form.has_changed(): @@ -322,7 +336,7 @@ def edit(request, acronym): session = sessions[0] session.requested_duration = datetime.timedelta(0,int(form.cleaned_data['length_session1'])) session.save() - + # session 2 if 'length_session2' in form.changed_data: length_session2 = form.cleaned_data['length_session2'] @@ -344,7 +358,7 @@ def edit(request, acronym): session = sessions[1] session.requested_duration = duration session.save() - + # session 3 if 'length_session3' in form.changed_data: length_session3 = form.cleaned_data['length_session3'] @@ -366,8 +380,8 @@ def edit(request, acronym): session = sessions[2] session.requested_duration = duration session.save() - - + + if 'attendees' in form.changed_data: sessions.update(attendees=form.cleaned_data['attendees']) if 'comments' in form.changed_data: @@ -381,21 +395,21 @@ def edit(request, acronym): if 'conflict3' in form.changed_data: Constraint.objects.filter(meeting=meeting,source=group,name='conflic3').delete() save_conflicts(group,meeting,form.cleaned_data['conflict3'],'conflic3') - + # deprecated # log activity #add_session_activity(group,'Session Request was updated',meeting,user) - + # send notification send_notification(group,meeting,login,form.cleaned_data,'update') - + messages.success(request, 'Session Request updated') url = reverse('sessions_view', kwargs={'acronym':acronym}) return HttpResponseRedirect(url) - + else: form = SessionForm(initial=initial) - + return render_to_response('sreq/edit.html', { 'meeting': meeting, 'form': form, @@ -407,22 +421,22 @@ def edit(request, acronym): def main(request): ''' Display list of groups the user has access to. - + Template variables form: a select box populated with unscheduled groups meeting: the current meeting - scheduled_sessions: + scheduled_sessions: ''' # check for locked flag is_locked = check_app_locked() - + if is_locked and not has_role(request.user,'Secretariat'): message = get_lock_message() return render_to_response('sreq/locked.html', { 'message': message}, RequestContext(request, {}), ) - + # TODO this is not currently used in the main template if request.method == 'POST': button_text = request.POST.get('submit', '') @@ -432,15 +446,15 @@ def main(request): else: redirect_url = reverse('sessions_new', kwargs={'acronym':request.POST['group']}) return HttpResponseRedirect(redirect_url) - + meeting = get_meeting() scheduled_groups,unscheduled_groups = groups_by_session(request.user, meeting) - + # load form select with unscheduled groups choices = zip([ g.pk for g in unscheduled_groups ], [ str(g) for g in unscheduled_groups ]) form = GroupSelectForm(choices=choices) - + # add session status messages for use in template for group in scheduled_groups: sessions = group.session_set.filter(meeting=meeting) @@ -448,12 +462,12 @@ def main(request): group.status_message = sessions[0].status else: group.status_message = 'First two sessions: %s, Third session: %s' % (sessions[0].status,sessions[2].status) - + # add not meeting indicators for use in template for group in unscheduled_groups: if group.session_set.filter(meeting=meeting,status='notmeet'): group.not_meeting = True - + return render_to_response('sreq/main.html', { 'is_locked': is_locked, 'form': form, @@ -469,18 +483,18 @@ def new(request, acronym): This view gathers details for a new session request. The user proceeds to confirm() to create the request. ''' - + group = get_object_or_404(Group, acronym=acronym) meeting = get_meeting() session_conflicts = session_conflicts_as_string(group, meeting) user = request.user - + if request.method == 'POST': button_text = request.POST.get('submit', '') if button_text == 'Cancel': url = reverse('sessions') return HttpResponseRedirect(url) - + form = SessionForm(request.POST) if form.is_valid(): # check if request already exists for this group @@ -488,13 +502,13 @@ def new(request, acronym): messages.warning(request, 'Sessions for working group %s have already been requested once.' % group.acronym) url = reverse('sessions') return HttpResponseRedirect(url) - + # save in user session request.session['session_form'] = form.data - + url = reverse('sessions_confirm',kwargs={'acronym':acronym}) return HttpResponseRedirect(url) - + # the "previous" querystring causes the form to be returned # pre-populated with data from last meeeting's session request elif request.method == 'GET' and request.GET.has_key('previous'): @@ -507,10 +521,10 @@ def new(request, acronym): initial = get_initial_session(previous_sessions) form = SessionForm(initial=initial) - + else: form = SessionForm() - + return render_to_response('sreq/new.html', { 'meeting': meeting, 'form': form, @@ -530,7 +544,7 @@ def no_session(request, acronym): meeting = get_meeting() group = get_object_or_404(Group, acronym=acronym) login = request.user.get_profile() - + # delete canceled record if there is one Session.objects.filter(group=group,meeting=meeting,status='canceled').delete() @@ -539,7 +553,7 @@ def no_session(request, acronym): messages.info(request, 'The group %s is already marked as not meeting' % group.acronym) url = reverse('sessions') return HttpResponseRedirect(url) - + session = Session(group=group, meeting=meeting, requested=datetime.datetime.now(), @@ -547,7 +561,7 @@ def no_session(request, acronym): requested_duration=0, status=SessionStatusName.objects.get(slug='notmeet')) session.save() - + # send notification to_email = SESSION_REQUEST_EMAIL cc_list = get_cc_list(group, login) @@ -557,12 +571,12 @@ def no_session(request, acronym): {'login':login, 'group':group, 'meeting':meeting}, cc=cc_list) - + # deprecated? # log activity #text = 'A message was sent to notify not having a session at IETF %d' % meeting.meeting_num #add_session_activity(group,text,meeting,request.person) - + # redirect messages.success(request, 'A message was sent to notify not having a session at IETF %s' % meeting.number) url = reverse('sessions') @@ -574,32 +588,32 @@ def tool_status(request): This view handles locking and unlocking of the tool to the public. ''' is_locked = check_app_locked() - + if request.method == 'POST': button_text = request.POST.get('submit', '') if button_text == 'Done': url = reverse('sessions') return HttpResponseRedirect(url) - + form = ToolStatusForm(request.POST) - + if button_text == 'Lock': if form.is_valid(): f = open(LOCKFILE,'w') f.write(form.cleaned_data['message']) f.close() - + messages.success(request, 'Session Request Tool is now Locked') url = reverse('sessions') return HttpResponseRedirect(url) - + elif button_text == 'Unlock': os.remove(LOCKFILE) - + messages.success(request, 'Session Request Tool is now Unlocked') url = reverse('sessions') return HttpResponseRedirect(url) - + else: if is_locked: message = get_lock_message() @@ -607,7 +621,7 @@ def tool_status(request): form = ToolStatusForm(initial=initial) else: form = ToolStatusForm() - + return render_to_response('sreq/tool_status.html', { 'is_locked': is_locked, 'form': form}, @@ -621,12 +635,12 @@ def view(request, acronym): meeting = get_meeting() group = get_object_or_404(Group, acronym=acronym) sessions = Session.objects.filter(~Q(status__in=('canceled','notmeet','deleted')),meeting=meeting,group=group).order_by('id') - + # if there are no session requests yet, redirect to new session request page if not sessions: redirect_url = reverse('sessions_new', kwargs={'acronym':acronym}) return HttpResponseRedirect(redirect_url) - + # TODO simulate activity records activities = [{'act_date':sessions[0].requested.strftime('%b %d, %Y'), 'act_time':sessions[0].requested.strftime('%H:%M:%S'), @@ -637,20 +651,20 @@ def view(request, acronym): 'act_time':sessions[0].scheduled.strftime('%H:%M:%S'), 'activity':'Session was scheduled', 'act_by':'Secretariat'}) - + # other groups that list this group in their conflicts session_conflicts = session_conflicts_as_string(group, meeting) show_approve_button = False - + # if sessions include a 3rd session waiting approval and the user is a secretariat or AD of the group # display approve button if sessions.filter(status='apprw'): if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.get_profile()): show_approve_button = True - + # build session dictionary (like querydict from new session request form) for use in template session = get_initial_session(sessions) - + return render_to_response('sreq/view.html', { 'session': session, 'activities': activities, diff --git a/ietf/secr/telechat/views.py b/ietf/secr/telechat/views.py index e2ca4580a..f2d9df6fb 100644 --- a/ietf/secr/telechat/views.py +++ b/ietf/secr/telechat/views.py @@ -180,14 +180,11 @@ def doc_detail(request, date, name): doc = get_object_or_404(Document, docalias__name=name) # As of Datatracker v4.32, Conflict Review (conflrev) Document Types can - # be added to the Telechat agenda. We need to check the document type here - # and set the state_type for use later in the view + # be added to the Telechat agenda. If Document.type_id == draft use draft-iesg + # for state type + state_type = doc.type_id if doc.type_id == 'draft': state_type = 'draft-iesg' - elif doc.type_id == 'conflrev': - state_type = 'conflrev' - elif doc.type_id == 'charter': - state_type = 'charter' started_process = doc.latest_event(type="started_iesg_process") login = request.user.get_profile() diff --git a/ietf/secr/templates/sreq/session_approval_notification.txt b/ietf/secr/templates/sreq/session_approval_notification.txt index 21f0fc053..e957de820 100644 --- a/ietf/secr/templates/sreq/session_approval_notification.txt +++ b/ietf/secr/templates/sreq/session_approval_notification.txt @@ -1,7 +1,7 @@ Dear {{ group.parent }} Director(s): {{ header }} meeting session request has just been -submitted by {{ login }}, a working group chair of {{ group.name }}. +submitted by {{ requester }}. The third session requires your approval. To approve the session go to the session request view here: diff --git a/ietf/secr/templates/sreq/session_cancel_notification.txt b/ietf/secr/templates/sreq/session_cancel_notification.txt index a95fbb9ec..3e6dd43f6 100644 --- a/ietf/secr/templates/sreq/session_cancel_notification.txt +++ b/ietf/secr/templates/sreq/session_cancel_notification.txt @@ -1,4 +1,4 @@ {% load ams_filters %} -A request to cancel a meeting session has just been submitted by {{ login|smart_login }} {{ group.acronym }} working group. +A request to cancel a meeting session has just been submitted by {{ requester }}. diff --git a/ietf/secr/templates/sreq/session_request_notification.txt b/ietf/secr/templates/sreq/session_request_notification.txt index 5d3c28b3b..db2006040 100644 --- a/ietf/secr/templates/sreq/session_request_notification.txt +++ b/ietf/secr/templates/sreq/session_request_notification.txt @@ -1,5 +1,5 @@ {% load ams_filters %} -{{ header }} meeting session request has just been submitted by {{ login|smart_login }} {{ group.acronym }} working group. +{{ header }} meeting session request has just been submitted by {{ requester }}. {% include "includes/session_info.txt" %} From d19fe19ce05d8f81d78e44474aaac35602588207 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Mon, 24 Jun 2013 23:30:30 +0000 Subject: [PATCH 11/26] add Standardization Level to drafts view/edit - Legacy-Id: 5804 --- ietf/secr/drafts/forms.py | 120 +++++++++++++-------------- ietf/secr/templates/drafts/edit.html | 7 +- ietf/secr/templates/drafts/view.html | 3 +- 3 files changed, 66 insertions(+), 64 deletions(-) diff --git a/ietf/secr/drafts/forms.py b/ietf/secr/drafts/forms.py index a17232290..5d4d13846 100644 --- a/ietf/secr/drafts/forms.py +++ b/ietf/secr/drafts/forms.py @@ -13,16 +13,16 @@ import re from os.path import splitext # --------------------------------------------- -# Select Choices +# Select Choices # --------------------------------------------- WITHDRAW_CHOICES = (('ietf','Withdraw by IETF'),('author','Withdraw by Author')) # --------------------------------------------- -# Custom Fields +# Custom Fields # --------------------------------------------- class DocumentField(forms.FileField): '''A validating document upload field''' - + def __init__(self, unique=False, *args, **kwargs): self.extension = kwargs.pop('extension') self.filename = kwargs.pop('filename') @@ -36,7 +36,7 @@ class DocumentField(forms.FileField): m = re.search(r'.*-\d{2}\.(txt|pdf|ps|xml)', file.name) if not m: raise forms.ValidationError('File name must be in the form base-NN.[txt|pdf|ps|xml]') - + # ensure file extension is correct base,ext = os.path.splitext(file.name) if ext != self.extension: @@ -51,44 +51,44 @@ class DocumentField(forms.FileField): next_revision = str(int(self.rev)+1).zfill(2) if base[-2:] != next_revision: raise forms.ValidationError, "Expected revision # %s" % (next_revision) - + return file class GroupModelChoiceField(forms.ModelChoiceField): ''' - Custom ModelChoiceField sets queryset to include all active workgroups and the + Custom ModelChoiceField sets queryset to include all active workgroups and the individual submission group, none. Displays group acronyms as choices. Call it without the queryset argument, for example: - + group = GroupModelChoiceField(required=True) ''' def __init__(self, *args, **kwargs): kwargs['queryset'] = Group.objects.filter(type__in=('wg','individ'),state__in=('bof','proposed','active')).order_by('acronym') super(GroupModelChoiceField, self).__init__(*args, **kwargs) - + def label_from_instance(self, obj): return obj.acronym class AliasModelChoiceField(forms.ModelChoiceField): ''' - Custom ModelChoiceField, just uses Alias name in the select choices as opposed to the + Custom ModelChoiceField, just uses Alias name in the select choices as opposed to the more confusing alias -> doc format used by DocAlias.__unicode__ - ''' + ''' def label_from_instance(self, obj): return obj.name - + # --------------------------------------------- -# Forms +# Forms # --------------------------------------------- class AddModelForm(forms.ModelForm): start_date = forms.DateField() group = GroupModelChoiceField(required=True,help_text='Use group "none" for Individual Submissions') - + class Meta: model = Document fields = ('title','group','stream','start_date','pages','abstract','internal_comments') - - # use this method to set attrs which keeps other meta info from model. + + # use this method to set attrs which keeps other meta info from model. def __init__(self, *args, **kwargs): super(AddModelForm, self).__init__(*args, **kwargs) self.fields['title'].label='Document Name' @@ -104,17 +104,17 @@ class AuthorForm(forms.Form): ''' person = forms.CharField(max_length=50,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.") email = forms.CharField(widget=forms.Select(),help_text="Select an email") - - # check for id within parenthesis to ensure name was selected from the list + + # check for id within parenthesis to ensure name was selected from the list def clean_person(self): person = self.cleaned_data.get('person', '') m = re.search(r'(\d+)', person) if person and not m: - raise forms.ValidationError("You must select an entry from the list!") - + raise forms.ValidationError("You must select an entry from the list!") + # return person object return get_person(person) - + # check that email exists and return the Email object def clean_email(self): email = self.cleaned_data['email'] @@ -122,7 +122,7 @@ class AuthorForm(forms.Form): obj = Email.objects.get(address=email) except Email.ObjectDoesNoExist: raise forms.ValidationError("Email address not found!") - + # return email object return obj @@ -133,12 +133,12 @@ class EditModelForm(forms.ModelForm): group = GroupModelChoiceField(required=True) review_by_rfc_editor = forms.BooleanField(required=False) shepherd = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.",required=False) - + class Meta: model = Document - fields = ('title','group','ad','shepherd','notify','stream','review_by_rfc_editor','name','rev','pages','intended_std_level','abstract','internal_comments') - - # use this method to set attrs which keeps other meta info from model. + fields = ('title','group','ad','shepherd','notify','stream','review_by_rfc_editor','name','rev','pages','intended_std_level','std_level','abstract','internal_comments') + + # use this method to set attrs which keeps other meta info from model. def __init__(self, *args, **kwargs): super(EditModelForm, self).__init__(*args, **kwargs) self.fields['ad'].queryset = Person.objects.filter(role__name='ad') @@ -151,36 +151,36 @@ class EditModelForm(forms.ModelForm): self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk if self.instance.shepherd: self.initial['shepherd'] = "%s - (%s)" % (self.instance.shepherd.name, self.instance.shepherd.id) - + # setup special fields if self.instance: # setup replaced self.fields['review_by_rfc_editor'].initial = bool(self.instance.tags.filter(slug='rfc-rev')) - + def save(self, force_insert=False, force_update=False, commit=True): m = super(EditModelForm, self).save(commit=False) state = self.cleaned_data['state'] iesg_state = self.cleaned_data['iesg_state'] - + if 'state' in self.changed_data: m.set_state(state) - + # note we're not sending notices here, is this desired if 'iesg_state' in self.changed_data: if iesg_state == None: m.unset_state('draft-iesg') else: m.set_state(iesg_state) - + if 'review_by_rfc_editor' in self.changed_data: if self.cleaned_data.get('review_by_rfc_editor',''): m.tags.add('rfc-rev') else: m.tags.remove('rfc-rev') - + m.time = datetime.datetime.now() # handle replaced by - + if commit: m.save() return m @@ -189,19 +189,19 @@ class EditModelForm(forms.ModelForm): def clean_replaced_by(self): name = self.cleaned_data.get('replaced_by', '') if name and not InternetDraft.objects.filter(filename=name): - raise forms.ValidationError("ERROR: Draft does not exist") + raise forms.ValidationError("ERROR: Draft does not exist") return name - - # check for id within parenthesis to ensure name was selected from the list + + # check for id within parenthesis to ensure name was selected from the list def clean_shepherd(self): person = self.cleaned_data.get('shepherd', '') m = re.search(r'(\d+)', person) if person and not m: - raise forms.ValidationError("You must select an entry from the list!") - + raise forms.ValidationError("You must select an entry from the list!") + # return person object return get_person(person) - + def clean(self): super(EditModelForm, self).clean() cleaned_data = self.cleaned_data @@ -233,7 +233,7 @@ class EmailForm(forms.Form): class ExtendForm(forms.Form): expiration_date = forms.DateField() - + class ReplaceForm(forms.Form): replaced = AliasModelChoiceField(DocAlias.objects.none(),empty_label=None,help_text='This document may have more than one alias. Be sure to select the correct alias to replace.') replaced_by = forms.CharField(max_length=100,help_text='Enter the filename of the Draft which replaces this one.') @@ -242,7 +242,7 @@ class ReplaceForm(forms.Form): self.draft = kwargs.pop('draft') super(ReplaceForm, self).__init__(*args, **kwargs) self.fields['replaced'].queryset = DocAlias.objects.filter(document=self.draft) - + # field must contain filename of existing draft def clean_replaced_by(self): name = self.cleaned_data.get('replaced_by', '') @@ -263,49 +263,49 @@ class RevisionModelForm(forms.ModelForm): class Meta: model = Document fields = ('title','pages','abstract') - - # use this method to set attrs which keeps other meta info from model. + + # use this method to set attrs which keeps other meta info from model. def __init__(self, *args, **kwargs): super(RevisionModelForm, self).__init__(*args, **kwargs) self.fields['title'].label='Document Name' self.fields['title'].widget=forms.Textarea() self.fields['pages'].label='Number of Pages' - + class RfcModelForm(forms.ModelForm): rfc_number = forms.IntegerField() rfc_published_date = forms.DateField(initial=datetime.datetime.now) group = GroupModelChoiceField(required=True) - + class Meta: model = Document fields = ('title','group','pages','std_level','internal_comments') - - # use this method to set attrs which keeps other meta info from model. + + # use this method to set attrs which keeps other meta info from model. def __init__(self, *args, **kwargs): super(RfcModelForm, self).__init__(*args, **kwargs) self.fields['title'].widget = forms.Textarea() self.fields['std_level'].required = True - + def save(self, force_insert=False, force_update=False, commit=True): obj = super(RfcModelForm, self).save(commit=False) - + # create DocAlias DocAlias.objects.create(document=self.instance,name="rfc%d" % self.cleaned_data['rfc_number']) - + if commit: obj.save() return obj - + def clean_rfc_number(self): rfc_number = self.cleaned_data['rfc_number'] if DocAlias.objects.filter(name='rfc' + str(rfc_number)): raise forms.ValidationError("RFC %d already exists" % rfc_number) return rfc_number - + class RfcObsoletesForm(forms.Form): relation = forms.ModelChoiceField(queryset=DocRelationshipName.objects.filter(slug__in=('updates','obs')),required=False) rfc = forms.IntegerField(required=False) - + # ensure that RFC exists def clean_rfc(self): rfc = self.cleaned_data.get('rfc','') @@ -313,7 +313,7 @@ class RfcObsoletesForm(forms.Form): if not Document.objects.filter(docalias__name="rfc%s" % rfc): raise forms.ValidationError("RFC does not exist") return rfc - + def clean(self): super(RfcObsoletesForm, self).clean() cleaned_data = self.cleaned_data @@ -348,8 +348,8 @@ class UploadForm(forms.Form): for field in self.fields.itervalues(): field.filename = self.draft.name field.rev = self.draft.rev - - + + def clean(self): # Checks that all files have the same base if any(self.errors): @@ -359,7 +359,7 @@ class UploadForm(forms.Form): xml = self.cleaned_data['xml'] pdf = self.cleaned_data['pdf'] ps = self.cleaned_data['ps'] - + # we only need to do these validations for new drafts if not self.draft: names = [] @@ -368,19 +368,19 @@ class UploadForm(forms.Form): base = splitext(file.name)[0] if base not in names: names.append(base) - + if len(names) > 1: raise forms.ValidationError, "All files must have the same base name" - + # ensure that the basename is unique base = splitext(txt.name)[0] if Document.objects.filter(name=base[:-3]): raise forms.ValidationError, "This doucment filename already exists: %s" % base[:-3] - + # ensure that rev is 00 if base[-2:] != '00': raise forms.ValidationError, "New Drafts must start with 00 revision number." - + return self.cleaned_data class WithdrawForm(forms.Form): diff --git a/ietf/secr/templates/drafts/edit.html b/ietf/secr/templates/drafts/edit.html index 066c22137..eeb4ed7ee 100644 --- a/ietf/secr/templates/drafts/edit.html +++ b/ietf/secr/templates/drafts/edit.html @@ -9,10 +9,10 @@ {% endblock %} -{% block breadcrumbs %}{{ block.super }} +{% block breadcrumbs %}{{ block.super }} » Drafts » {{ draft.name }} - » Edit + » Edit {% endblock %} {% block content %} @@ -36,12 +36,13 @@ Abstract:{{ form.abstract.errors }}{{ form.abstract }} Expiration Date:{{ draft.expires|date:"M. d, Y" }} Intended Std Level:{{ form.intended_std_level.errors }}{{ form.intended_std_level }} + Standardization Level:{{ form.std_level.errors }}{{ form.std_level }} RFC Number:{{ draft.rfc_number }} Comments:{{ form.internal_comments.errors }}{{ form.internal_comments }} Replaced by:{{ form.replaced_by.errors }}{{ form.replaced_by }} Last Modified Date:{{ draft.time }} - +