889 lines
35 KiB
Python
889 lines
35 KiB
Python
# Copyright The IETF Trust 2007-2019, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import datetime
|
|
import os
|
|
import time
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.db.models import Max
|
|
from django.forms.models import inlineformset_factory
|
|
from django.shortcuts import render, get_object_or_404, redirect
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
from ietf.ietfauth.utils import role_required
|
|
from ietf.utils.mail import send_mail
|
|
from ietf.meeting.forms import duration_string
|
|
from ietf.meeting.helpers import get_meeting, make_materials_directories, populate_important_dates
|
|
from ietf.meeting.models import Meeting, Session, Room, TimeSlot, SchedTimeSessAssignment, Schedule, SchedulingEvent
|
|
from ietf.meeting.utils import add_event_info_to_session_qs
|
|
from ietf.meeting.utils import only_sessions_that_can_meet
|
|
from ietf.name.models import SessionStatusName
|
|
from ietf.group.models import Group, GroupEvent
|
|
from ietf.person.models import Person
|
|
from ietf.secr.meetings.blue_sheets import create_blue_sheets
|
|
from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm, MeetingSelectForm,
|
|
MeetingRoomForm, MiscSessionForm, TimeSlotForm, RegularSessionEditForm,
|
|
UploadBlueSheetForm )
|
|
from ietf.secr.proceedings.utils import handle_upload_file
|
|
from ietf.secr.sreq.views import get_initial_session
|
|
from ietf.secr.utils.meeting import get_session, get_timeslot
|
|
from ietf.mailtrigger.utils import gather_address_lists
|
|
|
|
|
|
# prep for agenda changes
|
|
# --------------------------------------------------
|
|
# Helper Functions
|
|
# --------------------------------------------------
|
|
|
|
def build_timeslots(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.
|
|
If room is passed pre-create timeslots for the new room. Call this after saving new rooms
|
|
or adding a room.
|
|
'''
|
|
slots = meeting.timeslot_set.filter(type='regular')
|
|
|
|
# Don't do anything if the room is not capable of handling sessions
|
|
if room and not room.session_types.filter(slug='regular'):
|
|
return
|
|
|
|
if room:
|
|
rooms = [room]
|
|
else:
|
|
rooms = meeting.room_set.filter(session_types__slug='regular')
|
|
if not slots or room:
|
|
# if we are just building timeslots for a new room, the room argument was passed,
|
|
# then we need to use current meeting times as a template, not the last meeting times
|
|
if room:
|
|
source_meeting = meeting
|
|
else:
|
|
source_meeting = get_last_meeting(meeting)
|
|
|
|
delta = meeting.date - source_meeting.date
|
|
timeslots = []
|
|
time_seen = set()
|
|
for t in source_meeting.timeslot_set.filter(type='regular'):
|
|
if not t.time in time_seen:
|
|
time_seen.add(t.time)
|
|
timeslots.append(t)
|
|
for t in timeslots:
|
|
new_time = t.time + delta
|
|
for room in rooms:
|
|
TimeSlot.objects.create(type_id='regular',
|
|
meeting=meeting,
|
|
name=t.name,
|
|
time=new_time,
|
|
location=room,
|
|
duration=t.duration)
|
|
|
|
def check_misc_sessions(meeting,schedule):
|
|
'''
|
|
Ensure misc session timeslots exist and have appropriate SchedTimeSessAssignment objects
|
|
for the specified schedule.
|
|
'''
|
|
slots = TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other','plenary','lead','offagenda'))
|
|
plenary = slots.filter(type='plenary').first()
|
|
if plenary:
|
|
assignments = plenary.sessionassignments.all()
|
|
if not assignments.filter(schedule=schedule):
|
|
source = assignments.first().schedule
|
|
copy_assignments(slots,source,schedule)
|
|
|
|
def copy_assignments(slots,source,target):
|
|
'''
|
|
Copy SchedTimeSessAssignment objects from source schedule to target schedule. Slots is
|
|
a queryset of slots
|
|
'''
|
|
for ss in SchedTimeSessAssignment.objects.filter(schedule=source,timeslot__in=slots):
|
|
SchedTimeSessAssignment.objects.create(schedule=target,session=ss.session,timeslot=ss.timeslot)
|
|
|
|
def get_last_meeting(meeting):
|
|
last_number = int(meeting.number) - 1
|
|
try:
|
|
return Meeting.objects.get(number=last_number)
|
|
except Meeting.DoesNotExist:
|
|
return None
|
|
|
|
def is_combined(session,meeting,schedule=None):
|
|
'''
|
|
Check to see if this session is using two combined timeslots
|
|
'''
|
|
if schedule == None:
|
|
schedule = meeting.schedule
|
|
if session.timeslotassignments.filter(schedule=schedule).count() > 1:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def send_notifications(meeting, groups, person):
|
|
'''
|
|
Send session scheduled email notifications for each group in groups. Person is the
|
|
user who initiated this action, request.uesr.get_profile().
|
|
'''
|
|
now = datetime.datetime.now()
|
|
for group in groups:
|
|
sessions = group.session_set.filter(meeting=meeting)
|
|
addrs = gather_address_lists('session_scheduled',group=group,session=sessions[0])
|
|
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'
|
|
|
|
# easier to populate template from timeslot perspective. assuming one-to-one timeslot-session
|
|
items = [ {'session':s, 'timeslot':get_timeslot(s)} for s in sessions ]
|
|
items.sort(key=lambda d: d['timeslot'].time)
|
|
for i,d in enumerate(items):
|
|
s = d['session']
|
|
t = d['timeslot']
|
|
dur = s.requested_duration.seconds/60
|
|
items[i]['duration'] = "%d:%02d" % (dur//60, dur%60)
|
|
items[i]['period'] = '%s-%s' % (t.time.strftime('%H%M'),(t.time + t.duration).strftime('%H%M'))
|
|
|
|
# send email
|
|
first_event = SchedulingEvent.objects.filter(session=sessions[0]).select_related('by').order_by('time', 'id').first()
|
|
requested_by = None
|
|
if first_event and first_event.status_id in ['appw', 'schedw']:
|
|
requested_by = first_event.by
|
|
|
|
context = {
|
|
'items': items,
|
|
'meeting': meeting,
|
|
'baseurl': settings.IDTRACKER_BASE_URL,
|
|
}
|
|
context['to_name'] = str(requested_by) or "Requester"
|
|
context['agenda_note'] = sessions[0].agenda_note
|
|
context['session'] = get_initial_session(sessions)
|
|
context['group'] = group
|
|
context['login'] = requested_by
|
|
|
|
send_mail(None,
|
|
addrs.to,
|
|
from_email,
|
|
subject,
|
|
template,
|
|
context,
|
|
cc=addrs.cc)
|
|
|
|
# create sent_notification event
|
|
GroupEvent.objects.create(group=group,time=now,type='sent_notification',
|
|
by=person,desc='sent scheduled notification for %s' % meeting)
|
|
|
|
|
|
# -------------------------------------------------
|
|
# AJAX Functions
|
|
# -------------------------------------------------
|
|
# def ajax_get_times(request, meeting_id, day):
|
|
# '''
|
|
# Ajax function to get timeslot times for a given day.
|
|
# returns JSON format response: [{id:start_time, value:start_time-end_time},...]
|
|
# '''
|
|
# # TODO strip duplicates if there are any
|
|
# from ietf.utils import log
|
|
# log.unreachable("2017-07-08")
|
|
# results=[]
|
|
# room = Room.objects.filter(meeting__number=meeting_id)[0]
|
|
# slots = TimeSlot.objects.filter(meeting__number=meeting_id,time__week_day=day,location=room).order_by('time')
|
|
# for slot in slots:
|
|
# d = {'id': slot.time.strftime('%H%M'), 'value': '%s-%s' % (slot.time.strftime('%H%M'), slot.end_time().strftime('%H%M'))}
|
|
# results.append(d)
|
|
#
|
|
# return HttpResponse(json.dumps(results), content_type='application/javascript')
|
|
|
|
# --------------------------------------------------
|
|
# STANDARD VIEW FUNCTIONS
|
|
# --------------------------------------------------
|
|
@role_required('Secretariat')
|
|
def add(request):
|
|
'''
|
|
Add a new IETF Meeting. Creates Meeting and Proceeding objects.
|
|
|
|
**Templates:**
|
|
|
|
* ``meetings/add.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* proceedingform
|
|
|
|
'''
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('ietf.secr.meetings.views.main')
|
|
|
|
form = MeetingModelForm(request.POST)
|
|
if form.is_valid():
|
|
meeting = form.save()
|
|
|
|
schedule = Schedule.objects.create(meeting = meeting,
|
|
name = 'Empty-Schedule',
|
|
owner = Person.objects.get(name='(System)'),
|
|
visible = True,
|
|
public = True)
|
|
meeting.schedule = schedule
|
|
|
|
# we want to carry session request lock status over from previous meeting
|
|
previous_meeting = get_meeting( int(meeting.number) - 1 )
|
|
meeting.session_request_lock_message = previous_meeting.session_request_lock_message
|
|
meeting.save()
|
|
|
|
populate_important_dates(meeting)
|
|
|
|
# Create Physical new meeting directory and subdirectories
|
|
make_materials_directories(meeting)
|
|
|
|
messages.success(request, 'The Meeting was created successfully!')
|
|
return redirect('ietf.secr.meetings.views.main')
|
|
else:
|
|
# display initial forms
|
|
max_number = Meeting.objects.filter(type='ietf').aggregate(Max('number'))['number__max']
|
|
form = MeetingModelForm(initial={'number':int(max_number) + 1})
|
|
|
|
return render(request, 'meetings/add.html', {
|
|
'form': form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def blue_sheet(request, meeting_id):
|
|
'''
|
|
Blue Sheet view. The user can generate blue sheets or upload scanned bluesheets
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
url = settings.SECR_BLUE_SHEET_URL
|
|
blank_sheets_path = settings.SECR_BLUE_SHEET_PATH
|
|
try:
|
|
last_run = time.ctime(os.stat(blank_sheets_path).st_ctime)
|
|
except OSError:
|
|
last_run = None
|
|
uploaded_sheets_path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'bluesheets')
|
|
uploaded_files = sorted(os.listdir(uploaded_sheets_path))
|
|
|
|
if request.method == 'POST':
|
|
form = UploadBlueSheetForm(request.POST,request.FILES)
|
|
if form.is_valid():
|
|
file = request.FILES['file']
|
|
save_error = handle_upload_file(file,file.name,meeting,'bluesheets')
|
|
if save_error:
|
|
form.add_error(None, save_error)
|
|
else:
|
|
messages.success(request, 'File Uploaded')
|
|
return redirect('ietf.secr.meetings.views.blue_sheet', meeting_id=meeting.number)
|
|
else:
|
|
form = UploadBlueSheetForm()
|
|
|
|
return render(request, 'meetings/blue_sheet.html', {
|
|
'meeting': meeting,
|
|
'url': url,
|
|
'form': form,
|
|
'last_run': last_run,
|
|
'uploaded_files': uploaded_files},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def blue_sheet_generate(request, meeting_id):
|
|
'''
|
|
Generate bluesheets
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
|
|
if request.method == "POST":
|
|
# TODO: Why aren't 'ag' in here as well?
|
|
groups = Group.objects.filter(
|
|
type__in=['wg','rg'],
|
|
session__timeslotassignments__schedule=meeting.schedule).order_by('acronym')
|
|
create_blue_sheets(meeting, groups)
|
|
|
|
messages.success(request, 'Blue Sheets generated')
|
|
return redirect('ietf.secr.meetings.views.blue_sheet', meeting_id=meeting.number)
|
|
|
|
@role_required('Secretariat')
|
|
def blue_sheet_redirect(request):
|
|
'''
|
|
This is the generic blue sheet URL. It gets the next IETF meeting and redirects
|
|
to the meeting specific URL.
|
|
'''
|
|
today = datetime.date.today()
|
|
qs = Meeting.objects.filter(date__gt=today,type='ietf').order_by('date')
|
|
if qs:
|
|
meeting = qs[0]
|
|
else:
|
|
meeting = Meeting.objects.filter(type='ietf').order_by('-date')[0]
|
|
return redirect('ietf.secr.meetings.views.blue_sheet', meeting_id=meeting.number)
|
|
|
|
@role_required('Secretariat')
|
|
def edit_meeting(request, meeting_id):
|
|
'''
|
|
Edit Meeting information.
|
|
|
|
**Templates:**
|
|
|
|
* ``meetings/meeting_edit.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* meeting, form
|
|
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit','')
|
|
if button_text == 'Cancel':
|
|
return redirect('ietf.secr.meetings.views.view', meeting_id=meeting_id)
|
|
|
|
form = MeetingModelForm(request.POST, instance=meeting)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request,'The meeting entry was changed successfully')
|
|
return redirect('ietf.secr.meetings.views.view', meeting_id=meeting_id)
|
|
|
|
else:
|
|
form = MeetingModelForm(instance=meeting)
|
|
|
|
return render(request, 'meetings/edit_meeting.html', {
|
|
'meeting': meeting,
|
|
'form' : form, },
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def main(request):
|
|
'''
|
|
In this view the user can choose a meeting to manage or elect to create a new meeting.
|
|
'''
|
|
meetings = Meeting.objects.filter(type='ietf').order_by('-date')
|
|
|
|
if request.method == 'POST':
|
|
return redirect('ietf.secr.meetings.views.view', meeting_id=request.POST['meeting'])
|
|
|
|
choices = [ (str(x.number),str(x.number)) for x in meetings ]
|
|
form = MeetingSelectForm(choices=choices)
|
|
|
|
return render(request, 'meetings/main.html', {
|
|
'form': form,
|
|
'meetings': meetings},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def misc_sessions(request, meeting_id, schedule_name):
|
|
'''
|
|
Display and add misc session time slots, e.g. 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)
|
|
|
|
check_misc_sessions(meeting,schedule)
|
|
|
|
misc_session_types = ['break','reg','other','plenary','lead']
|
|
assignments = schedule.assignments.filter(timeslot__type__in=misc_session_types)
|
|
assignments = assignments.order_by('-timeslot__type__name','timeslot__time')
|
|
|
|
if request.method == 'POST':
|
|
form = MiscSessionForm(request.POST, meeting=meeting)
|
|
if form.is_valid():
|
|
time = get_timeslot_time(form, meeting)
|
|
name = form.cleaned_data['name']
|
|
short = form.cleaned_data['short']
|
|
type = form.cleaned_data['type']
|
|
group = form.cleaned_data['group']
|
|
duration = form.cleaned_data['duration']
|
|
location = form.cleaned_data['location']
|
|
|
|
# create TimeSlot object
|
|
timeslot = TimeSlot.objects.create(type=type,
|
|
meeting=meeting,
|
|
name=name,
|
|
time=time,
|
|
duration=duration,
|
|
location=location,
|
|
show_location=form.cleaned_data['show_location'])
|
|
|
|
if timeslot.type.slug not in ('other','plenary','lead'):
|
|
group = Group.objects.get(acronym='secretariat')
|
|
|
|
# create associated Session object
|
|
session = Session.objects.create(meeting=meeting,
|
|
name=name,
|
|
short=short,
|
|
group=group,
|
|
type=type)
|
|
|
|
SchedulingEvent.objects.create(
|
|
session=session,
|
|
status=SessionStatusName.objects.get(slug='sched'),
|
|
by=request.user.person,
|
|
)
|
|
|
|
# create association
|
|
SchedTimeSessAssignment.objects.create(timeslot=timeslot,
|
|
session=session,
|
|
schedule=schedule)
|
|
|
|
messages.success(request, 'Misc. sessions updated successfully')
|
|
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
else:
|
|
form = MiscSessionForm(initial={'show_location':True}, meeting=meeting)
|
|
|
|
no_room = TimeSlot.objects.filter(meeting=meeting,type='other',location__isnull=True)
|
|
if no_room:
|
|
messages.warning(request, 'There are misc. session time slots which do not have a room assigned')
|
|
|
|
session_statuses = {
|
|
e.session_id: e.status_id
|
|
for e in SchedulingEvent.objects.filter(session__in=[a.session_id for a in assignments]).order_by('time', 'id')
|
|
}
|
|
|
|
for a in assignments:
|
|
a.current_session_status = session_statuses.get(a.session_id)
|
|
|
|
return render(request, 'meetings/misc_sessions.html', {
|
|
'assignments': assignments,
|
|
'form': form,
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'selected': 'misc-sessions'},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def misc_session_cancel(request, meeting_id, schedule_name, slot_id):
|
|
'''
|
|
This function cancels the misc session TimeSlot. Check for uploaded
|
|
material first. SchedTimeSessAssignment objects get cancelled as well.
|
|
'''
|
|
slot = get_object_or_404(TimeSlot, id=slot_id)
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
|
|
|
if request.method == 'POST' and request.POST['post'] == 'yes':
|
|
for session in Session.objects.filter(timeslotassignments__schedule=schedule, timeslotassignments__timeslot=slot):
|
|
SchedulingEvent.objects.create(
|
|
session=session,
|
|
status=SessionStatusName.objects.get(slug='canceled'),
|
|
by=request.user.person,
|
|
)
|
|
|
|
messages.success(request, 'The session was cancelled successfully')
|
|
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
|
|
return render(request, 'confirm_cancel.html', {'object': slot})
|
|
|
|
@role_required('Secretariat')
|
|
def misc_session_delete(request, meeting_id, schedule_name, slot_id):
|
|
'''
|
|
This function deletes the misc session TimeSlot. Check for uploaded
|
|
material first. SchedTimeSessAssignment objects get deleted as well.
|
|
'''
|
|
slot = get_object_or_404(TimeSlot, id=slot_id)
|
|
|
|
if request.method == 'POST' and request.POST['post'] == 'yes':
|
|
assignments = slot.sessionassignments.all()
|
|
session_objects = [ x.session for x in assignments ]
|
|
|
|
for session in session_objects:
|
|
if 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('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
|
|
# delete high order assignments, then sessions and slots
|
|
assignments.delete()
|
|
Session.objects.filter(pk__in=[ x.pk for x in session_objects ]).delete()
|
|
slot.delete()
|
|
|
|
messages.success(request, 'The entry was deleted successfully')
|
|
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
|
|
return render(request, 'confirm_delete.html', {'object': slot})
|
|
|
|
@role_required('Secretariat')
|
|
def misc_session_edit(request, meeting_id, schedule_name, slot_id):
|
|
'''
|
|
Allows the user to assign a location to this misc session timeslot
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
slot = get_object_or_404(TimeSlot, id=slot_id)
|
|
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
|
session = get_session(slot,schedule=schedule)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Back':
|
|
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
|
|
form = MiscSessionForm(request.POST,meeting=meeting,session=session)
|
|
if form.is_valid():
|
|
location = form.cleaned_data['location']
|
|
group = form.cleaned_data['group']
|
|
name = form.cleaned_data['name']
|
|
short = form.cleaned_data['short']
|
|
duration = form.cleaned_data['duration']
|
|
slot_type = form.cleaned_data['type']
|
|
show_location = form.cleaned_data['show_location']
|
|
time = get_timeslot_time(form, meeting)
|
|
slot.location = location
|
|
slot.name = name
|
|
slot.time = time
|
|
slot.duration = duration
|
|
slot.type = slot_type
|
|
slot.show_location = show_location
|
|
slot.save()
|
|
# save group to session object
|
|
session.group = group
|
|
session.name = name
|
|
session.short = short
|
|
session.save()
|
|
|
|
messages.success(request, 'Location saved')
|
|
return redirect('ietf.secr.meetings.views.misc_sessions', 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
|
|
delta = slot.time.date() - meeting.date
|
|
initial = {'location':slot.location,
|
|
'group':session.group,
|
|
'name':session.name,
|
|
'short':session.short,
|
|
'day':delta.days,
|
|
'time':slot.time.strftime('%H:%M'),
|
|
'duration':duration_string(slot.duration),
|
|
'show_location':slot.show_location,
|
|
'type':slot.type}
|
|
form = MiscSessionForm(initial=initial, meeting=meeting, session=session)
|
|
|
|
return render(request, 'meetings/misc_session_edit.html', {
|
|
'meeting': meeting,
|
|
'form': form,
|
|
'schedule': schedule,
|
|
'slot': slot},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
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.schedule.assignments.filter(timeslot__type='regular'):
|
|
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
|
|
sessions = add_event_info_to_session_qs(Session.objects.filter(timeslotassignments__schedule=meeting.schedule_id)).filter(current_status__in=["schedw", "appr"])
|
|
for session in sessions:
|
|
SchedulingEvent.objects.create(
|
|
session=session,
|
|
status=SessionStatusName.objects.get(slug='sched'),
|
|
by=request.user.person,
|
|
)
|
|
send_notifications(meeting,groups,request.user.person)
|
|
|
|
messages.success(request, "Notifications Sent")
|
|
return redirect('ietf.secr.meetings.views.view', meeting_id=meeting.number)
|
|
|
|
return render(request, 'meetings/notifications.html', {
|
|
'meeting': meeting,
|
|
'groups': sorted(groups, key=lambda a: a.acronym),
|
|
'last_notice': last_notice },
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
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
|
|
RoomFormset = inlineformset_factory(Meeting, Room, form=MeetingRoomForm, formset=BaseMeetingRoomFormSet, can_delete=True, extra=extra)
|
|
|
|
if request.method == 'POST':
|
|
button_text = request.POST.get('submit', '')
|
|
if button_text == 'Cancel':
|
|
return redirect('ietf.secr.meetings.views.main', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
formset = RoomFormset(request.POST, instance=meeting, prefix='room')
|
|
if formset.is_valid():
|
|
formset.save()
|
|
|
|
# if we are creating rooms for the first time create full set of timeslots
|
|
if first_time:
|
|
build_timeslots(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)
|
|
|
|
messages.success(request, 'Meeting Rooms changed successfully')
|
|
return redirect('ietf.secr.meetings.views.rooms', meeting_id=meeting_id, schedule_name=schedule_name)
|
|
else:
|
|
formset = RoomFormset(instance=meeting, prefix='room')
|
|
|
|
return render(request, 'meetings/rooms.html', {
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'formset': formset,
|
|
'selected': 'rooms'}
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def regular_sessions(request, meeting_id, schedule_name):
|
|
'''
|
|
Display and edit Session 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)
|
|
|
|
sessions = add_event_info_to_session_qs(
|
|
only_sessions_that_can_meet(schedule.meeting.session_set)
|
|
).order_by('group__acronym')
|
|
|
|
if request.method == 'POST':
|
|
if 'cancel' in request.POST:
|
|
pk = request.POST.get('pk')
|
|
session = get_object_or_404(sessions, pk=pk)
|
|
SchedulingEvent.objects.create(
|
|
session=session,
|
|
status=SessionStatusName.objects.get(slug='canceled'),
|
|
by=request.user.person,
|
|
)
|
|
messages.success(request, 'Session cancelled')
|
|
|
|
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
|
|
|
for s in sessions:
|
|
s.current_status_name = status_names.get(s.current_status, s.current_status)
|
|
|
|
return render(request, 'meetings/sessions.html', {
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'sessions': sessions,
|
|
'formset': None,
|
|
'selected': 'regular-sessions',},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
def regular_session_edit(request, meeting_id, schedule_name, session_id):
|
|
'''
|
|
Edit session details
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
|
session = get_object_or_404(Session, id=session_id)
|
|
assignment = SchedTimeSessAssignment.objects.filter(schedule=schedule, session=session).first()
|
|
|
|
if request.method == 'POST':
|
|
form = RegularSessionEditForm(request.POST, instance=session)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request, 'Session saved')
|
|
return redirect('ietf.secr.meetings.views.regular_sessions', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
else:
|
|
form = RegularSessionEditForm(instance=session)
|
|
|
|
current_status_name = None
|
|
latest_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
|
if latest_event:
|
|
current_status_name = latest_event.status.name
|
|
|
|
return render(request, 'meetings/regular_session_edit.html', {
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'session': session,
|
|
'timeslot': assignment.timeslot if assignment else None,
|
|
'current_status_name': current_status_name,
|
|
'form': form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
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,
|
|
rather it displays all the unique times.
|
|
The first time this view is called for a meeting it creates a form with times
|
|
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 = []
|
|
timeslots = []
|
|
time_seen = set()
|
|
for t in meeting.timeslot_set.filter(type='regular'):
|
|
if not t.time in time_seen:
|
|
time_seen.add(t.time)
|
|
timeslots.append(t)
|
|
for t in timeslots:
|
|
slots.append({'name':t.name,
|
|
'time':t.time,
|
|
'end_time':t.end_time()})
|
|
times = sorted(slots, key=lambda a: a['time'])
|
|
|
|
if request.method == 'POST':
|
|
form = TimeSlotForm(request.POST, meeting=meeting)
|
|
if form.is_valid():
|
|
time = get_timeslot_time(form, meeting)
|
|
duration = form.cleaned_data['duration']
|
|
name = form.cleaned_data['name']
|
|
|
|
# don't allow creation of timeslots with same start time as existing timeslots
|
|
# assert False, (new_time, time_seen)
|
|
if time in time_seen:
|
|
messages.error(request, 'There is already a timeslot for %s. To change you must delete the old one first.' % time.strftime('%a %H:%M'))
|
|
return redirect('ietf.secr.meetings.views.times', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
for room in meeting.room_set.all():
|
|
TimeSlot.objects.create(type_id='regular',
|
|
meeting=meeting,
|
|
name=name,
|
|
time=time,
|
|
location=room,
|
|
duration=duration)
|
|
|
|
messages.success(request, 'Timeslots created')
|
|
return redirect('ietf.secr.meetings.views.times', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
else:
|
|
form = TimeSlotForm(meeting=meeting)
|
|
|
|
return render(request, 'meetings/times.html', {
|
|
'form': form,
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'times': times,
|
|
'selected': 'times'},
|
|
)
|
|
|
|
def get_timeslot_time(form, meeting):
|
|
'''Returns datetime calculated from day and time form fields'''
|
|
time = form.cleaned_data['time']
|
|
day = form.cleaned_data['day']
|
|
|
|
date = meeting.date + datetime.timedelta(days=int(day))
|
|
return datetime.datetime(date.year,date.month,date.day,time.hour,time.minute)
|
|
|
|
@role_required('Secretariat')
|
|
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('ietf.secr.meetings.views.times', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
form = TimeSlotForm(request.POST, meeting=meeting)
|
|
if form.is_valid():
|
|
day = form.cleaned_data['day']
|
|
time = get_timeslot_time(form, meeting)
|
|
duration = form.cleaned_data['duration']
|
|
name = form.cleaned_data['name']
|
|
|
|
for timeslot in timeslots:
|
|
timeslot.time = time
|
|
timeslot.duration = duration
|
|
timeslot.name = name
|
|
timeslot.save()
|
|
|
|
messages.success(request, 'TimeSlot saved')
|
|
return redirect('ietf.secr.meetings.views.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, meeting=meeting)
|
|
|
|
return render(request, 'meetings/times_edit.html', {
|
|
'meeting': meeting,
|
|
'schedule': schedule,
|
|
'form': form},
|
|
)
|
|
|
|
@role_required('Secretariat')
|
|
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)
|
|
|
|
parts = [ int(x) for x in time.split(':') ]
|
|
dtime = datetime.datetime(*parts)
|
|
status = SessionStatusName.objects.get(slug='schedw')
|
|
|
|
if request.method == 'POST' and request.POST['post'] == 'yes':
|
|
for slot in TimeSlot.objects.filter(meeting=meeting,time=dtime):
|
|
for assignment in slot.sessionassignments.all():
|
|
if assignment.session:
|
|
session = assignment.session
|
|
latest_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
|
if not latest_event or latest_event.status_id != 'schedw':
|
|
SchedulingEvent.objects.create(
|
|
session=session,
|
|
status=status,
|
|
by=request.user.person,
|
|
)
|
|
assignment.delete()
|
|
slot.delete()
|
|
messages.success(request, 'The entry was deleted successfully')
|
|
return redirect('ietf.secr.meetings.views.times', meeting_id=meeting_id,schedule_name=schedule_name)
|
|
|
|
return render(request, 'confirm_delete.html', {
|
|
'object': '%s timeslots' % dtime.strftime("%A %H:%M"),
|
|
'extra': 'Any sessions assigned to this timeslot will be unscheduled'
|
|
})
|
|
|
|
@role_required('Secretariat')
|
|
def view(request, meeting_id):
|
|
'''
|
|
View Meeting information.
|
|
|
|
**Templates:**
|
|
|
|
* ``meetings/view.html``
|
|
|
|
**Template Variables:**
|
|
|
|
* meeting , proceeding
|
|
|
|
'''
|
|
meeting = get_object_or_404(Meeting, number=meeting_id)
|
|
|
|
return render(request, 'meetings/view.html', {
|
|
'meeting': meeting},
|
|
)
|