Added functionality which allows the secretariat to manage more meeting types, including leadership meetings.
Backfilled those types of meetings from IETF91 and IETF92.
Addressed several facelift issues in the meeting application.
- Legacy-Id: 9606
Note: SVN reference [9603] has been migrated to Git commit a85424ad23
209 lines
8.9 KiB
Python
209 lines
8.9 KiB
Python
import datetime
|
|
import re
|
|
|
|
from django import forms
|
|
|
|
from ietf.group.models import Group
|
|
from ietf.meeting.models import Meeting, Room, TimeSlot, Session, ScheduledSession
|
|
from ietf.meeting.timedeltafield import TimedeltaFormField, TimedeltaWidget
|
|
from ietf.name.models import TimeSlotTypeName
|
|
|
|
|
|
DAYS_CHOICES = ((-1,'Saturday'),
|
|
(0,'Sunday'),
|
|
(1,'Monday'),
|
|
(2,'Tuesday'),
|
|
(3,'Wednesday'),
|
|
(4,'Thursday'),
|
|
(5,'Friday'))
|
|
|
|
# using Django week_day lookup values (Sunday=1)
|
|
SESSION_DAYS = ((2,'Monday'),
|
|
(3,'Tuesday'),
|
|
(4,'Wednesday'),
|
|
(5,'Thursday'),
|
|
(6,'Friday'))
|
|
|
|
#----------------------------------------------------------
|
|
# Helper Functions
|
|
#----------------------------------------------------------
|
|
def get_next_slot(slot):
|
|
'''Takes a TimeSlot object and returns the next TimeSlot same day and same room, None if there
|
|
aren't any. You must check availability of the slot as we sometimes need to get the next
|
|
slot whether it's available or not. For use with combine option.
|
|
'''
|
|
same_day_slots = TimeSlot.objects.filter(meeting=slot.meeting,location=slot.location,time__day=slot.time.day).order_by('time')
|
|
try:
|
|
i = list(same_day_slots).index(slot)
|
|
return same_day_slots[i+1]
|
|
except IndexError:
|
|
return None
|
|
|
|
def get_times(meeting,day):
|
|
'''
|
|
Takes a Meeting object and an integer representing the week day (sunday=1).
|
|
Returns a list of tuples for use in a ChoiceField. The value is start_time,
|
|
The label is [start_time]-[end_time].
|
|
'''
|
|
# pick a random room
|
|
rooms = Room.objects.filter(meeting=meeting)
|
|
if rooms:
|
|
room = rooms[0]
|
|
else:
|
|
room = None
|
|
slots = TimeSlot.objects.filter(meeting=meeting,time__week_day=day,location=room).order_by('time')
|
|
choices = [ (t.time.strftime('%H%M'), '%s-%s' % (t.time.strftime('%H%M'), t.end_time().strftime('%H%M'))) for t in slots ]
|
|
return choices
|
|
|
|
#----------------------------------------------------------
|
|
# Base Classes
|
|
#----------------------------------------------------------
|
|
class BaseMeetingRoomFormSet(forms.models.BaseInlineFormSet):
|
|
def clean(self):
|
|
'''Check that any rooms marked for deletion are not in use'''
|
|
for form in self.deleted_forms:
|
|
room = form.cleaned_data['id']
|
|
scheduledsessions = ScheduledSession.objects.filter(timeslot__location=room,
|
|
session__isnull=False)
|
|
if scheduledsessions:
|
|
raise forms.ValidationError('Cannot delete meeting room %s. Already assigned to some session.' % room.name)
|
|
|
|
class TimeSlotModelChoiceField(forms.ModelChoiceField):
|
|
'''
|
|
Custom ModelChoiceField, changes the label to a more readable format
|
|
'''
|
|
def label_from_instance(self, obj):
|
|
|
|
return "%s %s - %s" % (obj.time.strftime('%a %H:%M'),obj.name,obj.location)
|
|
|
|
class TimeChoiceField(forms.ChoiceField):
|
|
'''
|
|
We are modifying the time choice field with javascript so the value submitted may not have
|
|
been in the initial select list. Just override valid_value validaion.
|
|
'''
|
|
def valid_value(self, value):
|
|
return True
|
|
|
|
#----------------------------------------------------------
|
|
# Forms
|
|
#----------------------------------------------------------
|
|
class MeetingModelForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Meeting
|
|
exclude = ('type', 'agenda', 'session_request_lock_message')
|
|
|
|
def clean_number(self):
|
|
number = self.cleaned_data['number']
|
|
if not number.isdigit():
|
|
raise forms.ValidationError('Meeting number must be an integer')
|
|
return number
|
|
|
|
def save(self, force_insert=False, force_update=False, commit=True):
|
|
meeting = super(MeetingModelForm, self).save(commit=False)
|
|
meeting.type_id = 'ietf'
|
|
if commit:
|
|
meeting.save()
|
|
return meeting
|
|
|
|
class MeetingRoomForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Room
|
|
fields = '__all__'
|
|
|
|
class NewSessionForm(forms.Form):
|
|
day = forms.ChoiceField(choices=SESSION_DAYS)
|
|
time = TimeChoiceField()
|
|
room = forms.ModelChoiceField(queryset=Room.objects.none)
|
|
session = forms.CharField(widget=forms.HiddenInput)
|
|
note = forms.CharField(max_length=255, required=False, label='Special Note from Scheduler')
|
|
combine = forms.BooleanField(required=False, label='Combine with next session')
|
|
|
|
# setup the timeslot options based on meeting passed in
|
|
def __init__(self,*args,**kwargs):
|
|
self.meeting = kwargs.pop('meeting')
|
|
super(NewSessionForm, self).__init__(*args,**kwargs)
|
|
|
|
# attach session object to the form so we can use it in the template
|
|
self.session_object = Session.objects.get(id=self.initial['session'])
|
|
self.fields['room'].queryset = Room.objects.filter(meeting=self.meeting)
|
|
self.fields['time'].choices = get_times(self.meeting,self.initial['day'])
|
|
|
|
def clean(self):
|
|
super(NewSessionForm, self).clean()
|
|
if any(self.errors):
|
|
return
|
|
cleaned_data = self.cleaned_data
|
|
time = cleaned_data['time']
|
|
day = cleaned_data['day']
|
|
room = cleaned_data['room']
|
|
if cleaned_data['combine']:
|
|
# calculate datetime object from inputs, get current slot, feed to get_next_slot()
|
|
day_obj = self.meeting.get_meeting_date(int(day)-1)
|
|
hour = datetime.time(int(time[:2]),int(time[2:]))
|
|
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:
|
|
raise forms.ValidationError('There is no next session to combine')
|
|
|
|
return cleaned_data
|
|
|
|
class NonSessionEditForm(forms.Form):
|
|
name = forms.CharField(help_text='Name that appears on the agenda')
|
|
short = forms.CharField(max_length=32,label='Short Name',help_text='Enter an abbreviated session name (used for material file names)')
|
|
location = forms.ModelChoiceField(queryset=Room.objects)
|
|
group = forms.ModelChoiceField(queryset=Group.objects.filter(acronym__in=('edu','ietf','iepg','tools','iesg','iab','iaoc')),
|
|
help_text='''Select a group to associate with this session. For example:<br>
|
|
Tutorials = Education,<br>
|
|
Code Sprint = Tools Team,<br>
|
|
Technical Plenary = IAB,<br>
|
|
Administrative Plenary = IAOC or IESG''',empty_label=None)
|
|
|
|
def __init__(self,*args,**kwargs):
|
|
meeting = kwargs.pop('meeting')
|
|
self.session = kwargs.pop('session')
|
|
super(NonSessionEditForm, self).__init__(*args,**kwargs)
|
|
self.fields['location'].queryset = Room.objects.filter(meeting=meeting)
|
|
|
|
def clean_group(self):
|
|
group = self.cleaned_data['group']
|
|
if self.session.group != group and self.session.materials.all():
|
|
raise forms.ValidationError("ERROR: can't change group after materials have been uploaded")
|
|
return group
|
|
|
|
class TimeSlotForm(forms.Form):
|
|
day = forms.ChoiceField(choices=DAYS_CHOICES)
|
|
time = forms.TimeField()
|
|
duration = TimedeltaFormField(widget=TimedeltaWidget(attrs={'inputs':['hours','minutes']}))
|
|
name = forms.CharField(help_text='Name that appears on the agenda')
|
|
|
|
class NonSessionForm(TimeSlotForm):
|
|
short = forms.CharField(max_length=32,label='Short Name',help_text='Enter an abbreviated session name (used for material file names)',required=False)
|
|
type = forms.ModelChoiceField(queryset=TimeSlotTypeName.objects.filter(used=True).exclude(slug__in=('session',)),empty_label=None)
|
|
group = forms.ModelChoiceField(queryset=Group.objects.filter(acronym__in=('edu','ietf','iepg','tools','iesg','iab','iaoc')),help_text='Required for Session types: other, plenary',required=False)
|
|
show_location = forms.BooleanField(required=False)
|
|
|
|
def clean(self):
|
|
super(NonSessionForm, self).clean()
|
|
if any(self.errors):
|
|
return
|
|
cleaned_data = self.cleaned_data
|
|
group = cleaned_data['group']
|
|
type = cleaned_data['type']
|
|
short = cleaned_data['short']
|
|
if type.slug in ('other','plenary') and not group:
|
|
raise forms.ValidationError('ERROR: a group selection is required')
|
|
if type.slug in ('other','plenary') and not short:
|
|
raise forms.ValidationError('ERROR: a short name is required')
|
|
|
|
return cleaned_data
|
|
|
|
class UploadBlueSheetForm(forms.Form):
|
|
file = forms.FileField(help_text='example: bluesheets-84-ancp-01.pdf')
|
|
|
|
def clean_file(self):
|
|
file = self.cleaned_data['file']
|
|
if not re.match(r'bluesheets-\d+',file.name):
|
|
raise forms.ValidationError('Incorrect filename format')
|
|
return file
|