Skip to content

Commit b85df63

Browse files
committed
use formset for interim sessions
- Legacy-Id: 10978
1 parent 2cdd073 commit b85df63

7 files changed

Lines changed: 183 additions & 75 deletions

File tree

ietf/meeting/forms.py

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -117,72 +117,62 @@ def label_from_instance(self, obj):
117117
class InterimRequestForm(forms.Form):
118118
group = GroupModelChoiceField(queryset = Group.objects.filter(type__in=('wg','rg'),state='active').order_by('acronym'))
119119
face_to_face = forms.BooleanField(required=False)
120-
multi_day = forms.BooleanField(required=False)
121-
series = forms.BooleanField(required=False)
122-
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Date', required=True)
123-
time = forms.TimeField()
124-
duration = DurationField()
125-
city = forms.CharField(max_length=255,required=False)
126-
country = forms.ChoiceField(choices=countries,required=False)
127-
timezone = forms.ChoiceField(choices=timezones)
128-
remote_instructions = forms.CharField(max_length=1024,required=False)
129-
agenda = forms.CharField(required=False,widget=forms.Textarea)
130-
agenda_note = forms.CharField(max_length=255,required=False)
120+
meeting_type = forms.ChoiceField(choices=(("single", "Single"), ("multi-day", "Multi-Day"), ('series','Series')), required=False, initial='single', widget=forms.RadioSelect)
131121

132122
def __init__(self, request, *args, **kwargs):
133123
super(InterimRequestForm, self).__init__(*args, **kwargs)
134124
self.user = request.user
135125
self.person = self.user.person
136126
self.fields["group"].widget.attrs["class"] = "select2-field"
137-
138127
self.set_group_options()
139128

129+
def set_group_options(self):
130+
'''Set group options based on user accessing the form'''
131+
132+
if has_role(self.user, "Secretariat"):
133+
return # don't reduce group options
134+
if has_role(self.user, "Area Director"):
135+
queryset = Group.objects.filter(type="wg", state="active").order_by('acronym')
136+
elif has_role(self.user, "IRTF Chair"):
137+
queryset = Group.objects.filter(type="rg", state="active").order_by('acronym')
138+
elif has_role(self.user, "WG Chair"):
139+
queryset = Group.objects.filter(type="wg", state="active", role__person=self.person, role__name="chair").distinct().order_by('acronym')
140+
141+
self.fields['group'].queryset = queryset
142+
143+
class InterimSessionForm(forms.Form):
144+
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Date', required=True)
145+
time = forms.TimeField()
146+
duration = DurationField()
147+
remote_instructions = forms.CharField(max_length=1024,required=False)
148+
agenda = forms.CharField(required=False,widget=forms.Textarea)
149+
agenda_note = forms.CharField(max_length=255,required=False)
150+
city = forms.CharField(max_length=255,required=False)
151+
country = forms.ChoiceField(choices=countries,required=False)
152+
timezone = forms.ChoiceField(choices=timezones)
153+
140154
def _save_agenda(self, text):
141155
pass
142156

143-
def save(self):
157+
def save(self, request, group, meeting):
158+
person = request.user.person
144159
agenda = self.cleaned_data.get('agenda')
145160
agenda_note = self.cleaned_data.get('agenda_note')
146161
date = self.cleaned_data.get('date')
147162
time = self.cleaned_data.get('time')
148163
duration = self.cleaned_data.get('duration')
149-
group = self.cleaned_data.get('group')
150-
city = self.cleaned_data.get('city')
151-
country = self.cleaned_data.get('country')
152-
timezone = self.cleaned_data.get('timezone')
153164
remote_instructions = self.cleaned_data.get('remote_instructions')
154-
sequence = Meeting.objects.filter(number__startswith='interim-%s-%s' % (date.year,group.acronym)).count() + 1
155-
number = 'interim-%s-%s-%s' % (date.year,group.acronym,sequence)
156-
meeting = Meeting.objects.create(number=number,type_id='interim',date=date,city=city,
157-
country=country,agenda_note=agenda_note,time_zone=timezone)
158-
schedule = Schedule.objects.create(meeting=meeting, owner=self.person, visible=True, public=True)
159-
meeting.agenda = schedule
160-
meeting.save()
165+
161166
slot = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=duration,
162167
time=datetime.datetime.combine(date, time))
163168
session = Session.objects.create(meeting=meeting,
164169
group=group,
165-
requested_by=self.person,
170+
requested_by=person,
166171
status_id='apprw',
167172
type_id='session',
168-
remote_instructions=remote_instructions)
169-
SchedTimeSessAssignment.objects.create(timeslot=slot, session=session, schedule=schedule)
173+
remote_instructions=remote_instructions,
174+
agenda_note=agenda_note,)
175+
SchedTimeSessAssignment.objects.create(timeslot=slot, session=session, schedule=meeting.agenda)
170176

171177
if agenda:
172178
self._save_agenda(agenda)
173-
174-
return meeting
175-
176-
def set_group_options(self):
177-
'''Set group options based on user accessing the form'''
178-
179-
if has_role(self.user, "Secretariat"):
180-
return # don't reduce group options
181-
if has_role(self.user, "Area Director"):
182-
queryset = Group.objects.filter(type="wg", state="active").order_by('acronym')
183-
elif has_role(self.user, "IRTF Chair"):
184-
queryset = Group.objects.filter(type="rg", state="active").order_by('acronym')
185-
elif has_role(self.user, "WG Chair"):
186-
queryset = Group.objects.filter(type="wg", state="active", role__person=self.person, role__name="chair").distinct().order_by('acronym')
187-
188-
self.fields['group'].queryset = queryset

ietf/meeting/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,6 @@ def session_constraint_expire(request,session):
285285
if key is not None and cache.has_key(key):
286286
cache.delete(key)
287287

288-
288+
def get_next_interim_number(group,date):
289+
sequence = Meeting.objects.filter(number__startswith='interim-%s-%s' % (date.year,group.acronym)).count() + 1
290+
return 'interim-%s-%s-%s' % (date.year,group.acronym,sequence)

ietf/meeting/tests_views.py

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,13 @@ def test_interim_request_options(self):
380380
self.assertEqual(Group.objects.filter(type__in=('wg','rg'),state='active').count(),
381381
len(q("#id_group option")) -1 ) # -1 for options placeholder
382382

383-
def test_interim_request_submit(self):
383+
def test_interim_request_single(self):
384384
make_meeting_test_data()
385+
group = Group.objects.get(acronym='mars')
385386
date = datetime.date.today() + datetime.timedelta(days=30)
386387
time = datetime.datetime.now().time().replace(microsecond=0,second=0)
387388
dt = datetime.datetime.combine(date, time)
388389
duration = datetime.timedelta(hours=3)
389-
group = Group.objects.get(acronym='mars')
390390
city = 'San Francisco'
391391
country = 'US'
392392
timezone = 'US/Pacific'
@@ -395,15 +395,18 @@ def test_interim_request_submit(self):
395395
agenda_note = 'On second level'
396396
self.client.login(username="secretary", password="secretary+password")
397397
data = {'group':group.pk,
398-
'date':date.strftime("%Y-%m-%d"),
399-
'time':time.strftime('%H:%M'),
400-
'duration':'03:00:00',
401-
'city':city,
402-
'country':country,
403-
'timezone':timezone,
404-
'remote_instructions':remote_instructions,
405-
'agenda':agenda,
406-
'agenda_note':agenda_note}
398+
'meeting_type':'single',
399+
'form-0-date':date.strftime("%Y-%m-%d"),
400+
'form-0-time':time.strftime('%H:%M'),
401+
'form-0-duration':'03:00:00',
402+
'form-0-city':city,
403+
'form-0-country':country,
404+
'form-0-timezone':timezone,
405+
'form-0-remote_instructions':remote_instructions,
406+
'form-0-agenda':agenda,
407+
'form-0-agenda_note':agenda_note,
408+
'form-TOTAL_FORMS':1,
409+
'form-INITIAL_FORMS':0}
407410

408411
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
409412

@@ -422,5 +425,60 @@ def test_interim_request_submit(self):
422425
self.assertEqual(timeslot.time,dt)
423426
self.assertEqual(timeslot.duration,duration)
424427

428+
def test_interim_request_multi_day(self):
429+
make_meeting_test_data()
430+
date = datetime.date.today() + datetime.timedelta(days=30)
431+
date2 = date + datetime.timedelta(days=1)
432+
time = datetime.datetime.now().time().replace(microsecond=0,second=0)
433+
dt = datetime.datetime.combine(date, time)
434+
dt2 = datetime.datetime.combine(date2, time)
435+
duration = datetime.timedelta(hours=3)
436+
group = Group.objects.get(acronym='mars')
437+
city = 'San Francisco'
438+
country = 'US'
439+
timezone = 'US/Pacific'
440+
remote_instructions = 'Use webex'
441+
agenda = 'Intro. Slides. Discuss.'
442+
agenda_note = 'On second level'
443+
self.client.login(username="secretary", password="secretary+password")
444+
data = {'group':group.pk,
445+
'meeting_type':'multi-day',
446+
'form-0-date':date.strftime("%Y-%m-%d"),
447+
'form-0-time':time.strftime('%H:%M'),
448+
'form-0-duration':'03:00:00',
449+
'form-0-city':city,
450+
'form-0-country':country,
451+
'form-0-timezone':timezone,
452+
'form-0-remote_instructions':remote_instructions,
453+
'form-0-agenda':agenda,
454+
'form-0-agenda_note':agenda_note,
455+
'form-1-date':date2.strftime("%Y-%m-%d"),
456+
'form-1-time':time.strftime('%H:%M'),
457+
'form-1-duration':'03:00:00',
458+
'form-1-city':city,
459+
'form-1-country':country,
460+
'form-1-timezone':timezone,
461+
'form-1-remote_instructions':remote_instructions,
462+
'form-1-agenda':agenda,
463+
'form-1-agenda_note':agenda_note,
464+
'form-TOTAL_FORMS':2,
465+
'form-INITIAL_FORMS':0}
425466

467+
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
468+
469+
self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming'))
470+
meeting = Meeting.objects.order_by('id').last()
471+
self.assertEqual(meeting.type_id,'interim')
472+
self.assertEqual(meeting.date,date)
473+
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year,group.acronym,1))
474+
self.assertEqual(meeting.city,city)
475+
self.assertEqual(meeting.country,country)
476+
self.assertEqual(meeting.time_zone,timezone)
477+
self.assertEqual(meeting.agenda_note,agenda_note)
478+
self.assertEqual(meeting.session_set.count(),2)
479+
for session in meeting.session_set.all():
480+
self.assertEqual(session.remote_instructions,remote_instructions)
481+
timeslot = session.official_timeslotassignment().timeslot
482+
self.assertEqual(timeslot.time,dt2)
483+
self.assertEqual(timeslot.duration,duration)
426484

ietf/meeting/views.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from django.db.models import Min, Max
2121
from django.conf import settings
2222
from django.forms.models import modelform_factory
23-
from django.forms import ModelForm
23+
from django.forms import ModelForm, formset_factory
2424
from django.views.decorators.csrf import ensure_csrf_cookie
2525

2626
from ietf.doc.models import Document, State
@@ -35,11 +35,11 @@
3535
from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting
3636
from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions, get_meetings
3737
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
38-
from ietf.meeting.helpers import convert_draft_to_pdf
38+
from ietf.meeting.helpers import convert_draft_to_pdf, get_next_interim_number
3939
from ietf.utils.pipe import pipe
4040
from ietf.utils.pdf import pdf_pages
4141

42-
from .forms import InterimRequestForm
42+
from .forms import InterimRequestForm, InterimSessionForm
4343

4444
# -------------------------------------------------
4545
# Helper Functions
@@ -892,17 +892,36 @@ def sort_key(session):
892892
@role_required('Area Director','Secretariat','IRTF Chair','WG Chair')
893893
def interim_request(request):
894894
'''View for requesting an interim meeting'''
895-
#form = InterimRequestForm(request=request)
896-
895+
SessionFormset = formset_factory(InterimSessionForm, extra=2)
896+
897897
if request.method == 'POST':
898898
form = InterimRequestForm(request, data=request.POST)
899-
if form.is_valid():
900-
form.save()
899+
formset = SessionFormset(data=request.POST)
900+
if form.is_valid() and formset.is_valid():
901+
group = form.cleaned_data.get('group')
902+
meeting_type = form.cleaned_data.get('meeting_type')
903+
if meeting_type in ('single','multi-day'):
904+
date = sorted([ f.cleaned_data.get('date') for f in formset.forms])[0]
905+
number = get_next_interim_number(group,date)
906+
city = formset.forms[0].cleaned_data.get('city')
907+
country = formset.forms[0].cleaned_data.get('country')
908+
timezone = formset.forms[0].cleaned_data.get('timezone')
909+
meeting = Meeting.objects.create(number=number,type_id='interim',date=date,city=city,
910+
country=country,time_zone=timezone)
911+
schedule = Schedule.objects.create(meeting=meeting, owner=person, visible=True, public=True)
912+
meeting.agenda = schedule
913+
meeting.save()
914+
for f in formset.forms:
915+
# TODO: create meetings if type == series
916+
f.save(request,group,meeting)
901917
return redirect(upcoming)
918+
else:
919+
assert False, (form.errors, formset.errors)
902920
else:
903921
form = InterimRequestForm(request=request,initial={'meeting_type':'single'})
922+
formset = SessionFormset()
904923

905-
return render(request, "meeting/interim_request.html", {"form":form})
924+
return render(request, "meeting/interim_request.html", {"form":form, "formset":formset})
906925

907926
def ical_upcoming(request):
908927
'''ICAL upcoming meetings'''

ietf/static/ietf/css/ietf.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,7 @@ form.navbar-form input.form-control.input-sm { width: 141px; }
462462
padding: 16px 16px;
463463
margin-bottom: 20px;
464464
}
465+
466+
#interim-request-form .fieldset.template {
467+
display: none;
468+
}

ietf/static/ietf/js/meeting-interim-request.js

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,48 @@ var interimRequest = {
33
// functions for Interim Meeting Request
44
init : function() {
55
interimRequest.form = $(this);
6+
//interimRequest.sessionTemplate = interimRequest.form.find('.fieldset.template');
67
$('.select2-field').select2();
8+
$('#add_session').click(interimRequest.addSession);
79
$('#id_face_to_face').change(interimRequest.toggleLocation);
8-
$('#id_face_to_face').each(interimRequest.toggleLocation)
10+
$('#id_face_to_face').each(interimRequest.toggleLocation);
11+
},
12+
13+
addSession : function() {
14+
//var templateData = interimRequest.sessionTemplate.clone();
15+
var template = interimRequest.form.find('.fieldset.template');
16+
var el = template.clone(true);
17+
var totalField = $('#id_form-TOTAL_FORMS');
18+
var total = +totalField.val();
19+
20+
el.find(':input').each(function() {
21+
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
22+
var id = 'id_' + name;
23+
$(this).attr({'name': name, 'id': id}).val('');
24+
});
25+
26+
el.find('label').each(function() {
27+
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
28+
$(this).attr('for', newFor);
29+
});
30+
31+
++total;
32+
33+
totalField.val(total);
34+
35+
template.before(el);
36+
el.removeClass("template");
37+
38+
el.find(".select2-field").each(function () {
39+
setupSelect2Field($(this));
40+
});
941
},
1042

1143
toggleLocation : function() {
1244
if(this.checked){
13-
$("#id_city").prop('disabled', false);
14-
$("#id_country").prop('disabled', false);
15-
$("#id_timezone").prop('disabled', false);
45+
$(".location").prop('disabled', false);
1646
} else {
17-
$("#id_city").prop('disabled', true);
18-
$("#id_country").prop('disabled', true);
19-
$("#id_timezone").prop('disabled', true);
47+
$(".location").prop('disabled', true);
2048
}
2149
}
2250
}

ietf/templates/meeting/interim_request.html

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h1>Interim Meeting Request</h1>
3333
<!-- <div id="meeting-type-options"> -->
3434
<div class="col-md-2 radio-inline"><strong>Meeting Type:</strong></div>
3535
<label class="radio-inline">
36-
<input type="radio" value="single" name="meeting_type">Single
36+
<input type="radio" value="single" checked="checked" name="meeting_type">Single
3737
</label>
3838
<label class="radio-inline">
3939
<input type="radio" value="multi-day" name="meeting_type">Multi-Day
@@ -47,13 +47,15 @@ <h1>Interim Meeting Request</h1>
4747

4848
</div>
4949

50-
<div class="fieldset">
50+
{{ formset.management_form }}
51+
{% for form in formset %}
52+
<div class="fieldset{% if forloop.last %} template{% endif %}" >
5153
<div class="form-group">
5254
<label for="face-to_face" class="col-md-2 control-label">Location</label>
5355
<div class="col-md-10 form-inline">
54-
{% render_field form.city class="form-control" placeholder="City" %}
55-
{% render_field form.country class="form-control" %}
56-
{% render_field form.timezone class="form-control" %}
56+
{% render_field form.city class="form-control location" placeholder="City" %}
57+
{% render_field form.country class="form-control location" %}
58+
{% render_field form.timezone class="form-control location" %}
5759
</div>
5860
</div>
5961

@@ -83,6 +85,11 @@ <h1>Interim Meeting Request</h1>
8385
<div class="col-md-10">{% render_field form.agenda_note class="form-control" placeholder="Note" %}</div>
8486
</div>
8587
</div> <!-- fieldset -->
88+
{% endfor %}
89+
90+
<div class="row">
91+
<button id="add_session" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus" aria-hidden="true">Add Session</button>
92+
</div>
8693

8794
{% buttons %}
8895
<button type="submit" class="btn btn-primary">Submit</button>

0 commit comments

Comments
 (0)