diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py index 8b4f3addb..45a53188a 100644 --- a/ietf/meeting/forms.py +++ b/ietf/meeting/forms.py @@ -3,6 +3,7 @@ import re from django import forms from django.core.validators import ValidationError +from django.forms import BaseFormSet from django.forms.fields import Field from django.utils.encoding import force_text from django.utils import six @@ -110,6 +111,27 @@ class GroupModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return obj.acronym +''' +class BaseSessionFormSet(BaseFormSet): + def save_new_objects(self, commit=True): + self.new_objects = [] + for form in self.extra_forms: + if not form.has_changed(): + continue + # If someone has marked an add form for deletion, don't save the + # object. + if self.can_delete and self._should_delete_form(form): + continue + self.new_objects.append(self.save_new(form, commit=commit)) + if not commit: + self.saved_forms.append(form) + return self.new_objects + + def save(self,commit=True): + #return self.save_existing_objects(commit) + self.save_new_objects(commit) + return self.save_new_objects(commit) +''' + # ------------------------------------------------- # Forms # ------------------------------------------------- @@ -140,10 +162,16 @@ class InterimRequestForm(forms.Form): self.fields['group'].queryset = queryset + # if there's only one possibility make it the default + if len(queryset) == 1: + self.fields['group'].initial = queryset[0] + class InterimSessionForm(forms.Form): date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Date', required=True) time = forms.TimeField() + utc_time = forms.TimeField() duration = DurationField() + end_time = forms.TimeField() remote_instructions = forms.CharField(max_length=1024,required=False) agenda = forms.CharField(required=False,widget=forms.Textarea) agenda_note = forms.CharField(max_length=255,required=False) diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py index aa2af76fc..b733ec281 100644 --- a/ietf/meeting/helpers.py +++ b/ietf/meeting/helpers.py @@ -18,7 +18,7 @@ from ietf.doc.models import Document from ietf.group.models import Group from ietf.ietfauth.utils import has_role, user_is_person from ietf.person.models import Person -from ietf.meeting.models import Meeting +from ietf.meeting.models import Meeting, Schedule from ietf.utils.history import find_history_active_at, find_history_replacements_active_at from ietf.utils.pipe import pipe @@ -285,6 +285,35 @@ def session_constraint_expire(request,session): if key is not None and cache.has_key(key): cache.delete(key) +def get_earliest_session(session_formset): + '''Return earliest InterimSessionForm from formset''' + earliest = session_formset[0] + if len(session_formset) == 1: + return earliest + for form in session_formset[1:]: + date = form.cleaned_data.get('date') + if date and date < earliest.cleaned_data.get('date'): + earliest = form + return earliest + def get_next_interim_number(group,date): sequence = Meeting.objects.filter(number__startswith='interim-%s-%s' % (date.year,group.acronym)).count() + 1 return 'interim-%s-%s-%s' % (date.year,group.acronym,sequence) + +def create_interim_meeting(request_form,session_form): + '''Create an Interim meeting, given an InterimRequestForm and InterimSessionForm''' + group = request_form.cleaned_data.get('group') + date = session_form.cleaned_data.get('date') + number = get_next_interim_number(group,date) + city = session_form.cleaned_data.get('city') + country = session_form.cleaned_data.get('country') + timezone = session_form.cleaned_data.get('timezone') + if not request_form.cleaned_data.get('face_to_face'): + timezone = 'UTC' + meeting = Meeting.objects.create(number=number,type_id='interim',date=date,city=city, + country=country,time_zone=timezone) + schedule = Schedule.objects.create(meeting=meeting, owner=request_form.person, visible=True, public=True) + meeting.agenda = schedule + meeting.save() + return meeting + diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 4325eb832..883d5cbb8 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -381,6 +381,48 @@ class InterimTests(TestCase): len(q("#id_group option")) -1 ) # -1 for options placeholder def test_interim_request_single(self): + make_meeting_test_data() + group = Group.objects.get(acronym='mars') + date = datetime.date.today() + datetime.timedelta(days=30) + time = datetime.datetime.now().time().replace(microsecond=0,second=0) + dt = datetime.datetime.combine(date, time) + duration = datetime.timedelta(hours=3) + remote_instructions = 'Use webex' + agenda = 'Intro. Slides. Discuss.' + agenda_note = 'On second level' + self.client.login(username="secretary", password="secretary+password") + data = {'group':group.pk, + 'meeting_type':'single', + 'form-0-date':date.strftime("%Y-%m-%d"), + 'form-0-time':time.strftime('%H:%M'), + 'form-0-duration':'03:00:00', + 'form-0-city':'', + 'form-0-country':'', + 'form-0-timezone':'', + 'form-0-remote_instructions':remote_instructions, + 'form-0-agenda':agenda, + 'form-0-agenda_note':agenda_note, + 'form-TOTAL_FORMS':1, + 'form-INITIAL_FORMS':0} + + r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) + + self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) + meeting = Meeting.objects.order_by('id').last() + self.assertEqual(meeting.type_id,'interim') + self.assertEqual(meeting.date,date) + self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year,group.acronym,1)) + self.assertEqual(meeting.city,'') + self.assertEqual(meeting.country,'') + self.assertEqual(meeting.time_zone,'UTC') + self.assertEqual(meeting.agenda_note,agenda_note) + session = meeting.session_set.first() + self.assertEqual(session.remote_instructions,remote_instructions) + timeslot = session.official_timeslotassignment().timeslot + self.assertEqual(timeslot.time,dt) + self.assertEqual(timeslot.duration,duration) + + def test_interim_request_single_f2f(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') date = datetime.date.today() + datetime.timedelta(days=30) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index b68b1509f..bebbb0d29 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -35,7 +35,8 @@ from ietf.meeting.helpers import get_modified_from_assignments from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions, get_meetings from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file -from ietf.meeting.helpers import convert_draft_to_pdf, get_next_interim_number +from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session +from ietf.meeting.helpers import create_interim_meeting from ietf.utils.pipe import pipe from ietf.utils.pdf import pdf_pages @@ -897,22 +898,20 @@ def interim_request(request): if request.method == 'POST': form = InterimRequestForm(request, data=request.POST) formset = SessionFormset(data=request.POST) + person = request.user.person if form.is_valid() and formset.is_valid(): group = form.cleaned_data.get('group') meeting_type = form.cleaned_data.get('meeting_type') + + # pre create meeting if meeting_type in ('single','multi-day'): - date = sorted([ f.cleaned_data.get('date') for f in formset.forms])[0] - number = get_next_interim_number(group,date) - city = formset.forms[0].cleaned_data.get('city') - country = formset.forms[0].cleaned_data.get('country') - timezone = formset.forms[0].cleaned_data.get('timezone') - meeting = Meeting.objects.create(number=number,type_id='interim',date=date,city=city, - country=country,time_zone=timezone) - schedule = Schedule.objects.create(meeting=meeting, owner=person, visible=True, public=True) - meeting.agenda = schedule - meeting.save() + meeting = create_interim_meeting(request_form=form,session_form=get_earliest_session(formset)) + for f in formset.forms: - # TODO: create meetings if type == series + if not f.has_changed(): + continue + if meeting_type == 'series': + meeting = create_interim_meeting(form,f) f.save(request,group,meeting) return redirect(upcoming) else: @@ -935,7 +934,7 @@ def ical_upcoming(request): def upcoming(request): '''List of upcoming meetings''' today = datetime.datetime.today() - meetings = Meeting.objects.filter(date__gt=today) + meetings = Meeting.objects.filter(date__gte=today).order_by('date') # extract groups hierarchy seen = set() diff --git a/ietf/static/ietf/css/ietf.css b/ietf/static/ietf/css/ietf.css index abad5db3a..d9e0bff2e 100644 --- a/ietf/static/ietf/css/ietf.css +++ b/ietf/static/ietf/css/ietf.css @@ -466,3 +466,7 @@ form.navbar-form input.form-control.input-sm { width: 141px; } #interim-request-form .fieldset.template { display: none; } + +#interim-request-form .time-field { + width: 100px; +} diff --git a/ietf/static/ietf/js/meeting-interim-request.js b/ietf/static/ietf/js/meeting-interim-request.js index 5116de79a..43e8aa4ef 100644 --- a/ietf/static/ietf/js/meeting-interim-request.js +++ b/ietf/static/ietf/js/meeting-interim-request.js @@ -2,12 +2,18 @@ var interimRequest = { // functions for Interim Meeting Request init : function() { + // get elements interimRequest.form = $(this); - //interimRequest.sessionTemplate = interimRequest.form.find('.fieldset.template'); + interimRequest.addButton = $('#add_session'); + interimRequest.faceToFace = $('#id_face_to_face'); + // bind functions $('.select2-field').select2(); $('#add_session').click(interimRequest.addSession); $('#id_face_to_face').change(interimRequest.toggleLocation); - $('#id_face_to_face').each(interimRequest.toggleLocation); + $('input[name="meeting_type"]').change(interimRequest.checkAddButton); + // init + interimRequest.faceToFace.each(interimRequest.toggleLocation); + interimRequest.checkAddButton(); }, addSession : function() { @@ -16,6 +22,7 @@ var interimRequest = { var el = template.clone(true); var totalField = $('#id_form-TOTAL_FORMS'); var total = +totalField.val(); + var meeting_type = $('input[name="meeting_type"]:checked').val(); el.find(':input').each(function() { var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-'); @@ -38,6 +45,27 @@ var interimRequest = { el.find(".select2-field").each(function () { setupSelect2Field($(this)); }); + + if(interimRequest.faceToFace.prop('checked')){ + var first_session = $(".fieldset:first"); + el.find("input[name$='city']").val(first_session.find("input[name$='city']").val()); + el.find("select[name$='country']").val(first_session.find("select[name$='country']").val()); + el.find("select[name$='timezone']").val(first_session.find("select[name$='timezone']").val()); + } + + if(meeting_type == 'multi-day'){ + el.find(".location").prop('disabled', true); + } + + }, + + checkAddButton : function() { + var meeting_type = $('input[name="meeting_type"]:checked').val(); + if(meeting_type == 'single'){ + interimRequest.addButton.hide(); + } else { + interimRequest.addButton.show(); + } }, toggleLocation : function() { diff --git a/ietf/templates/meeting/interim_request.html b/ietf/templates/meeting/interim_request.html index ba96a4ff2..4112904ad 100644 --- a/ietf/templates/meeting/interim_request.html +++ b/ietf/templates/meeting/interim_request.html @@ -63,9 +63,16 @@
-
{% render_field form.date class="form-control" %}
-
{% render_field form.time class="form-control" placeholder="time" %}
-
{% render_field form.duration class="form-control" placeholder="duration" %}
+
+ {% render_field form.date class="form-control" %} +
+ {% render_field form.time class="form-control time-field" placeholder="time" %} +

Time

+
+ {% render_field form.utc_time class="form-control time-field computed" disabled="disabled" placeholder="UTC time" %} + {% render_field form.duration class="form-control time-field" placeholder="duration" %} + {% render_field form.end_time class="form-control time-field computed" placeholder="end time" disabled="disabled" %} +
@@ -87,14 +94,19 @@ {% endfor %} -
- -
+
+
+ +
+
+
Submit Back {% endbuttons %} +
+ {% endblock %}