allow edit of timeslots, changes to scheduled notifications, support non-official schedules

- Legacy-Id: 7554
This commit is contained in:
Ryan Cross 2014-03-28 23:16:41 +00:00
parent e3d946999c
commit 0fafe56381
17 changed files with 422 additions and 157 deletions

View file

@ -139,10 +139,10 @@ class GroupMilestoneInfo(models.Model):
docs = models.ManyToManyField('doc.Document', blank=True)
def __unicode__(self):
return self.desc[:20] + "..."
return self.desc[:20] + "..."
class Meta:
abstract = True
ordering = ['due', 'id']
ordering = ['due', 'id']
class GroupMilestone(GroupMilestoneInfo):
time = models.DateTimeField(auto_now=True)
@ -159,7 +159,7 @@ class GroupStateTransitions(models.Model):
next_states = models.ManyToManyField('doc.State', related_name='previous_groupstatetransitions_states')
def __unicode__(self):
return u'%s "%s" -> %s' % (self.group.acronym, self.state.name, [s.name for s in self.next_states.all()])
return u'%s "%s" -> %s' % (self.group.acronym, self.state.name, [s.name for s in self.next_states.all()])
GROUP_EVENT_CHOICES = [
("changed_state", "Changed state"),
@ -167,6 +167,7 @@ GROUP_EVENT_CHOICES = [
("info_changed", "Changed metadata"),
("requested_close", "Requested closing group"),
("changed_milestone", "Changed milestone"),
("sent_notification", "Sent notification")
]
class GroupEvent(models.Model):

View file

@ -206,20 +206,9 @@ class Meeting(models.Model):
return ''
def set_official_agenda(self, agenda):
# send_notification should be refactored into Session
from ietf.secr.meetings.views import send_notification
if self.agenda != agenda:
self.agenda = agenda
self.save()
if self.agenda is not None:
for ss in self.agenda.scheduledsession_set.all():
session = ss.session
if session.status.slug == "schedw":
session.status_id = "sched"
session.scheduled = datetime.datetime.now()
session.save()
# refactoring send_notification will obviate this odd hoop-jump
send_notification(None, Session.objects.filter(id=session.id))
class Meta:
ordering = ["-date", ]
@ -1174,3 +1163,4 @@ class Session(models.Model):
if self.badness_test(1):
self.badness_log(1, "badgroup: %s badness = %u\n" % (self.group.acronym, badness))
return badness

View file

@ -1,7 +1,7 @@
from django.core.urlresolvers import reverse
from ietf.utils import TestCase
from ietf.meeting.models import Meeting
from ietf.meeting.models import Meeting, Schedule
from ietf.utils.test_data import make_test_data
from pyquery import PyQuery
@ -22,3 +22,14 @@ class MainTestCase(TestCase):
url = reverse('meetings_view', kwargs={'meeting_id':meeting.number})
response = self.client.get(url, REMOTE_USER=SECR_USER)
self.assertEqual(response.status_code, 200)
def test_add_meeting(self):
"Add Meeting"
url = reverse('meetings_add')
post_data = dict(number=1,city='Seattle',date='2014-07-20',country='US',
time_zone='America/Los_Angeles',venue_name='Hilton',
venue_addr='100 First Ave')
response = self.client.post(url, post_data,follow=True,REMOTE_USER=SECR_USER)
self.assertEqual(response.status_code, 200)
self.assertEqual(Meeting.objects.count(),1)
self.assertEqual(Schedule.objects.count(),1)

View file

@ -9,14 +9,17 @@ urlpatterns = patterns('ietf.secr.meetings.views',
url(r'^(?P<meeting_id>\d{1,6})/blue_sheet/$', 'blue_sheet', name='meetings_blue_sheet'),
url(r'^(?P<meeting_id>\d{1,6})/blue_sheet/generate/$', 'blue_sheet_generate', name='meetings_blue_sheet_generate'),
url(r'^(?P<meeting_id>\d{1,6})/edit/$', 'edit_meeting', name='meetings_edit_meeting'),
url(r'^(?P<meeting_id>\d{1,6})/rooms/$', 'rooms', name='meetings_rooms'),
url(r'^(?P<meeting_id>\d{1,6})/times/$', 'times', name='meetings_times'),
url(r'^(?P<meeting_id>\d{1,6})/times/delete/(?P<time>[0-9\:]+)/$', 'times_delete', name='meetings_times_delete'),
url(r'^(?P<meeting_id>\d{1,6})/non_session/$', 'non_session', name='meetings_non_session'),
url(r'^(?P<meeting_id>\d{1,6})/non_session/edit/(?P<slot_id>\d{1,6})/$', 'non_session_edit', name='meetings_non_session_edit'),
url(r'^(?P<meeting_id>\d{1,6})/non_session/delete/(?P<slot_id>\d{1,6})/$', 'non_session_delete', name='meetings_non_session_delete'),
url(r'^(?P<meeting_id>\d{1,6})/select/$', 'select_group',
name='meetings_select_group'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<acronym>[A-Za-z0-9_\-\+]+)/schedule/$', 'schedule', name='meetings_schedule'),
url(r'^(?P<meeting_id>\d{1,6})/notifications/$', 'notifications', name='meetings_notifications'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/$', 'select', name='meetings_select'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/$', 'non_session', name='meetings_non_session'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/edit/(?P<slot_id>\d{1,6})/$', 'non_session_edit', name='meetings_non_session_edit'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/delete/(?P<slot_id>\d{1,6})/$', 'non_session_delete', name='meetings_non_session_delete'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/rooms/$', 'rooms', name='meetings_rooms'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/select/$', 'select_group', name='meetings_select_group'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/$', 'times', name='meetings_times'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/delete/(?P<time>[0-9\:]+)/$', 'times_delete', name='meetings_times_delete'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/edit/(?P<time>[0-9\:]+)/$', 'times_edit', name='meetings_times_edit'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/unschedule/(?P<session_id>\d{1,6})/$', 'unschedule', name='meetings_unschedule'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/(?P<acronym>[A-Za-z0-9_\-\+]+)/schedule/$', 'schedule', name='meetings_schedule'),
url(r'^(?P<meeting_id>\d{1,6})/(?P<acronym>[A-Za-z0-9_\-\+]+)/remove/$', 'remove_session', name='meetings_remove_session'),
)

View file

@ -13,7 +13,7 @@ from django.utils.functional import curry
from ietf.utils.mail import send_mail
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.group.models import Group, GroupEvent
from ietf.name.models import SessionStatusName, TimeSlotTypeName
from ietf.person.models import Person
from ietf.secr.meetings.blue_sheets import create_blue_sheets
@ -33,27 +33,15 @@ import json
# --------------------------------------------------
# Helper Functions
# --------------------------------------------------
def assign(session,timeslot,meeting):
def assign(session,timeslot,meeting,schedule=None):
'''
Robust function to assign a session to a timeslot
Robust function to assign a session to a timeslot. Much simplyfied 2014-03-26.
'''
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)
if schedule == None:
schedule = meeting.agenda
ScheduledSession.objects.create(schedule=schedule,
session=session,
timeslot=timeslot)
session.status_id = 'sched'
session.save()
@ -135,11 +123,13 @@ def get_last_meeting(meeting):
last_number = int(meeting.number) - 1
return Meeting.objects.get(number=last_number)
def is_combined(session):
def is_combined(session,meeting,schedule=None):
'''
Check to see if this session is using two combined timeslots
'''
if session.scheduledsession_set.filter(schedule=meeting.agenda).count() > 1:
if schedule == None:
schedule = meeting.agenda
if session.scheduledsession_set.filter(schedule=schedule).count() > 1:
return True
else:
return False
@ -156,60 +146,63 @@ def make_directories(meeting):
if not os.path.exists(os.path.join(path,d)):
os.mkdir(os.path.join(path,d))
def send_notification(request, sessions):
def send_notifications(meeting, groups, person):
'''
This view generates notifications for schedule sessions
Send session scheduled email notifications for each group in groups. Person is the
user who initiated this action, request.uesr.get_profile().
'''
session_info_template = '''{0} Session {1} ({2})
{3}, {4} {5}
Room Name: {6}
---------------------------------------------
'''
now = datetime.datetime.now()
for group in groups:
sessions = group.session_set.filter(meeting=meeting)
to_email = sessions[0].requested_by.role_email('chair').address
# TODO confirm list, remove requested_by from cc, add session-request@ietf.org?
cc_list = get_cc_list(group)
from_email = ('"IETF Secretariat"','agenda@ietf.org')
if len(sessions) == 1:
subject = '%s - Requested session has been scheduled for IETF %s' % (group.acronym, meeting.number)
else:
subject = '%s - Requested sessions have been scheduled for IETF %s' % (group.acronym, meeting.number)
template = 'meetings/session_schedule_notification.txt'
requestinguser = None
if request is not None:
requestinguser = request.user.person
# easier to populate template from timeslot perspective. assuming one-to-one timeslot-session
count = 0
session_info = ''
data = [ (s,get_timeslot(s)) for s in sessions ]
for s,t in data:
count += 1
session_info += session_info_template.format(group.acronym,
count,
s.requested_duration,
t.time.strftime('%A'),
t.name,
'%s-%s' % (t.time.strftime('%H%M'),(t.time + t.duration).strftime('%H%M')),
t.location)
group = sessions[0].group
to_email = sessions[0].requested_by.role_email('chair').address
cc_list = get_cc_list(group, requestinguser)
from_email = ('"IETF Secretariat"','agenda@ietf.org')
if len(sessions) == 1:
subject = '%s - Requested session has been scheduled for IETF %s' % (group.acronym, sessions[0].meeting.number)
else:
subject = '%s - Requested sessions have been scheduled for IETF %s' % (group.acronym, sessions[0].meeting.number)
template = 'meetings/session_schedule_notification.txt'
# send email
context = {}
context['to_name'] = sessions[0].requested_by
context['agenda_note'] = sessions[0].agenda_note
context['session'] = get_initial_session(sessions)
context['session_info'] = session_info
context['group'] = group
context['login'] = sessions[0].requested_by
# easier to populate template from timeslot perspective. assuming one-to-one timeslot-session
count = 0
session_info = ''
data = [ (s,get_timeslot(s)) for s in sessions ]
for s,t in data:
count += 1
session_info += session_info_template.format(group.acronym,
count,
s.requested_duration,
t.time.strftime('%A'),
t.name,
'%s-%s' % (t.time.strftime('%H%M'),(t.time + t.duration).strftime('%H%M')),
t.location)
# send email
context = {}
context['to_name'] = sessions[0].requested_by
context['agenda_note'] = sessions[0].agenda_note
context['session'] = get_initial_session(sessions)
context['session_info'] = session_info
context['group'] = group
context['login'] = sessions[0].requested_by
send_mail(request,
to_email,
from_email,
subject,
template,
context,
cc=cc_list)
send_mail(None,
to_email,
from_email,
subject,
template,
context,
cc=cc_list)
# create sent_notification event
GroupEvent.objects.create(group=group,time=now,type='sent_notification',
by=person,desc='sent scheduled notification for %s' % meeting)
def sort_groups(meeting):
'''
@ -401,12 +394,13 @@ def main(request):
RequestContext(request, {}),
)
def non_session(request, meeting_id):
def non_session(request, meeting_id, schedule_name):
'''
Display and add "non-session" time slots, ie. registration, beverage and snack breaks
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
# 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)
@ -453,7 +447,7 @@ def non_session(request, meeting_id):
schedule=meeting.agenda)
messages.success(request, 'Non-Sessions updated successfully')
return redirect('meetings_non_session', meeting_id=meeting_id)
return redirect('meetings_non_session', meeting_id=meeting_id, schedule_name=schedule.name)
else:
form = NonSessionForm(initial={'show_location':True})
@ -463,11 +457,12 @@ def non_session(request, meeting_id):
return render_to_response('meetings/non_session.html', {
'slots': slots,
'form': form,
'meeting': meeting},
'meeting': meeting,
'schedule': schedule},
RequestContext(request, {}),
)
def non_session_delete(request, meeting_id, slot_id):
def non_session_delete(request, meeting_id, schedule_name, slot_id):
'''
This function deletes the non-session TimeSlot. For "other" and "plenary" timeslot types
we need to delete the corresponding Session object as well. Check for uploaded material
@ -475,30 +470,30 @@ 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'):
session = get_session(slot)
session = get_session(slot,schedule=schedule)
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)
return redirect('meetings_non_session', meeting_id=meeting_id)
return redirect('meetings_non_session', meeting_id=meeting_id, schedule_name=schedule_name)
else:
slot.sessions.all().delete()
slot.delete()
messages.success(request, 'Non-Session timeslot deleted successfully')
return redirect('meetings_non_session', meeting_id=meeting_id)
return redirect('meetings_non_session', meeting_id=meeting_id, schedule_name=schedule_name)
def non_session_edit(request, meeting_id, slot_id):
def non_session_edit(request, meeting_id, schedule_name, slot_id):
'''
Allows the user to assign a location to this non-session timeslot
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
slot = get_object_or_404(TimeSlot, id=slot_id)
session = get_session(slot)
session = get_session(slot,schedule=schedule)
if request.method == 'POST':
button_text = request.POST.get('submit', '')
if button_text == 'Cancel':
return redirect('meetings_non_session', meeting_id=meeting_id)
return redirect('meetings_non_session', meeting_id=meeting_id, schedule_name=schedule_name)
form = NonSessionEditForm(request.POST,meeting=meeting, session=session)
if form.is_valid():
@ -516,7 +511,7 @@ def non_session_edit(request, meeting_id, slot_id):
session.save()
messages.success(request, 'Location saved')
return redirect('meetings_non_session', meeting_id=meeting_id)
return redirect('meetings_non_session', meeting_id=meeting_id, schedule_name=schedule_name)
else:
# we need to pass the session to the form in order to disallow changing
@ -534,6 +529,41 @@ def non_session_edit(request, meeting_id, slot_id):
RequestContext(request, {}),
)
def notifications(request, meeting_id):
'''
Send scheduled session email notifications. Finds all groups with
schedule changes since the last time notifications were sent.
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
last_notice = GroupEvent.objects.filter(type='sent_notification').first()
groups = set()
for ss in meeting.agenda.scheduledsession_set.filter(timeslot__type='session'):
last_notice = ss.session.group.latest_event(type='sent_notification')
if last_notice and ss.modified > last_notice.time:
groups.add(ss.session.group)
elif not last_notice:
groups.add(ss.session.group)
if request.method == "POST":
# ensure session state is scheduled
for ss in meeting.agenda.scheduledsession_set.all():
session = ss.session
if session.status.slug == "schedw":
session.status_id = "sched"
session.scheduled = datetime.datetime.now()
session.save()
send_notifications(meeting,groups,request.user.person)
messages.success(request, "Notifications Sent")
return redirect('meetings_view', meeting_id=meeting.number)
return render_to_response('meetings/notifications.html', {
'meeting': meeting,
'groups': groups,
'last_notice': last_notice },
RequestContext(request, {}),
)
def remove_session(request, meeting_id, acronym):
'''
Remove session from agenda. Disassociate session from timeslot and set status.
@ -557,12 +587,13 @@ def remove_session(request, meeting_id, acronym):
messages.success(request, '%s Session removed from agenda' % (group.acronym))
return redirect('meetings_select_group', meeting_id=meeting.number)
def rooms(request, meeting_id):
def rooms(request, meeting_id, schedule_name):
'''
Display and edit MeetingRoom records for the specified meeting
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
# if no rooms exist yet (new meeting) formset extra=10
first_time = not bool(meeting.room_set.all())
extra = 10 if first_time else 0
@ -571,7 +602,7 @@ def rooms(request, meeting_id):
if request.method == 'POST':
button_text = request.POST.get('submit', '')
if button_text == 'Cancel':
return redirect('meetings', meeting_id=meeting_id)
return redirect('meetings', meeting_id=meeting_id,schedule_name=schedule_name)
formset = RoomFormset(request.POST, instance=meeting, prefix='room')
if formset.is_valid():
@ -589,22 +620,25 @@ def rooms(request, meeting_id):
build_timeslots(meeting,room=form.instance)
messages.success(request, 'Meeting Rooms changed successfully')
return redirect('meetings_rooms', meeting_id=meeting_id)
return redirect('meetings_rooms', meeting_id=meeting_id, schedule_name=schedule_name)
else:
formset = RoomFormset(instance=meeting, prefix='room')
return render_to_response('meetings/rooms.html', {
'meeting': meeting,
'schedule': schedule,
'formset': formset},
RequestContext(request, {}),
)
def schedule(request, meeting_id, acronym):
def schedule(request, meeting_id, schedule_name, acronym):
'''
This view handles scheduling session requests to TimeSlots
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
group = get_object_or_404(Group, acronym=acronym)
sessions = Session.objects.filter(meeting=meeting,group=group,status__in=('schedw','apprw','appr','sched','canceled'))
legacy_session = get_initial_session(sessions)
session_conflicts = session_conflicts_as_string(group, meeting)
@ -615,7 +649,7 @@ def schedule(request, meeting_id, acronym):
for s in sessions:
d = {'session':s.id,
'note':s.agenda_note}
timeslot = get_timeslot(s)
timeslot = get_timeslot(s, schedule=schedule)
if timeslot:
d['room'] = timeslot.location.id
@ -623,7 +657,7 @@ def schedule(request, meeting_id, acronym):
d['time'] = timeslot.time.strftime('%H%M')
else:
d['day'] = 2 # default
if is_combined(s,meeting):
if is_combined(s,meeting,schedule=schedule):
d['combine'] = True
initial.append(d)
@ -634,7 +668,7 @@ def schedule(request, meeting_id, acronym):
if request.method == 'POST':
button_text = request.POST.get('submit', '')
if button_text == 'Cancel':
return redirect('meetings_select_group', meeting_id=meeting_id)
return redirect('meetings_select_group', meeting_id=meeting_id,schedule_name=schedule_name)
formset = NewSessionFormset(request.POST,initial=initial)
extra_form = ExtraSessionForm(request.POST)
@ -652,7 +686,7 @@ def schedule(request, meeting_id, acronym):
day = form.cleaned_data['day']
combine = form.cleaned_data.get('combine',None)
session = Session.objects.get(id=id)
initial_timeslot = get_timeslot(session)
initial_timeslot = get_timeslot(session,schedule=schedule)
# find new timeslot
new_day = meeting.date + datetime.timedelta(days=int(day)-1)
@ -663,20 +697,18 @@ def schedule(request, meeting_id, acronym):
# 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):
for ss in next_slot.scheduledsession_set.filter(schedule=schedule,session=session):
ss.session = None
ss.save()
# ---------------------------------------
if any(x in form.changed_data for x in ('day','time','room')):
# 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
ss.save()
# delete scheduledsession records to unschedule
session.scheduledsession_set.filter(schedule=schedule).delete()
if timeslot:
assign(session,timeslot,meeting)
assign(session,timeslot,meeting,schedule=schedule)
if timeslot.sessions.all().count() > 1:
messages.warning(request, 'WARNING: There are now multiple sessions scheduled for the timeslot: %s' % timeslot)
else:
@ -693,23 +725,23 @@ def schedule(request, meeting_id, acronym):
# COMBINE SECTION - AFTER ---------------
if 'combine' in form.changed_data and combine:
next_slot = get_next_slot(timeslot)
assign(session,next_slot,meeting)
assign(session,next_slot,meeting,schedule=schedule)
# ---------------------------------------
# notify. dont send if Tutorial, BOF or indicated on form
notification_message = "No notification has been sent to anyone for this session."
if (has_changed
and not extra_form.cleaned_data.get('no_notify',False)
and group.state.slug != 'bof'
and get_timeslot(session)): # and the session is scheduled, else skip
#notification_message = "No notification has been sent to anyone for this session."
#if (has_changed
# and not extra_form.cleaned_data.get('no_notify',False)
# and group.state.slug != 'bof'
# and get_timeslot(session,schedule=schedule)): # and the session is scheduled, else skip
send_notification(request, sessions)
notification_message = "Notification sent."
# send_notification(request, sessions)
# notification_message = "Notification sent."
if has_changed:
messages.success(request, 'Session(s) Scheduled for %s. %s' % (group.acronym, notification_message))
return redirect('meetings_select_group', meeting_id=meeting_id)
return redirect('meetings_select_group', meeting_id=meeting_id,schedule_name=schedule_name)
else:
@ -720,13 +752,27 @@ def schedule(request, meeting_id, acronym):
'extra_form': extra_form,
'group': group,
'meeting': meeting,
'schedule': schedule,
'show_request': True,
'session': legacy_session,
'formset': formset},
RequestContext(request, {}),
)
def select_group(request, meeting_id):
def select(request, meeting_id, schedule_name):
'''
Options to edit Rooms & Times or schedule a session
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
return render_to_response('meetings/select.html', {
'meeting': meeting,
'schedule': schedule},
RequestContext(request, {}),
)
def select_group(request, meeting_id, schedule_name):
'''
In this view the user can select the group to schedule. Only those groups that have
submitted session requests appear in the dropdowns.
@ -734,7 +780,8 @@ def select_group(request, meeting_id):
NOTE: BOF list includes Proposed Working Group type, per Wanda
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
if request.method == 'POST':
group = request.POST.get('group',None)
if group:
@ -765,11 +812,12 @@ def select_group(request, meeting_id):
'bof_form': bof_form,
'irtf_form': irtf_form,
'scheduled_groups': scheduled_groups,
'meeting': meeting},
'meeting': meeting,
'schedule': schedule},
RequestContext(request, {}),
)
def times(request, meeting_id):
def times(request, meeting_id, schedule_name):
'''
Display and edit time slots (TimeSlots). It doesn't display every TimeSlot
object for the meeting because there is one timeslot per time per room,
@ -778,6 +826,7 @@ def times(request, meeting_id):
prepopulated from the last meeting
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
# build list of timeslots
slots = []
@ -808,7 +857,7 @@ def times(request, meeting_id):
# 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'))
return redirect('meetings_times', meeting_id=meeting_id)
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
for room in meeting.room_set.all():
ts = TimeSlot.objects.create(type_id='session',
@ -820,7 +869,7 @@ def times(request, meeting_id):
#ScheduledSession.objects.create(schedule=meeting.agenda,timeslot=ts)
messages.success(request, 'Timeslots created')
return redirect('meetings_times', meeting_id=meeting_id)
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
else:
form = TimeSlotForm()
@ -828,17 +877,73 @@ def times(request, meeting_id):
return render_to_response('meetings/times.html', {
'form': form,
'meeting': meeting,
'schedule': schedule,
'times': times},
RequestContext(request, {}),
)
def times_delete(request, meeting_id, time):
def times_edit(request, meeting_id, schedule_name, time):
'''
This view handles bulk edit of timeslot details.
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts)
timeslots = TimeSlot.objects.filter(meeting=meeting,time=dtime)
if request.method == 'POST':
button_text = request.POST.get('submit', '')
if button_text == 'Cancel':
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
form = TimeSlotForm(request.POST)
if form.is_valid():
day = form.cleaned_data['day']
time = form.cleaned_data['time']
duration = form.cleaned_data['duration']
name = form.cleaned_data['name']
t = meeting.date + datetime.timedelta(days=int(day))
new_time = datetime.datetime(t.year,t.month,t.day,time.hour,time.minute)
for timeslot in timeslots:
timeslot.time = new_time
timeslot.duration = duration
timeslot.name = name
timeslot.save()
messages.success(request, 'TimeSlot saved')
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
else:
# we need to pass the session to the form in order to disallow changing
# of group after materials have been uploaded
day = dtime.strftime('%w')
if day == 6:
day = -1
initial = {'day':day,
'time':dtime.strftime('%H:%M'),
'duration':timeslots.first().duration,
'name':timeslots.first().name}
form = TimeSlotForm(initial=initial)
return render_to_response('meetings/times_edit.html', {
'meeting': meeting,
'schedule': schedule,
'form': form},
RequestContext(request, {}),
)
def times_delete(request, meeting_id, schedule_name, time):
'''
This view handles bulk delete of all timeslots matching time (datetime) for the given
meeting. There is one timeslot for each room.
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
parts = [ int(x) for x in time.split(':') ]
dtime = datetime.datetime(*parts)
@ -847,12 +952,26 @@ def times_delete(request, meeting_id, time):
if qs:
messages.error(request, 'ERROR deleting timeslot. There is one or more sessions scheduled for this timeslot.')
return redirect('meetings_times', meeting_id=meeting_id)
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
TimeSlot.objects.filter(meeting=meeting,time=dtime).delete()
messages.success(request, 'Timeslot deleted')
return redirect('meetings_times', meeting_id=meeting_id)
return redirect('meetings_times', meeting_id=meeting_id,schedule_name=schedule_name)
def unschedule(request, meeting_id, schedule_name, session_id):
'''
Unschedule given session object
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
session = get_object_or_404(Session, id=session_id)
session.scheduledsession_set.filter(schedule=meeting.agenda).delete()
# TODO: change session state?
messages.success(request, 'Session unscheduled')
return redirect('meetings_select_group', meeting_id=meeting_id, schedule_name=schedule_name)
def view(request, meeting_id):
'''
@ -868,7 +987,7 @@ def view(request, meeting_id):
'''
meeting = get_object_or_404(Meeting, number=meeting_id)
return render_to_response('meetings/view.html', {
'meeting': meeting},
RequestContext(request, {}),

View file

@ -11,8 +11,9 @@
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="../">{{ meeting.number }}</a>
&raquo; <a href="{% url "meetings" %}">Meetings</a>
&raquo; <a href="{% url "meetings_view" meeting_id=meeting.number %}">{{ meeting.number }}</a>
&raquo; <a href="{% url "meetings_select" meeting_id=meeting.number schedule_name=schedule.name %}">{{ schedule.name }}</a>
&raquo; Rooms and Times
{% endblock %}
@ -21,9 +22,9 @@
<div id="nav" class="rooms-times-nav">
<ul id="list-nav">
<li id="nav-room" class="leftmost"><a href="{% url "meetings_rooms" meeting_id=meeting.number %}">Rooms</a></li>
<li id="nav-time"><a href="{% url "meetings_times" meeting_id=meeting.number %}">Times</a></li>
<li id="nav-non-session"><a href="{% url "meetings_non_session" meeting_id=meeting.number %}">Non-Session</a></li>
<li id="nav-room" class="leftmost"><a href="{% url "meetings_rooms" meeting_id=meeting.number schedule_name=schedule.name %}">Rooms</a></li>
<li id="nav-time"><a href="{% url "meetings_times" meeting_id=meeting.number schedule_name=schedule.name %}">Times</a></li>
<li id="nav-non-session"><a href="{% url "meetings_non_session" meeting_id=meeting.number schedule_name=schedule.name %}">Non-Session</a></li>
</ul>
</div>

View file

@ -8,8 +8,9 @@
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../">Meetings</a>
&raquo; Blue Sheet
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="../">{{ meeting.number }}</a>
&raquo; Blue Sheets
{% endblock %}
{% block content %}
@ -36,7 +37,7 @@
<div class="button-group">
<ul>
<li><button onclick="window.location='../../'">Back</button></li>
<li><button onclick="window.location='../'">Back</button></li>
</ul>
</div> <!-- button-group -->

View file

@ -29,13 +29,13 @@
<td>{{ item.session.short }}</td>
<td>{{ item.session.group.acronym }}</td>
{% if item.type.slug == 'other' or item.type.slug == 'plenary' %}
<td><a href="{% url "meetings_non_session_edit" meeting_id=meeting.number slot_id=item.id %}">{{ item.location }}</a></td>
<td><a href="{% url "meetings_non_session_edit" meeting_id=meeting.number schedule_name=schedule.name slot_id=item.id %}">{{ item.location }}</a></td>
{% else %}
<td>{{ item.location }}</td>
{% endif %}
<td>{{ item.show_location }}</td>
<td>{{ item.type }}</td>
<td><a href="{% url "meetings_non_session_delete" meeting_id=meeting.number slot_id=item.id %}">Delete</a></td>
<td><a href="{% url "meetings_non_session_delete" meeting_id=meeting.number schedule_name=schedule.name slot_id=item.id %}">Delete</a></td>
</tr>
{% endfor %}
</tbody>

View file

@ -0,0 +1,53 @@
{% extends "base_site.html" %}
{% block title %}Meetings{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="../">{{ meeting.number }}</a>
&raquo; Notifications
{% endblock %}
{% block content %}
<div class="module">
<h2>IETF {{ meeting.number }} - Send Notifications</h2>
<form id="id_notification_form" action="." method="post">{% csrf_token %}
<p>Send email notifications to all groups that have been scheduled since the last
notification went out on {{ last_notice.time|date:"Y-m-d" }}:</p>
<p>{% if not groups %}(none){% endif %}{% for group in groups %}{{ group.acronym }}{% if not forloop.last %},{% endif %}{% endfor %}<p>
<input type="submit" value="Send Now" name="submit" onclick="return window.confirm('Are you sure you want to send notifications?');">
</form>
{% include "includes/buttons_back.html" %}
</div> <!-- module -->
{% endblock %}

View file

@ -37,7 +37,8 @@
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="{% url "meetings_view" meeting_id=meeting.number %}">{{ meeting }}</a>
&raquo; <a href="{% url "meetings_select_group" meeting_id=meeting.number %}">Select</a>
&raquo; <a href="{% url "meetings_select" meeting_id=meeting.number schedule_name=schedule.name %}">{{ schedule.name }}</a>
&raquo; <a href="{% url "meetings_select_group" meeting_id=meeting.number schedule_name=schedule.name %}">Select</a>
&raquo; {{ group.acronym }}
{% endblock %}
@ -75,7 +76,7 @@
<ul>
<li><button type="submit" name="submit" value="Save">Save</button></li>
<li><button type="button" onclick="if (window.confirm('This group will be permanently removed from the agenda and scheduling tool')) { window.location='{% url "meetings_remove_session" meeting_id=meeting.number acronym=group.acronym %}' };">Remove this group from agenda</button></li>
<li><button type="button" onclick="window.location='{% url "meetings_select_group" meeting_id=meeting.number %}'">Back</button></li>
<li><button type="button" onclick="window.location='{% url "meetings_select_group" meeting_id=meeting.number schedule_name=schedule.name %}'">Back</button></li>
</ul>
</div> <!-- button-group -->

View file

@ -0,0 +1,51 @@
{% extends "base_site.html" %}
{% block title %}Meetings{% endblock %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="../">{{ meeting.number }}</a>
&raquo; {{ schedule.name }}
{% endblock %}
{% block content %}
<div class="module">
<h2>IETF {{ meeting.number }}</h2>
<div class="button-group">
<ul>
<li><button onclick="window.location='{% url "meetings_rooms" meeting_id=meeting.number schedule_name=schedule.name %}'">Rooms and Times</button></li>
<li><button onclick="window.location='{% url "meetings_select_group" meeting_id=meeting.number schedule_name=schedule.name %}'">Schedule WG Sessions</button></li>
<li><button type="button" onclick="window.location='../'">Back</button></li>
</ul>
</div> <!-- button-group -->
</div> <!-- module -->
{% endblock %}

View file

@ -7,8 +7,9 @@
{% endblock %}
{% block breadcrumbs %}{{ block.super }}
&raquo; <a href="../../">Meetings</a>
&raquo; <a href="../">{{ meeting.number }}</a>
&raquo; <a href="{% url "meetings" %}">Meetings</a>
&raquo; <a href="{% url "meetings_view" meeting_id=meeting.number %}">{{ meeting.number }}</a>
&raquo; <a href="{% url "meetings_select" meeting_id=meeting.number schedule_name=schedule.name %}">{{ schedule.name }}</a>
&raquo; Select Group
{% endblock %}
@ -49,7 +50,7 @@
<h2>Scheduled Sessions</h2>
<ul>
{% for group in scheduled_groups %}
<li><a href="{% url "meetings_schedule" meeting_id=meeting.number acronym=group.acronym %}">{{ group.acronym }}</a></li>
<li><a href="{% url "meetings_schedule" meeting_id=meeting.number schedule_name=schedule.name acronym=group.acronym %}">{{ group.acronym }}</a></li>
{% endfor %}
</ul>
</div><!-- inline-related-->

View file

@ -13,6 +13,7 @@
<th>Time</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@ -21,7 +22,8 @@
<td>{{ item.time|date:"D" }}</td>
<td>{{ item.time|date:"H:i" }} - {{ item.end_time|date:"H:i" }}</td>
<td>{{ item.name }}</td>
<td><a href="{% url "meetings_times_delete" meeting_id=meeting.number time=item.time|date:"Y:m:d:H:i" %}">Delete</a></td>
<td><a href="{% url "meetings_times_edit" meeting_id=meeting.number schedule_name=schedule.name time=item.time|date:"Y:m:d:H:i" %}">Edit</a></td>
<td><a href="{% url "meetings_times_delete" meeting_id=meeting.number schedule_name=schedule.name time=item.time|date:"Y:m:d:H:i" %}">Delete</a></td>
</tr>
{% endfor %}
</tbody>

View file

@ -0,0 +1,18 @@
{% extends "meetings/base_rooms_times.html" %}
{% block subsection %}
<div class="module interim-container">
<h2>Meeting - {{ meeting }}</h2>
<p><h3>TimeSlot:</h3></p>
<form id="times-edit-form" enctype="multipart/form-data" action="." method="post">{% csrf_token %}
<table class="full-width amstable">
{{ form.as_table }}
</table>
{% include "includes/buttons_save_cancel.html" %}
</form>
</div> <!-- module -->
{% endblock %}

View file

@ -34,8 +34,16 @@
<div class="button-group">
<ul>
<li><button onclick="window.location='{% url "meetings_edit_meeting" meeting_id=meeting.number %}'">Edit</button></li>
<li><button onclick="window.location='{% url "meetings_rooms" meeting_id=meeting.number %}'">Rooms and Times</button></li>
<li><button onclick="window.location='{% url "meetings_select_group" meeting_id=meeting.number %}'">Schedule WG Sessions</button></li>
<li><button onclick="window.location='{% url "meetings_notifications" meeting_id=meeting.number %}'">Notifications</button></li>
<li><button onclick="window.location='{% url "meetings_blue_sheet" meeting_id=meeting.number %}'">Blue Sheets</button></li>
<li><form id="schedule-selector">
<select name="forma" onchange="location = this.options[this.selectedIndex].value;">
{% for sched in meeting.schedule_set.all %}
<option value="">Select a schedule...</option>
<option value="{{ sched.name }}">{{ sched.name }}</option>
{% endfor %}
</select>
</form></li>
</ul>
</div> <!-- button-group -->
@ -44,7 +52,8 @@
{% endblock %}
<li><button onclick="window.location='{% url "meetings_rooms" meeting_id=meeting.number %}'">Rooms and Times</button></li>
<li><button onclick="window.location='{% url "meetings_select_group" meeting_id=meeting.number %}'">Schedule WG Sessions</button></li>

View file

@ -490,6 +490,10 @@ tr.break td {
border-top: 2px solid black;
}
#schedule-selector {
display: inline;
}
/* ==========================================================================
Proceedings Tool
========================================================================== */

View file

@ -59,7 +59,7 @@ $(function() {
'</ul>'
$('.inline-group').each(function(i) {
//prefix is in the name of the input fields before the "-"
var prefix = $("input[type='hidden']", this).attr("name").split("-")[0]
var prefix = $("input[type='hidden'][name!='csrfmiddlewaretoken']", this).attr("name").split("-")[0]
$(this).append(html_template.replace("{{prefix}}", prefix))
})
})