diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py index fd2f9e4c1..4a3b1e1f7 100644 --- a/ietf/meeting/forms.py +++ b/ietf/meeting/forms.py @@ -3,18 +3,19 @@ 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 from ietf.group.models import Group from ietf.ietfauth.utils import has_role -from ietf.meeting.models import Meeting, Schedule, TimeSlot, Session, SchedTimeSessAssignment, countries, timezones +from ietf.meeting.models import Session, countries, timezones +from ietf.meeting.helpers import assign_interim_session from ietf.utils.fields import DatepickerDateField # need to insert empty option for use in ChoiceField -countries.insert(0, ('', '-'*9 )) +#countries.insert(0, ('', '-'*9 )) +countries.insert(0, ('', '')) timezones.insert(0, ('', '-'*9 )) # ------------------------------------------------- @@ -138,7 +139,7 @@ class BaseSessionFormSet(BaseFormSet): class InterimRequestForm(forms.Form): group = GroupModelChoiceField(queryset = Group.objects.filter(type__in=('wg','rg'),state='active').order_by('acronym')) - face_to_face = forms.BooleanField(required=False) + in_person = forms.BooleanField(required=False) meeting_type = forms.ChoiceField(choices=(("single", "Single"), ("multi-day", "Multi-Day"), ('series','Series')), required=False, initial='single', widget=forms.RadioSelect) approved = forms.BooleanField(required=False) @@ -160,7 +161,8 @@ class InterimRequestForm(forms.Form): queryset = Group.objects.filter(type="rg", state="active").order_by('acronym') elif has_role(self.user, "WG Chair"): queryset = Group.objects.filter(type="wg", state="active", role__person=self.person, role__name="chair").distinct().order_by('acronym') - + elif has_role(self.user, "RG Chair"): + queryset = Group.objects.filter(type="rg", state="active", role__person=self.person, role__name="chair").distinct().order_by('acronym') self.fields['group'].queryset = queryset # if there's only one possibility make it the default @@ -168,10 +170,11 @@ class InterimRequestForm(forms.Form): 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() + # unset: date,time,duration + date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Date', required=False) + time = forms.TimeField(required=False) time_utc = forms.TimeField(required=False) - duration = DurationField() + duration = DurationField(required=False) end_time = forms.TimeField(required=False) end_time_utc = forms.TimeField(required=False) remote_instructions = forms.CharField(max_length=1024,required=False) @@ -196,17 +199,16 @@ class InterimSessionForm(forms.Form): time = self.cleaned_data.get('time') duration = self.cleaned_data.get('duration') remote_instructions = self.cleaned_data.get('remote_instructions') - - slot = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=duration, - time=datetime.datetime.combine(date, time)) + time=datetime.datetime.combine(date, time) session = Session.objects.create(meeting=meeting, group=group, requested_by=person, + requested_duration=duration, status_id='apprw', type_id='session', remote_instructions=remote_instructions, agenda_note=agenda_note,) - SchedTimeSessAssignment.objects.create(timeslot=slot, session=session, schedule=meeting.agenda) - + assign_interim_session(session,time) + if agenda: self._save_agenda(agenda) diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py index b733ec281..8b1d0d1e2 100644 --- a/ietf/meeting/helpers.py +++ b/ietf/meeting/helpers.py @@ -17,8 +17,9 @@ import debug # pyflakes:ignore from ietf.doc.models import Document from ietf.group.models import Group from ietf.ietfauth.utils import has_role, user_is_person +from ietf.liaisons.utils import get_person_for_user from ietf.person.models import Person -from ietf.meeting.models import Meeting, Schedule +from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment from ietf.utils.history import find_history_active_at, find_history_replacements_active_at from ietf.utils.pipe import pipe @@ -285,6 +286,10 @@ def session_constraint_expire(request,session): if key is not None and cache.has_key(key): cache.delete(key) +# ------------------------------------------------- +# Interim Meeting Helpers +# ------------------------------------------------- + def get_earliest_session(session_formset): '''Return earliest InterimSessionForm from formset''' earliest = session_formset[0] @@ -300,20 +305,79 @@ 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') +def create_interim_meeting(group,date,city='',country='',timezone='UTC',person=None): + '''Helper function to create interim meeting and associated schedule''' + if not person: + person = Person.objects.get(name="(System)") 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) + schedule = Schedule.objects.create(meeting=meeting, owner=person, visible=True, public=True) meeting.agenda = schedule meeting.save() return meeting +def create_interim_meeting_from_forms(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') + city = session_form.cleaned_data.get('city') + country = session_form.cleaned_data.get('country') + timezone = session_form.cleaned_data.get('timezone') + person = request_form.person + return create_interim_meeting(group=group,date=date,city=city,country=country,timezone=timezone,person=person) + +def assign_interim_session(session,time): + '''Helper function to create a timeslot and assign the interim session''' + slot = TimeSlot.objects.create(meeting=session.meeting, type_id="session", + duration=session.requested_duration, time=time) + SchedTimeSessAssignment.objects.create(timeslot=slot, session=session, schedule=session.meeting.agenda) + +def can_approve_interim_request(meeting,user): + '''Returns True if the user has permissions to approve an interim meeting request''' + if meeting.type.slug != 'interim': + return False + if has_role(user, 'Secretariat'): + return True + person = get_person_for_user(user) + session = meeting.session_set.first() + if not session: + return False + group = session.group + if group.type.slug == 'wg' and group.parent.role_set.filter(name='ad',person=person): + return True + if group.type.slug == 'rg' and group.parent.role_set.filter(name='chair',person=person): + return True + return False + +def can_edit_interim_request(meeting,user): + '''Returns True if the user can edit the interim meeting request''' + + if can_approve_interim_request(meeting,user): + return True + + return False + +def can_request_interim_meeting(user): + if has_role(user, ('Secretariat','Area Director','WG Chair','IRTF Chair', 'RG Chair')): + return True + return False + +def can_view_interim_request(meeting,user): + '''Returns True if the user can see the pending interim request in the pending interim view''' + if meeting.type.slug != 'interim': + return False + if has_role(user, 'Secretariat'): + return True + person = get_person_for_user(user) + session = meeting.session_set.first() + if not session: + return False + group = session.group + if has_role(user, 'Area Director') and group.type.slug == 'wg': + return True + if has_role(user, 'IRTF Chair') and group.type.slug == 'rg': + return True + if group.role_set.filter(name='chair',person=person): + return True + return False diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py index 17bccc9e2..657e59c93 100644 --- a/ietf/meeting/test_data.py +++ b/ietf/meeting/test_data.py @@ -3,10 +3,21 @@ import datetime from ietf.doc.models import Document, State from ietf.group.models import Group from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, SchedTimeSessAssignment, ResourceAssociation, SessionPresentation +from ietf.meeting.helpers import create_interim_meeting, assign_interim_session from ietf.name.models import RoomResourceName from ietf.person.models import Person from ietf.utils.test_data import make_test_data +def make_interim_meeting(group,date,status='sched'): + system_person = Person.objects.get(name="(System)") + time = datetime.datetime.combine(date, datetime.time(9)) + meeting = create_interim_meeting(group=group,date=date) + session = Session.objects.create(meeting=meeting, group=group, + attendees=10, requested_by=system_person, + requested_duration=20, status_id=status, + scheduled=datetime.datetime.now(),type_id="session") + assign_interim_session(session,time) + return meeting def make_meeting_test_data(): if not Group.objects.filter(acronym='mars'): @@ -79,29 +90,13 @@ def make_meeting_test_data(): # Future Interim Meetings date = datetime.date.today() + datetime.timedelta(days=365) - mars_meeting = Meeting.objects.create( - number="interim-%s-mars-1" % date.year, - type_id='interim', - date=date, - city="New York", - country="US", - ) - mars_session = Session.objects.create(meeting=mars_meeting, group=mars, - attendees=10, requested_by=system_person, - requested_duration=20, status_id="sched", - scheduled=datetime.datetime.now(),type_id="session") + date2 = datetime.date.today() + datetime.timedelta(days=1000) ames = Group.objects.get(acronym="ames") - ames_meeting = Meeting.objects.create( - number="interim-%s-ames-1" % date.year, - type_id='interim', - date=date, - city="New York", - country="US", - ) - ames_session = Session.objects.create(meeting=ames_meeting, group=ames, - attendees=10, requested_by=system_person, - requested_duration=20, status_id="canceled", - scheduled=datetime.datetime.now(),type_id="session") + + make_interim_meeting(group=mars,date=date,status='sched') + make_interim_meeting(group=mars,date=date2,status='apprw') + make_interim_meeting(group=ames,date=date,status='canceled') + make_interim_meeting(group=ames,date=date2,status='apprw') return meeting diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 883d5cbb8..ecbce5b2c 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -5,11 +5,13 @@ import urlparse from django.core.urlresolvers import reverse as urlreverse from django.conf import settings +from django.contrib.auth.models import User from pyquery import PyQuery from ietf.doc.models import Document from ietf.group.models import Group +from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request from ietf.meeting.models import Session, TimeSlot, Meeting from ietf.meeting.test_data import make_meeting_test_data from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent @@ -332,6 +334,10 @@ class EditTests(TestCase): self.assertTrue(mars_slot.slot_to_the_right) self.assertTrue(mars_scheduled.slot_to_the_right) +# ------------------------------------------------- +# Interim Meeting Tests +# ------------------------------------------------- + class InterimTests(TestCase): def test_upcoming(self): make_meeting_test_data() @@ -354,20 +360,30 @@ class InterimTests(TestCase): def test_interim_request_permissions(self): '''Ensure only authorized users see link to request interim meeting''' - # test unauthorized + make_meeting_test_data() + + # test unauthorized not logged in upcoming_url = urlreverse("ietf.meeting.views.upcoming") request_url = urlreverse("ietf.meeting.views.interim_request") r = self.client.get(upcoming_url) self.assertNotContains(r,'Request new interim meeting') + + # test unauthorized user + login_testing_unauthorized(self,"plain",request_url) + r = self.client.get(upcoming_url) + self.assertNotContains(r,'Request new interim meeting') r = self.client.get(request_url) - self.assertRedirects(r, '/accounts/login/?next=/meeting/interim/request/') + self.assertEqual(r.status_code, 403) + self.client.logout() # test authorized - self.client.login(username="secretary", password="secretary+password") - r = self.client.get(upcoming_url) - self.assertContains(r,'Request new interim meeting') - r = self.client.get(request_url) - self.assertEqual(r.status_code, 200) + for username in ('secretary','ad','marschairman','irtf-chair','irgchairman'): + self.client.login(username=username, password= username + "+password") + r = self.client.get(upcoming_url) + self.assertContains(r,'Request new interim meeting') + r = self.client.get(request_url) + self.assertEqual(r.status_code, 200) + self.client.logout() def test_interim_request_options(self): make_meeting_test_data() @@ -398,7 +414,7 @@ class InterimTests(TestCase): 'form-0-duration':'03:00:00', 'form-0-city':'', 'form-0-country':'', - 'form-0-timezone':'', + 'form-0-timezone':'UTC', 'form-0-remote_instructions':remote_instructions, 'form-0-agenda':agenda, 'form-0-agenda_note':agenda_note, @@ -415,9 +431,9 @@ class InterimTests(TestCase): 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) + self.assertEqual(session.agenda_note,agenda_note) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) @@ -460,9 +476,9 @@ class InterimTests(TestCase): self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,timezone) - self.assertEqual(meeting.agenda_note,agenda_note) session = meeting.session_set.first() self.assertEqual(session.remote_instructions,remote_instructions) + self.assertEqual(session.agenda_note,agenda_note) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) @@ -516,11 +532,97 @@ class InterimTests(TestCase): self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,timezone) - self.assertEqual(meeting.agenda_note,agenda_note) self.assertEqual(meeting.session_set.count(),2) - for session in meeting.session_set.all(): - self.assertEqual(session.remote_instructions,remote_instructions) - timeslot = session.official_timeslotassignment().timeslot - self.assertEqual(timeslot.time,dt2) - self.assertEqual(timeslot.duration,duration) + # first sesstion + session = meeting.session_set.all()[0] + self.assertEqual(session.remote_instructions,remote_instructions) + timeslot = session.official_timeslotassignment().timeslot + self.assertEqual(timeslot.time,dt) + self.assertEqual(timeslot.duration,duration) + self.assertEqual(session.agenda_note,agenda_note) + # second sesstion + session = meeting.session_set.all()[1] + self.assertEqual(session.remote_instructions,remote_instructions) + timeslot = session.official_timeslotassignment().timeslot + self.assertEqual(timeslot.time,dt2) + self.assertEqual(timeslot.duration,duration) + self.assertEqual(session.agenda_note,agenda_note) + def test_interim_pending(self): + make_meeting_test_data() + url = urlreverse('ietf.meeting.views.interim_pending') + count = Meeting.objects.filter(type='interim',session__status='apprw').distinct().count() + + # unpriviledged user + login_testing_unauthorized(self,"plain",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 403) + + # secretariat + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q("#pending-interim-meetings-table tr"))-1, count) + self.client.logout() + + def test_can_approve_interim_request(self): + make_meeting_test_data() + # unprivileged user + user = User.objects.get(username='plain') + group = Group.objects.get(acronym='mars') + meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group=group).first() + self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) + # Secretariat + user = User.objects.get(username='secretary') + self.assertTrue(can_approve_interim_request(meeting=meeting,user=user)) + # related AD + user = User.objects.get(username='ad') + self.assertTrue(can_approve_interim_request(meeting=meeting,user=user)) + # other AD + user = User.objects.get(username='ops-ad') + self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) + # WG Chair + user = User.objects.get(username='marschairman') + self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) + + def test_can_view_interim_request(self): + make_meeting_test_data() + # unprivileged user + user = User.objects.get(username='plain') + group = Group.objects.get(acronym='mars') + meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group=group).first() + self.assertFalse(can_view_interim_request(meeting=meeting,user=user)) + # Secretariat + user = User.objects.get(username='secretary') + self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) + # related AD + user = User.objects.get(username='ad') + self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) + # other AD + user = User.objects.get(username='ops-ad') + self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) + # WG Chair + user = User.objects.get(username='marschairman') + self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) + # Other WG Chair + user = User.objects.get(username='ameschairman') + self.assertFalse(can_view_interim_request(meeting=meeting,user=user)) + + def test_interim_request_details(self): + make_meeting_test_data() + meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first() + url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + + def test_interim_request_details_permissions(self): + make_meeting_test_data() + meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first() + url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) + + # unprivileged user + login_testing_unauthorized(self,"plain",url) + r = self.client.get(url) + self.assertEqual(r.status_code, 403) diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 4be559919..39c7e5576 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -68,7 +68,9 @@ urlpatterns = [ url(r'^(?P\d+)/', include(type_ietf_only_patterns)), url(r'^upcoming/$', views.upcoming), url(r'^upcoming.ics/$', views.ical_upcoming), + url(r'^interim/request/(?P[A-Za-z0-9._+-]+)/$', views.interim_request_details), url(r'^interim/request/$', views.interim_request), + url(r'^interim/pending/$', views.interim_pending), url(r'^$', views.current_materials), ] diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index a05f661d5..0858b992b 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -14,7 +14,7 @@ import pytz import debug # pyflakes:ignore from django import forms -from django.shortcuts import render, redirect +from django.shortcuts import render, redirect, get_object_or_404 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.contrib import messages from django.core.urlresolvers import reverse @@ -37,19 +37,14 @@ 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_earliest_session -from ietf.meeting.helpers import create_interim_meeting +from ietf.meeting.helpers import create_interim_meeting_from_forms +from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request +from ietf.meeting.helpers import can_request_interim_meeting from ietf.utils.pipe import pipe from ietf.utils.pdf import pdf_pages from .forms import InterimRequestForm, InterimSessionForm -# ------------------------------------------------- -# Helper Functions -# ------------------------------------------------- -def can_request_interim_meeting(user): - if has_role(user, ('Secretariat','Area Director','WG Chair','IRTF Chair')): - return True - return False # ------------------------------------------------- # View Functions @@ -907,8 +902,20 @@ def ajax_get_utc(request): utc = utc_dt.strftime('%H:%M') context_data = {'timezone':timezone,'time':time,'utc':utc} return HttpResponse(json.dumps(context_data),content_type='application/json') + +@role_required('Area Director','Secretariat','IRTF Chair','WG Chair','RG Chair') +def interim_pending(request): + '''View which shows interim meeting requests pending approval''' + meetings = Meeting.objects.filter(type='interim',session__status='apprw') + + meetings = [ m for m in meetings if can_view_interim_request(m,request.user)] + for meeting in meetings: + if can_approve_interim_request(meeting,request.user): + meeting.can_approve = True -@role_required('Area Director','Secretariat','IRTF Chair','WG Chair') + return render(request, "meeting/interim_pending.html", {"meetings":meetings}) + +@role_required('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair') def interim_request(request): '''View for requesting an interim meeting''' SessionFormset = formset_factory(InterimSessionForm, extra=2) @@ -916,20 +923,22 @@ def interim_request(request): if request.method == 'POST': form = InterimRequestForm(request, data=request.POST) formset = SessionFormset(data=request.POST) - person = request.user.person + #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'): - meeting = create_interim_meeting(request_form=form,session_form=get_earliest_session(formset)) + meeting = create_interim_meeting_from_forms( + request_form=form, + session_form=get_earliest_session(formset)) for f in formset.forms: if not f.has_changed(): continue if meeting_type == 'series': - meeting = create_interim_meeting(form,f) + meeting = create_interim_meeting_from_forms(form,f) f.save(request,group,meeting) return redirect(upcoming) else: @@ -940,6 +949,21 @@ def interim_request(request): return render(request, "meeting/interim_request.html", {"form":form, "formset":formset}) +@role_required('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair') +def interim_request_details(request, number): + '''View details of an interim meeting reqeust''' + meeting = get_object_or_404(Meeting,number=number) + sessions = meeting.session_set.all() + can_edit = can_view_interim_request(meeting,request.user) + can_approve = can_approve_interim_request(meeting,request.user) + + return render(request, "meeting/interim_request_details.html",{ + "meeting":meeting, + "sessions":sessions, + "can_edit":can_edit, + "can_approve":can_approve, + }) + def ical_upcoming(request): '''ICAL upcoming meetings''' today = datetime.datetime.today() diff --git a/ietf/static/ietf/js/meeting-interim-request.js b/ietf/static/ietf/js/meeting-interim-request.js index 0e81f8b86..e8a56d394 100644 --- a/ietf/static/ietf/js/meeting-interim-request.js +++ b/ietf/static/ietf/js/meeting-interim-request.js @@ -5,22 +5,26 @@ var interimRequest = { // get elements interimRequest.form = $(this); interimRequest.addButton = $('#add_session'); - interimRequest.faceToFace = $('#id_face_to_face'); + interimRequest.inPerson = $('#id_in_person'); // bind functions $('.select2-field').select2(); - $('#add_session').click(interimRequest.addSession); + interimRequest.addButton.click(interimRequest.addSession); $('.btn-delete').click(interimRequest.deleteSession); - $('#id_face_to_face').change(interimRequest.toggleLocation); - $('input[name="meeting_type"]').change(interimRequest.checkAddButton); + interimRequest.inPerson.change(interimRequest.toggleLocation); + //$('input[name="meeting_type"]').change(interimRequest.checkAddButton); + $('input[name="meeting_type"]').change(interimRequest.meetingTypeChanged); $('input[name$="-duration"]').blur(interimRequest.calculateEndTime); $('input[name$="-time"]').blur(interimRequest.calculateEndTime); //$('input[name$="-time"]').blur(interimRequest.setUTC); $('input[name$="-time"]').blur(interimRequest.updateInfo); $('input[name$="-end_time"]').change(interimRequest.updateInfo); $('select[name$="-timezone"]').change(interimRequest.timezoneChange); + //interimRequest.form.submit(interimRequest.onSubmit); // init - interimRequest.faceToFace.each(interimRequest.toggleLocation); + interimRequest.inPerson.each(interimRequest.toggleLocation); interimRequest.checkAddButton(); + interimRequest.checkHelpText(); + //interimRequest.showRequired(); }, addSession : function() { @@ -53,13 +57,13 @@ var interimRequest = { 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()); - //} - + // copy field contents + 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()); + el.find("input[name$='remote_instructions']").val(first_session.find("input[name$='remote_instructions']").val()); + if(meeting_type == 'multi-day'){ el.find(".location").prop('disabled', true); } @@ -67,6 +71,15 @@ var interimRequest = { $('.btn-delete').removeClass("hidden"); }, + onSubmit : function() { + // must remove required attribute from non visible fields + var template = $(this).find(".template"); + //template.find('input,textarea,select').filter('[required]').prop('required',false); + var x = template.find('input,textarea,select') + alert(x.length); + return true; + }, + updateInfo : function() { //var url = liaisonForm.form.data("ajaxInfoUrl"); //alert('called update'); @@ -124,6 +137,30 @@ var interimRequest = { interimRequest.addButton.show(); } }, + + checkHelpText : function() { + var meeting_type = $('input[name="meeting_type"]:checked').val(); + if(meeting_type == 'single'){ + $('.meeting-type-help').hide(); + } else if(meeting_type == 'multi-day') { + $('.meeting-type-help').hide(); + $('.mth-multi').show(); + } else if(meeting_type == 'series') { + $('.meeting-type-help').hide(); + $('.mth-series').show(); + } + }, + + checkInPerson : function() { + var meeting_type = $('input[name="meeting_type"]:checked').val(); + if(meeting_type == 'series'){ + interimRequest.inPerson.prop('disabled', true); + interimRequest.inPerson.prop('checked', false); + interimRequest.toggleLocation(); + } else { + interimRequest.inPerson.prop('disabled', false); + } + }, get_formatted_time : function (d) { // returns time from Date object as HH:MM @@ -151,6 +188,12 @@ var interimRequest = { return interimRequest.pad(hours) + ":" + interimRequest.pad(minutes); }, + meetingTypeChanged : function () { + interimRequest.checkAddButton(); + interimRequest.checkInPerson(); + interimRequest.checkHelpText(); + }, + pad : function(str) { // zero pads string 00 if(str.length == 1){ @@ -169,6 +212,12 @@ var interimRequest = { //alert(utc.attr("id")); }, + showRequired : function() { + // add a required class on labels on forms that should have + // explicit requirement asterisks + //$("form.show-required").find("input[required],select[required],textarea[required]").closest(".form-group").find("label").first().addClass("required"); + }, + timezoneChange : function() { //alert("tz change"); var fieldset = $(this).parents(".fieldset"); diff --git a/ietf/templates/meeting/interim_pending.html b/ietf/templates/meeting/interim_pending.html new file mode 100644 index 000000000..74602f74e --- /dev/null +++ b/ietf/templates/meeting/interim_pending.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} +{% load staticfiles bootstrap3 widget_tweaks %} + +{% block title %}Interim Pending{% endblock %} + +{% block pagehead %} + + +{% endblock %} + +{% block content %} + {% origin %} +

Pending Interim Meetings

+ + + {% if meetings %} + + + + + + + + + + + {% for meeting in meetings %} + {% if meeting.type.slug == 'interim' %} + + {% else %} + + {% endif %} + + {% if meeting.type.slug == 'interim' %} + + {% else %} + + {% endif %} + + + + {% endfor %} + +
DateGroupName
{{ meeting.date }}{{ meeting.session_set.all.0.group.acronym }}ietf + {% if meeting.type.slug == "interim" %} + {{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} -- CANCELLED --{% endif %} + {% else %} + IETF - {{ meeting.number }} + {% endif %} + {% if meeting.can_approve %}can approve{% endif %}
+ {% else %} +

No pending interim meetings

+ {% endif %} + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/meeting/interim_request.html b/ietf/templates/meeting/interim_request.html index 535a22194..eda18f055 100644 --- a/ietf/templates/meeting/interim_request.html +++ b/ietf/templates/meeting/interim_request.html @@ -22,61 +22,69 @@
- -
- - - +
+ +
+ +
+ +
+ +
Meeting Type:
+ + + + +
+
+ + +
+
+
+

Use Multi-Day to request one meeting with sessions over multiple days.

+
- -
- - - -
- - -
Meeting Type:
- - - - - - -
-
- + +
+
+
+

Use Series to request a number of separate meetings. In Person series is not supported.

+
+
+
+ {{ formset.management_form }} {% for form in formset %}
- +
{% render_field form.city class="form-control location" placeholder="City" %} - {% render_field form.country class="form-control location" %} + {% render_field form.country class="form-control location" placeholder="Country" %} {% render_field form.timezone class="form-control" %}
- +
{% render_field form.date class="form-control" %}
- +
{% render_field form.time class="form-control time-field" placeholder="HH:MM" %} {% render_field form.time_utc class="form-control time-field computed" disabled="disabled" placeholder="UTC" %}
- +
{% render_field form.end_time class="form-control time-field computed" placeholder="HH:MM" disabled="disabled" %} {% render_field form.end_time_utc class="form-control time-field computed" disabled="disabled" placeholder="UTC" %} @@ -84,7 +92,7 @@
- +
{% render_field form.duration class="form-control time-field" placeholder="HH:MM" %}
diff --git a/ietf/templates/meeting/interim_request_details.html b/ietf/templates/meeting/interim_request_details.html new file mode 100644 index 000000000..0436d0e0d --- /dev/null +++ b/ietf/templates/meeting/interim_request_details.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} +{% load staticfiles bootstrap3 widget_tweaks %} + +{% block title %}Interim Request Details{% endblock %} + +{% block pagehead %} + + +{% endblock %} + +{% block content %} + {% origin %} +

Interim Meeting Request Details

+
+
Group
+
{{ sessions.0.group.acronym }} +
Requested By
+
{{ sessions.0.requested_by }} +
City
+
{{ meeting.city }}
+
Country
+
{{ meeting.country }}
+
Timezone
+
{{ meeting.time_zone }}
+ {% for session in sessions %} +
Date
+
{{ session.official_timeslotassignment.timeslot.time|date:"Y-m-d" }} +
Start Time
+
{{ session.official_timeslotassignment.timeslot.time|date:"H:i" }} +
Duration
+
{{ session.requested_duration }} +
Remote Instructions
+
{{ session.remote_instructions }} +
Agenda Note
+
{{ session.agenda_note }}
+ {% endfor %} +
+ +
+ {% csrf_token %} + {% if can_edit %} + Edit + {% endif %} + {% if can_approve %} + + {% endif %} + {% if can_edit %} + + {% endif %} +
+ +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 7d04ff6f1..416d22fff 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -89,6 +89,12 @@ def make_immutable_base_data(): area = create_group(name="Far Future", acronym="farfut", type_id="area", parent=ietf) create_person(area, "ad", name="AreaĆ° Irector", username="ad", email_address="aread@ietf.org") + # second area + opsarea = create_group(name="Operations", acronym="ops", type_id="area", parent=ietf) + create_person(opsarea, "ad") + sops = create_group(name="Server Operations", acronym="sops", type_id="wg", parent=opsarea) + create_person(sops, "chair", name="Sops Chairman", username="sopschairman") + # create a bunch of ads for swarm tests for i in range(1, 10): u = User.objects.create(username="ad%s" % i) @@ -116,6 +122,7 @@ def make_immutable_base_data(): def make_test_data(): area = Group.objects.get(acronym="farfut") ad = Person.objects.get(user__username="ad") + irtf = Group.objects.get(acronym='irtf') # mars WG group = Group.objects.create( @@ -166,6 +173,30 @@ def make_test_data(): group.charter = charter group.save() + # irg RG + irg_rg = Group.objects.create( + name="Internet Research Group", + acronym="irg", + state_id="active", + type_id="rg", + parent=irtf, + list_email="irg-rg@ietf.org", + ) + #charter = Document.objects.create( + # name="charter-ietf-" + group.acronym, + # type_id="charter", + # title=group.name, + # group=group, + # rev="00", + # ) + #charter.set_state(State.objects.get(used=True, slug="infrev", type="charter")) + #DocAlias.objects.create( + # name=charter.name, + # document=charter + # ) + #group.charter = charter + #group.save() + # plain IETF'er u = User.objects.create(username="plain") u.set_password("plain+password") @@ -187,6 +218,8 @@ def make_test_data(): ames_wg.role_set.get_or_create(name_id='ad',person=ad,email=ad.role_email('ad')) ames_wg.save() + create_person(irg_rg, "chair", name="Irg Chair Man", username="irgchairman") + # old draft old_draft = Document.objects.create( name="draft-foo-mars-test",