From edcc97aded640f3030c6146529b146f1b386834f Mon Sep 17 00:00:00 2001
From: Henrik Levkowetz <henrik@levkowetz.com>
Date: Mon, 21 Oct 2013 20:29:15 +0000
Subject: [PATCH] Merged in branch/amsl/1.50@6388 from rcross@amsl.com, with
 secretariat tool announcement fixes, permission fixes, and agenda database
 schema fixes.  - Legacy-Id: 6473

---
 ietf/secr/__init__.py               |   2 +-
 ietf/secr/announcement/forms.py     |  11 ++-
 ietf/secr/announcement/views.py     |   4 +-
 ietf/secr/meetings/forms.py         |   2 +-
 ietf/secr/meetings/views.py         | 137 +++++++++++++---------------
 ietf/secr/proceedings/proc_utils.py |   7 +-
 ietf/secr/proceedings/views.py      |  15 +--
 7 files changed, 85 insertions(+), 93 deletions(-)

diff --git a/ietf/secr/__init__.py b/ietf/secr/__init__.py
index 6dd3ce21e..785253a83 100644
--- a/ietf/secr/__init__.py
+++ b/ietf/secr/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "1.41"
+__version__ = "1.50"
 
 __date__    = "$Date: 2011/07/26 14:29:17 $"
 
diff --git a/ietf/secr/announcement/forms.py b/ietf/secr/announcement/forms.py
index 72dc4b937..eca264aac 100644
--- a/ietf/secr/announcement/forms.py
+++ b/ietf/secr/announcement/forms.py
@@ -35,7 +35,8 @@ FROM_LIST = ('IETF Secretariat <ietf-secretariat@ietf.org>',
              'RSOC Chair <rsoc-chair@iab.org>',
              'ISOC Board of Trustees <eburger@standardstrack.com>',
              'RFC Series Editor <rse@rfc-editor.org>',
-             'IAB Executive Director <execd@iab.org>')
+             'IAB Executive Director <execd@iab.org>',
+             'IETF Mentoring Program <mentoring@ietf.org>')
 
 TO_LIST = ('IETF Announcement List <ietf-announce@ietf.org>',
            'I-D Announcement List <i-d-announce@ietf.org>',
@@ -107,12 +108,16 @@ def get_from_choices(user):
                              group__acronym='iab',
                              name='execdir'):
         f = (FROM_LIST[6],FROM_LIST[16])
+    elif Role.objects.filter(person=person,
+                             group__acronym='mentor',
+                             name="chair"):
+        f = (FROM_LIST[17],)
 
     # NomCom
     nomcoms = Role.objects.filter(name="chair",
                                   group__acronym__startswith="nomcom",
                                   group__state="active",
-                                  group__type="ietf",
+                                  group__type="nomcom",
                                   person=person)
     if nomcoms:
         year = nomcoms[0].group.acronym[-4:]
@@ -141,7 +146,7 @@ TO_CHOICES = get_to_choices()
 
 class AnnounceForm(forms.ModelForm):
     #nomcom = forms.BooleanField(required=False)
-    nomcom = forms.ModelChoiceField(queryset=Group.objects.filter(acronym__startswith='nomcom',type='ietf',state='active'),required=False)
+    nomcom = forms.ModelChoiceField(queryset=Group.objects.filter(acronym__startswith='nomcom',type='nomcom',state='active'),required=False)
     to_custom = MultiEmailField(required=False,label='')
     #cc = MultiEmailField(required=False)
 
diff --git a/ietf/secr/announcement/views.py b/ietf/secr/announcement/views.py
index ca11a6e51..a32ec44f5 100644
--- a/ietf/secr/announcement/views.py
+++ b/ietf/secr/announcement/views.py
@@ -23,7 +23,7 @@ def check_access(user):
     Secretariat, IAD, IAB Chair, IETF Chair, RSOC Chair, IAOC Chair, NomCom Chair, RSE Chair
     '''
     person = user.get_profile()
-    groups_with_access = ("iab", "rsoc", "ietf", "iaoc", "rse")
+    groups_with_access = ("iab", "rsoc", "ietf", "iaoc", "rse", "mentor")
     if Role.objects.filter(person=person,
                            group__acronym__in=groups_with_access,
                            name="chair") or has_role(user, ["Secretariat","IAD"]):
@@ -31,7 +31,7 @@ def check_access(user):
     if Role.objects.filter(name="chair",
                            group__acronym__startswith="nomcom",
                            group__state="active",
-                           group__type="ietf",
+                           group__type="nomcom",
                            person=person):
         return True
     if Role.objects.filter(person=person,
diff --git a/ietf/secr/meetings/forms.py b/ietf/secr/meetings/forms.py
index 7b57fb16c..6b90d716b 100644
--- a/ietf/secr/meetings/forms.py
+++ b/ietf/secr/meetings/forms.py
@@ -146,7 +146,7 @@ class NewSessionForm(forms.Form):
             time_obj = datetime.datetime.combine(day_obj,hour)
             slot = TimeSlot.objects.get(meeting=self.meeting,time=time_obj,location=room)
             next_slot = get_next_slot(slot)
-            if not next_slot or next_slot.session != None:
+            if not next_slot:
                 raise forms.ValidationError('There is no next session to combine')
         
         return cleaned_data
diff --git a/ietf/secr/meetings/views.py b/ietf/secr/meetings/views.py
index b92c10811..aa69a2d40 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.meeting.helpers import get_schedule
 from ietf.group.models import Group
 from ietf.name.models import SessionStatusName, TimeSlotTypeName
@@ -22,7 +22,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 *
 
@@ -106,6 +106,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
@@ -119,9 +121,8 @@ def build_nonsession(meeting):
                               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,
@@ -136,7 +137,7 @@ def is_combined(session):
     '''
     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
@@ -175,7 +176,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,
@@ -204,8 +205,7 @@ 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 = []
@@ -217,10 +217,10 @@ def sort_groups(meeting):
     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)
-            else:
-                unscheduled_groups.append(group)
+        if group in groups_with_timeslots:
+            scheduled_groups.append(group)
+        else:
+            unscheduled_groups.append(group)
 
     return scheduled_groups, unscheduled_groups
 
@@ -418,6 +418,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
@@ -430,15 +438,11 @@ def non_session(request, meeting_id):
                                   requested_by=Person.objects.get(name='(system)'),
                                   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})
@@ -464,13 +468,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')
@@ -483,6 +488,7 @@ 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', '')
@@ -490,7 +496,7 @@ def non_session_edit(request, meeting_id, slot_id):
             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']
@@ -500,7 +506,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
@@ -514,10 +519,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,
@@ -538,10 +543,10 @@ def remove_session(request, meeting_id, acronym):
     now = datetime.datetime.now()
 
     for session in sessions:
-        for timeslot in session.timeslot_set.all():
-            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()
@@ -610,11 +615,12 @@ 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    # default
         if is_combined(s,meeting):
@@ -647,35 +653,24 @@ def schedule(request, meeting_id, acronym):
                     day = form.cleaned_data['day']
                     combine = form.cleaned_data.get('combine',None)
                     session = Session.objects.get(id=id)
-                    was_combined = is_combined(session)
-                    initial_timeslots = session.timeslot_set.all()
-                    if initial_timeslots:
-                        initial_timeslot = initial_timeslots[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]
 
+                    # COMBINE SECTION - BEFORE --------------
+                    if 'combine' in form.changed_data and not combine:
+                        next_slot = get_next_slot(initial_timeslot)
+                        for ss in next_slot.scheduledsession_set.filter(schedule=meeting.agenda,session=session):
+                            ss.session = None
+                            ss.save()
+                    # ---------------------------------------
                     if any(x in form.changed_data for x in ('day','time','room')):
-                        # clear the old timeslot(s)
-                        for ts in initial_timeslots:
+                        # clear the old association
+                        if initial_timeslot:
                             # get SS record(s) and unschedule by removing the session reference
                             for ss in session.scheduledsession_set.filter(schedule=meeting.agenda):
                                 ss.session = None
@@ -696,16 +691,10 @@ def schedule(request, meeting_id, acronym):
                         session.modified = now
                         session.save()
 
-                    # COMBINE SECTION ----------------------
-                    if 'combine' in form.changed_data:
+                    # COMBINE SECTION - AFTER ---------------
+                    if 'combine' in form.changed_data and combine:
                         next_slot = get_next_slot(timeslot)
-                        if combine:
-                            assign(session,next_slot,meeting)
-                        else:
-                            for ss in next_slot.scheduledsession_set.filter(schedule=meeting.agenda,session=session):
-                                ss.session = None
-                                ss.save()
-
+                        assign(session,next_slot,meeting)
                     # ---------------------------------------
 
             # notify.  dont send if Tutorial, BOF or indicated on form
@@ -713,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."
@@ -724,6 +713,7 @@ def schedule(request, meeting_id, acronym):
             url = reverse('meetings_select_group', kwargs={'meeting_id':meeting_id})
             return HttpResponseRedirect(url)
 
+
     else:
         formset = NewSessionFormset(initial=initial)
         extra_form = ExtraSessionForm()
@@ -856,9 +846,10 @@ def times_delete(request, meeting_id, time):
     parts = [ int(x) for x in time.split(':') ]
     dtime = datetime.datetime(*parts)
 
-    if ScheduledSession.objects.filter(timeslot__time=dtime,
-                                       timeslot__meeting=meeting,
-                                       session__isnull=False):
+    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 bc3f599af..ea2e69e2c 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
 from models import InterimMeeting    # proxy model
 
 from urllib2 import urlopen
@@ -32,7 +32,8 @@ 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
+        session = get_session(timeslot)
+        group = session.group
         key = '%s:%s' % (group.parent.acronym, group.acronym)
     except AttributeError:
         key = None
@@ -528,7 +529,7 @@ def gen_research(context):
 def gen_training(context):
     meeting = context['meeting']
     timeslots = context['others']
-    sessions = [ t.session for t in timeslots ]
+    sessions = [ get_session(t) for t in timeslots ]
     for counter,session in enumerate(sessions, start=1):
         slides = session.materials.filter(type='slides')
         minutes = session.materials.filter(type='minutes')
diff --git a/ietf/secr/proceedings/views.py b/ietf/secr/proceedings/views.py
index dddea1a13..b927de710 100644
--- a/ietf/secr/proceedings/views.py
+++ b/ietf/secr/proceedings/views.py
@@ -27,7 +27,7 @@ from ietf.group.models import Group
 from ietf.group.proxy import IETFWG
 from ietf.group.utils import get_charter_text
 from ietf.ietfauth.decorators import has_role
-from ietf.meeting.models import Meeting, Session, TimeSlot
+from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession
 from ietf.name.models import MeetingTypeName, SessionStatusName
 from ietf.person.models import Person
 
@@ -685,22 +685,17 @@ def select(request, meeting_num):
 
     # initialize Training form, this select widget needs to have a session id, because it's
     # utilmately the session that we associate material with
-    # NOTE: there are two ways to query for the groups we want, the later seems more specific
     if has_role(user,'Secretariat'):
-        choices = []
-        #for session in Session.objects.filter(meeting=meeting).exclude(name=""):
-        for session in Session.objects.filter(meeting=meeting,timeslot__type='other').order_by('name'):
-            choices.append((session.id,session.timeslot_set.all()[0].name))
+        ss = ScheduledSession.objects.filter(schedule=meeting.agenda,timeslot__type='other')
+        choices = [ (i.session.id, i.session.name) for i in sorted(ss,key=lambda x: x.session.name) ]
         training_form = GroupSelectForm(choices=choices)
     else:
         training_form = None
 
     # iniialize plenary form
     if has_role(user,['Secretariat','IETF Chair','IAB Chair']):
-        choices = []
-        for session in Session.objects.filter(meeting=meeting,
-                                              timeslot__type='plenary').order_by('name'):
-            choices.append((session.id,session.timeslot_set.all()[0].name))
+        ss = ScheduledSession.objects.filter(schedule=meeting.agenda,timeslot__type='plenary')
+        choices = [ (i.session.id, i.session.name) for i in sorted(ss,key=lambda x: x.session.name) ]
         plenary_form = GroupSelectForm(choices=choices)
     else:
         plenary_form = None