add edit view. refactor to use modelforms

- Legacy-Id: 11176
This commit is contained in:
Ryan Cross 2016-05-06 23:02:25 +00:00
parent facead5d95
commit 3f3e6f0b14
12 changed files with 848 additions and 304 deletions

View file

@ -610,3 +610,9 @@ def session_end_time(session):
timeslot = session.official_timeslotassignment().timeslot
return timeslot.time + timeslot.duration
@register.filter
def format_timedelta(timedelta):
s = timedelta.seconds
hours, remainder = divmod(s, 3600)
minutes, seconds = divmod(remainder, 60)
return '{hours:02d}:{minutes:02d}'.format(hours=hours,minutes=minutes)

View file

@ -9,22 +9,27 @@ from django.utils.encoding import force_text
from django.utils import six
from ietf.doc.models import Document, DocAlias, State, NewRevisionDocEvent
from ietf.doc.utils import get_document_content
from ietf.group.models import Group
from ietf.ietfauth.utils import has_role
from ietf.meeting.models import Session, countries, timezones
from ietf.meeting.helpers import assign_interim_session
from ietf.meeting.models import Session, Meeting, Schedule, countries, timezones
from ietf.meeting.helpers import get_next_interim_number, assign_interim_session
from ietf.meeting.helpers import is_meeting_approved
from ietf.message.models import Message
from ietf.person.models import Person
from ietf.secr.utils.meeting import get_upload_root
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 ))
timezones.insert(0, ('', '-' * 9))
# -------------------------------------------------
# DurationField from Django 1.8
# -------------------------------------------------
def duration_string(duration):
days = duration.days
seconds = duration.seconds
@ -36,7 +41,8 @@ def duration_string(duration):
hours = minutes // 60
minutes = minutes % 60
string = '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
# string = '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
string = '{:02d}:{:02d}'.format(hours, minutes)
if days:
string = '{} '.format(days) + string
if microseconds:
@ -44,6 +50,10 @@ def duration_string(duration):
return string
custom_duration_re = re.compile(
r'^(?P<hours>\d+):(?P<minutes>\d+)$'
)
standard_duration_re = re.compile(
r'^'
r'(?:(?P<days>-?\d+) (days?, )?)?'
@ -67,6 +77,7 @@ iso8601_duration_re = re.compile(
r'$'
)
def parse_duration(value):
"""Parses a duration string and returns a datetime.timedelta.
@ -74,7 +85,9 @@ def parse_duration(value):
Also supports ISO 8601 representation.
"""
match = standard_duration_re.match(value)
match = custom_duration_re.match(value)
if not match:
match = standard_duration_re.match(value)
if not match:
match = iso8601_duration_re.match(value)
if match:
@ -84,6 +97,7 @@ def parse_duration(value):
kw = {k: float(v) for k, v in six.iteritems(kw) if v is not None}
return datetime.timedelta(**kw)
class DurationField(Field):
default_error_messages = {
'invalid': 'Enter a valid duration.',
@ -109,6 +123,7 @@ class DurationField(Field):
# Helpers
# -------------------------------------------------
class GroupModelChoiceField(forms.ModelChoiceField):
'''
Custom ModelChoiceField, changes the label to a more readable format
@ -116,43 +131,42 @@ 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
# -------------------------------------------------
class InterimRequestForm(forms.Form):
group = GroupModelChoiceField(queryset = Group.objects.filter(type__in=('wg','rg'),state='active').order_by('acronym'))
class InterimMeetingModelForm(forms.ModelForm):
group = GroupModelChoiceField(queryset=Group.objects.filter(type__in=('wg', 'rg'), state='active').order_by('acronym'))
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)
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)
city = forms.CharField(max_length=255, required=False)
country = forms.ChoiceField(choices=countries, required=False)
time_zone = forms.ChoiceField(choices=timezones)
class Meta:
model = Meeting
fields = ('group', 'in_person', 'meeting_type', 'approved', 'city', 'country', 'time_zone')
def __init__(self, request, *args, **kwargs):
super(InterimRequestForm, self).__init__(*args, **kwargs)
super(InterimMeetingModelForm, self).__init__(*args, **kwargs)
self.user = request.user
self.person = self.user.person
self.fields["group"].widget.attrs["class"] = "select2-field"
self.is_edit = bool(self.instance.pk)
self.fields['group'].widget.attrs['class'] = "select2-field"
self.fields['time_zone'].initial = 'UTC'
self.set_group_options()
if self.is_edit:
self.fields['group'].initial = self.instance.session_set.first().group
self.fields['group'].widget.attrs['disabled'] = True
if self.instance.city or self.instance.country:
self.fields['in_person'].initial = True
if is_meeting_approved(self.instance):
self.fields['approved'].initial = True
def set_group_options(self):
'''Set group options based on user accessing the form'''
@ -160,40 +174,132 @@ class InterimRequestForm(forms.Form):
if has_role(self.user, "Secretariat"):
return # don't reduce group options
if has_role(self.user, "Area Director"):
queryset = Group.objects.filter(type="wg", state__in=("active","proposed")).order_by('acronym')
queryset = Group.objects.filter(type="wg", state__in=("active", "proposed")).order_by('acronym')
elif has_role(self.user, "IRTF Chair"):
queryset = Group.objects.filter(type="rg", state__in=("active","proposed")).order_by('acronym')
queryset = Group.objects.filter(type="rg", state__in=("active", "proposed")).order_by('acronym')
elif has_role(self.user, "WG Chair"):
queryset = Group.objects.filter(type="wg", state__in=("active","proposed"), role__person=self.person, role__name="chair").distinct().order_by('acronym')
queryset = Group.objects.filter(type="wg", state__in=("active", "proposed"), 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__in=("active","proposed"), role__person=self.person, role__name="chair").distinct().order_by('acronym')
queryset = Group.objects.filter(type="rg", state__in=("active", "proposed"), 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
if len(queryset) == 1:
self.fields['group'].initial = queryset[0]
def save(self, *args, **kwargs):
'''Save must handle fields not included in the form: date,number,type_id'''
date = kwargs.pop('date')
group = self.cleaned_data.get('group')
meeting = super(InterimMeetingModelForm, self).save(commit=False)
if not meeting.type_id:
meeting.type_id = 'interim'
if not meeting.number:
meeting.number = get_next_interim_number(group, date)
meeting.date = date
if kwargs.get('commit', True):
# create schedule with meeting
if not meeting.agenda:
meeting.agenda = Schedule.objects.create(
meeting=meeting,
owner=Person.objects.get(name='(System)'))
meeting.save()
return meeting
class InterimSessionModelForm(forms.ModelForm):
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=False)
time_utc = forms.TimeField(required=False)
requested_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)
agenda = forms.CharField(required=False, widget=forms.Textarea)
agenda_note = forms.CharField(max_length=255, required=False)
class Meta:
model = Session
fields = ('date', 'time', 'time_utc', 'requested_duration', 'end_time',
'end_time_utc', 'remote_instructions', 'agenda', 'agenda_note')
def __init__(self, *args, **kwargs):
if 'user' in kwargs:
self.user = kwargs.pop('user')
if 'group' in kwargs:
self.group = kwargs.pop('group')
if 'is_approved' in kwargs:
self.is_approved = kwargs.pop('is_approved')
super(InterimSessionModelForm, self).__init__(*args, **kwargs)
self.is_edit = bool(self.instance.pk)
# setup fields that aren't intrinsic to the Session object
if self.is_edit:
self.initial['date'] = self.instance.official_timeslotassignment().timeslot.time
self.initial['time'] = self.instance.official_timeslotassignment().timeslot.time
if self.instance.agenda():
doc = self.instance.agenda()
path = os.path.join(doc.get_file_path(), doc.filename_with_rev())
self.initial['agenda'] = get_document_content(os.path.basename(path), path, markup=False)
def save(self, *args, **kwargs):
"""NOTE: as the baseform of an inlineformset self.save(commit=True)
never gets called"""
session = super(InterimSessionModelForm, self).save(commit=kwargs.get('commit', True))
if self.is_approved:
session.status_id = 'scheda'
else:
session.status_id = 'apprw'
session.group = self.group
session.type_id = 'session'
if not self.instance.pk:
session.requested_by = self.user.person
return session
def save_agenda(self):
if self.instance.agenda():
doc = self.instance.agenda()
doc.rev = str(int(doc.rev) + 1).zfill(2)
doc.save()
else:
filename = 'agenda-interim-{group}-{date}-{time}'.format(
group=self.group.acronym,
date=self.cleaned_data['date'].strftime("%Y-%m-%d-"),
time=self.cleaned_data['time'].strftime("%H%M"))
doc = Document.objects.create(
type_id='agenda',
group=self.group,
name=filename,
rev='00')
doc.set_state(State.objects.get(type=doc.type, slug='active'))
DocAlias.objects.create(name=doc.name, document=doc)
self.instance.sessionpresentation_set.create(document=doc, rev=doc.rev)
NewRevisionDocEvent.objects.create(
type='new_revision',
by=self.user.person,
doc=doc,
rev=doc.rev,
desc='New revision available')
# write file
path = os.path.join(get_upload_root(self.instance.meeting), 'agenda', doc.filename_with_rev())
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
with open(path, "w") as file:
file.write(self.cleaned_data['agenda'])
class InterimSessionForm(forms.Form):
# unset: date,time,duration
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Date', required=False)
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(required=False)
end_time = forms.TimeField(required=False)
end_time_utc = forms.TimeField(required=False)
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)
city = forms.CharField(max_length=255,required=False)
country = forms.ChoiceField(choices=countries,required=False)
timezone = forms.ChoiceField(choices=timezones)
def __init__(self, *args, **kwargs):
super(InterimSessionForm, self).__init__(*args, **kwargs)
self.fields['timezone'].initial = 'UTC'
def _save_agenda(self, session):
pass
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)
def save(self, request, group, meeting, is_approved):
person = request.user.person
@ -208,7 +314,8 @@ class InterimSessionForm(forms.Form):
status_id = 'scheda'
else:
status_id = 'apprw'
session = Session.objects.create(meeting=meeting,
session = Session.objects.create(
meeting=meeting,
group=group,
requested_by=person,
requested_duration=duration,
@ -216,22 +323,23 @@ class InterimSessionForm(forms.Form):
type_id='session',
remote_instructions=remote_instructions,
agenda_note=agenda_note,)
assign_interim_session(session,time)
assign_interim_session(session, time)
if agenda:
# create objects
filename = 'agenda-interim-%s-%s' % (group.acronym,time.strftime("%Y-%m-%d-%H%M"))
doc = Document.objects.create(type_id='agenda',group=group,name=filename,rev='00')
doc.set_state(State.objects.get(type=doc.type,slug='active'))
filename = 'agenda-interim-%s-%s' % (group.acronym, time.strftime("%Y-%m-%d-%H%M"))
doc = Document.objects.create(type_id='agenda', group=group, name=filename, rev='00')
doc.set_state(State.objects.get(type=doc.type, slug='active'))
DocAlias.objects.create(name=doc.name, document=doc)
session.sessionpresentation_set.create(document=doc,rev=doc.rev)
NewRevisionDocEvent.objects.create(type='new_revision',
session.sessionpresentation_set.create(document=doc, rev=doc.rev)
NewRevisionDocEvent.objects.create(
type='new_revision',
by=request.user.person,
doc=doc,
rev=doc.rev,
desc='New revision available')
# write file
path = os.path.join(get_upload_root(meeting),'agenda',doc.filename_with_rev())
path = os.path.join(get_upload_root(meeting), 'agenda', doc.filename_with_rev())
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
@ -240,19 +348,17 @@ class InterimSessionForm(forms.Form):
return session
class InterimAnnounceForm(forms.ModelForm):
class Meta:
model = Message
fields = ('to','frm','cc','bcc','reply_to','subject','body')
fields = ('to', 'frm', 'cc', 'bcc', 'reply_to', 'subject', 'body')
#def __init__(self):
# super(InterimAnnounceForm, self).__init__(*args,**kwargs)
def save(self, *args, **kwargs):
user = kwargs.pop('user')
message = super(InterimAnnounceForm, self).save(commit=False)
message.by = user.person
message.save()
return message
return message

View file

@ -16,6 +16,7 @@ from django.template.loader import render_to_string
import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.doc.utils import get_document_content
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
@ -290,67 +291,31 @@ def session_constraint_expire(request,session):
# -------------------------------------------------
# Interim Meeting Helpers
# -------------------------------------------------
def get_announcement_initial(meeting):
'''Returns a dictionary suitable to initialize an InterimAnnouncementForm (Message ModelForm)'''
group = meeting.session_set.first().group
in_person = bool(meeting.city)
initial = {}
initial['to'] = settings.INTERIM_ANNOUNCE_TO_EMAIL
initial['cc'] = group.list_email
initial['frm'] = settings.INTERIM_ANNOUNCE_FROM_EMAIL
if in_person:
desc = 'Interim'
def assign_interim_session(form):
"""Helper function to create a timeslot and assign the interim session"""
time = datetime.datetime.combine(
form.cleaned_data['date'],
form.cleaned_data['time'])
session = form.instance
if session.official_timeslotassignment():
slot = session.official_timeslotassignment().timeslot
slot.time = time
slot.save()
else:
desc = 'Virtual'
initial['subject'] = '%s (%s) WG %s Meeting: %s' % (group.name,group.acronym,desc,meeting.date)
body = render_to_string('meeting/interim_announcement.txt', locals())
initial['body'] = body
return initial
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 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(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)
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()
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):
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
@ -361,26 +326,29 @@ def can_approve_interim_request(meeting,user):
if not session:
return False
group = session.group
if group.type.slug == 'wg' and group.parent.role_set.filter(name='ad',person=person):
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):
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):
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):
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')):
if has_role(user, ('Secretariat', 'Area Director', 'WG Chair', 'IRTF Chair', 'RG Chair')):
return True
return False
def can_view_interim_request(meeting,user):
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
@ -395,6 +363,129 @@ def can_view_interim_request(meeting,user):
return True
if has_role(user, 'IRTF Chair') and group.type.slug == 'rg':
return True
if group.role_set.filter(name='chair',person=person):
if group.role_set.filter(name='chair', person=person):
return True
return False
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)
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()
return meeting
def get_announcement_initial(meeting):
'''Returns a dictionary suitable to initialize an InterimAnnouncementForm (Message ModelForm)'''
group = meeting.session_set.first().group
in_person = bool(meeting.city)
initial = {}
initial['to'] = settings.INTERIM_ANNOUNCE_TO_EMAIL
initial['cc'] = group.list_email
initial['frm'] = settings.INTERIM_ANNOUNCE_FROM_EMAIL
if in_person:
desc = 'Interim'
else:
desc = 'Virtual'
initial['subject'] = '%s (%s) WG %s Meeting: %s' % (group.name, group.acronym, desc, meeting.date)
body = render_to_string('meeting/interim_announcement.txt', locals())
initial['body'] = body
return initial
def get_earliest_session_date(formset):
'''Return earliest date from InterimSession Formset'''
return sorted([f.cleaned_data['date'] for f in formset.forms if f.cleaned_data.get('date')])[0]
def get_interim_initial(meeting):
'''Returns a dictionary suitable to initialize a InterimRequestForm'''
initial = {}
initial['group'] = meeting.session_set.first().group
if meeting.city:
initial['in_person'] = True
else:
initial['in_person'] = False
if meeting.session_set.count() > 1:
initial['meeting_type'] = 'multi-day'
else:
initial['meeting_type'] = 'single'
if meeting.session_set.first().status.slug == 'apprw':
initial['approved'] = False
else:
initial['approved'] = True
return initial
def get_interim_session_initial(meeting):
'''Returns a list of dictionaries suitable to initialize a InterimSessionForm'''
initials = []
for session in meeting.session_set.all():
initial = {}
initial['date'] = session.official_timeslotassignment().timeslot.time
initial['time'] = session.official_timeslotassignment().timeslot.time
initial['duration'] = session.requested_duration
initial['remote_instructions'] = session.remote_instructions
initial['agenda_note'] = session.agenda_note
doc = session.agenda()
if doc:
path = os.path.join(doc.get_file_path(), doc.filename_with_rev())
initial['agenda'] = get_document_content(os.path.basename(path), path, markup=False)
initials.append(initial)
return initials
def is_meeting_approved(meeting):
"""Returns True if the meeting is approved"""
if meeting.session_set.first().status.slug == 'apprw':
return False
else:
return True
def get_next_interim_number(group, date):
"""Returns a unique number to use for the next interim meeting for
*group*"""
meetings = Meeting.objects.filter(
number__startswith='interim-{year}-{group}'.format(
year=date.year,
group=group.acronym))
if meetings:
sequences = [int(m.number.split('-')[-1]) for m in meetings]
last_sequence = sorted(sequences)[-1]
else:
last_sequence = 0
return 'interim-{year}-{group}-{sequence}'.format(
year=date.year,
group=group.acronym,
sequence=last_sequence + 1)
def sessions_post_save(forms):
"""Helper function to perform various post save operations on each form of a
InterimSessionModelForm formset"""
for form in forms:
if not form.has_changed():
continue
if ('date' in form.changed_data) or ('time' in form.changed_data):
assign_interim_session(form)
if 'agenda' in form.changed_data:
form.save_agenda()

View file

@ -995,7 +995,7 @@ class Session(models.Model):
return Constraint.objects.filter(target=self.group, meeting=self.meeting).order_by('name__name')
def timeslotassignment_for_agenda(self, schedule):
return self.timeslotassignments.filter(schedule=schedule)[0]
return self.timeslotassignments.filter(schedule=schedule).first()
def official_timeslotassignment(self):
return self.timeslotassignment_for_agenda(self.meeting.agenda)

View file

@ -16,7 +16,16 @@ def make_interim_meeting(group,date,status='sched'):
attendees=10, requested_by=system_person,
requested_duration=20, status_id=status,
scheduled=datetime.datetime.now(),type_id="session")
assign_interim_session(session,time)
#assign_interim_session(session,time)
slot = TimeSlot.objects.create(
meeting=meeting,
type_id="session",
duration=session.requested_duration,
time=time)
SchedTimeSessAssignment.objects.create(
timeslot=slot,
session=session,
schedule=session.meeting.agenda)
return meeting
def make_meeting_test_data():

View file

@ -6,6 +6,7 @@ import urlparse
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from django.contrib.auth.models import User
from django.db import transaction
from pyquery import PyQuery
@ -468,6 +469,46 @@ class InterimTests(TestCase):
self.assertEqual(Group.objects.filter(type__in=('wg','rg'),state='active').count(),
len(q("#id_group option")) -1 ) # -1 for options placeholder
def test_temp(self):
from django.forms.models import modelform_factory, inlineformset_factory
from ietf.meeting.forms import InterimSessionModelForm
from django.utils.functional import curry
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',
'city':'',
'country':'',
'time_zone':'UTC',
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':1,
'session_set-INITIAL_FORMS':0,
'session_set-MIN_NUM_FORMS':0,
'session_set-MAX_NUM_FORMS':1000}
user = User.objects.get(username='secretary')
is_approved = False
meeting = Meeting.objects.order_by('id').last()
SessionFormset = inlineformset_factory(Meeting, Session, form=InterimSessionModelForm, can_delete=False, extra=2)
SessionFormset.form = staticmethod(curry(InterimSessionModelForm, user=user,group=group,is_approved=is_approved))
formset = SessionFormset(instance=meeting, data=data)
#assert False, (formset.management_form)
formset.save()
def test_interim_request_single(self):
make_meeting_test_data()
group = Group.objects.get(acronym='mars')
@ -481,17 +522,19 @@ class InterimTests(TestCase):
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':'UTC',
'form-0-remote_instructions':remote_instructions,
'form-0-agenda':agenda,
'form-0-agenda_note':agenda_note,
'form-TOTAL_FORMS':1,
'form-INITIAL_FORMS':0}
'city':'',
'country':'',
'time_zone':'UTC',
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':1,
'session_set-INITIAL_FORMS':0,
'session_set-MIN_NUM_FORMS':0,
'session_set-MAX_NUM_FORMS':1000}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
@ -524,24 +567,24 @@ class InterimTests(TestCase):
duration = datetime.timedelta(hours=3)
city = 'San Francisco'
country = 'US'
timezone = 'US/Pacific'
time_zone = 'US/Pacific'
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':city,
'form-0-country':country,
'form-0-timezone':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}
'city':city,
'country':country,
'time_zone':time_zone,
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':1,
'session_set-INITIAL_FORMS':0}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
@ -552,7 +595,7 @@ class InterimTests(TestCase):
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year,group.acronym,1))
self.assertEqual(meeting.city,city)
self.assertEqual(meeting.country,country)
self.assertEqual(meeting.time_zone,timezone)
self.assertEqual(meeting.time_zone,time_zone)
session = meeting.session_set.first()
self.assertEqual(session.remote_instructions,remote_instructions)
self.assertEqual(session.agenda_note,agenda_note)
@ -571,33 +614,30 @@ class InterimTests(TestCase):
group = Group.objects.get(acronym='mars')
city = 'San Francisco'
country = 'US'
timezone = 'US/Pacific'
time_zone = 'US/Pacific'
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':'multi-day',
'form-0-date':date.strftime("%Y-%m-%d"),
'form-0-time':time.strftime('%H:%M'),
'form-0-duration':'03:00:00',
'form-0-city':city,
'form-0-country':country,
'form-0-timezone':timezone,
'form-0-remote_instructions':remote_instructions,
'form-0-agenda':agenda,
'form-0-agenda_note':agenda_note,
'form-1-date':date2.strftime("%Y-%m-%d"),
'form-1-time':time.strftime('%H:%M'),
'form-1-duration':'03:00:00',
'form-1-city':city,
'form-1-country':country,
'form-1-timezone':timezone,
'form-1-remote_instructions':remote_instructions,
'form-1-agenda':agenda,
'form-1-agenda_note':agenda_note,
'form-TOTAL_FORMS':2,
'form-INITIAL_FORMS':0}
'city':city,
'country':country,
'time_zone':time_zone,
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-1-date':date2.strftime("%Y-%m-%d"),
'session_set-1-time':time.strftime('%H:%M'),
'session_set-1-requested_duration':'03:00:00',
'session_set-1-remote_instructions':remote_instructions,
'session_set-1-agenda':agenda,
'session_set-1-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':2,
'session_set-INITIAL_FORMS':0}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
@ -608,7 +648,7 @@ class InterimTests(TestCase):
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year,group.acronym,1))
self.assertEqual(meeting.city,city)
self.assertEqual(meeting.country,country)
self.assertEqual(meeting.time_zone,timezone)
self.assertEqual(meeting.time_zone,time_zone)
self.assertEqual(meeting.session_set.count(),2)
# first sesstion
session = meeting.session_set.all()[0]
@ -625,6 +665,81 @@ class InterimTests(TestCase):
self.assertEqual(timeslot.duration,duration)
self.assertEqual(session.agenda_note,agenda_note)
def test_interim_request_series(self):
make_meeting_test_data()
meeting_count_before = Meeting.objects.filter(type='interim').count()
date = datetime.date.today() + datetime.timedelta(days=30)
date2 = date + datetime.timedelta(days=1)
time = datetime.datetime.now().time().replace(microsecond=0,second=0)
dt = datetime.datetime.combine(date, time)
dt2 = datetime.datetime.combine(date2, time)
duration = datetime.timedelta(hours=3)
group = Group.objects.get(acronym='mars')
city = ''
country = ''
time_zone = 'US/Pacific'
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':'series',
'city':city,
'country':country,
'time_zone':time_zone,
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-1-date':date2.strftime("%Y-%m-%d"),
'session_set-1-time':time.strftime('%H:%M'),
'session_set-1-requested_duration':'03:00:00',
'session_set-1-remote_instructions':remote_instructions,
'session_set-1-agenda':agenda,
'session_set-1-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':2,
'session_set-INITIAL_FORMS':0}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming'))
meeting_count_after = Meeting.objects.filter(type='interim').count()
self.assertEqual(meeting_count_after,meeting_count_before + 2)
meetings = Meeting.objects.order_by('-id')[:2]
# first meeting
meeting = meetings[1]
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,city)
self.assertEqual(meeting.country,country)
self.assertEqual(meeting.time_zone,time_zone)
self.assertEqual(meeting.session_set.count(),1)
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)
self.assertEqual(session.agenda_note,agenda_note)
# second meeting
meeting = meetings[0]
self.assertEqual(meeting.type_id,'interim')
self.assertEqual(meeting.date,date2)
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year,group.acronym,2))
self.assertEqual(meeting.city,city)
self.assertEqual(meeting.country,country)
self.assertEqual(meeting.time_zone,time_zone)
self.assertEqual(meeting.session_set.count(),1)
session = meeting.session_set.first()
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')
@ -715,6 +830,14 @@ class InterimTests(TestCase):
for session in meeting.session_set.all():
self.assertEqual(session.status_id,'canceledpa')
def test_interim_request_edit(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_edit',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()

View file

@ -72,6 +72,7 @@ urlpatterns = [
url(r'^interim/announce/(?P<number>[A-Za-z0-9._+-]+)/$', views.interim_send_announcement),
url(r'^interim/request/$', views.interim_request),
url(r'^interim/request/(?P<number>[A-Za-z0-9._+-]+)/$', views.interim_request_details),
url(r'^interim/request/(?P<number>[A-Za-z0-9._+-]+)/edit/$', views.interim_request_edit),
url(r'^interim/pending/$', views.interim_pending),
url(r'^$', views.current_materials),
]

View file

@ -20,8 +20,9 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Min, Max
from django.conf import settings
from django.forms.models import modelform_factory
from django.forms.models import modelform_factory, inlineformset_factory
from django.forms import ModelForm, formset_factory
from django.utils.functional import curry
from django.views.decorators.csrf import ensure_csrf_cookie
from ietf.doc.models import Document, State
@ -36,15 +37,16 @@ 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_earliest_session
from ietf.meeting.helpers import create_interim_meeting_from_forms
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
from ietf.meeting.helpers import get_interim_initial, get_interim_session_initial
from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
from ietf.utils.mail import send_mail_message
from ietf.utils.pipe import pipe
from ietf.utils.pdf import pdf_pages
from .forms import InterimRequestForm, InterimSessionForm, InterimAnnounceForm
from .forms import InterimMeetingModelForm, InterimSessionForm, InterimAnnounceForm, InterimSessionModelForm
def get_menu_entries(request):
@ -901,45 +903,49 @@ def session_details(request, num, acronym ):
# Interim Views
# -------------------------------------------------
def ajax_get_utc(request):
'''Ajax view that takes arguments time and timezone and returns UTC'''
time = request.GET.get('time')
timezone = request.GET.get('timezone')
hour,minute = time.split(':')
dt = datetime.datetime(2016,1,1,int(hour),int(minute))
hour, minute = time.split(':')
dt = datetime.datetime(2016, 1, 1, int(hour), int(minute))
tz = pytz.timezone(timezone)
aware_dt = tz.localize(dt, is_dst=None)
utc_dt = aware_dt.astimezone(pytz.utc)
utc = utc_dt.strftime('%H:%M')
context_data = {'timezone':timezone,'time':time,'utc':utc}
return HttpResponse(json.dumps(context_data),content_type='application/json')
context_data = {'timezone': timezone, 'time': time, 'utc': utc}
return HttpResponse(json.dumps(context_data),
content_type='application/json')
@role_required('Secretariat',)
def interim_announce(request):
'''View which shows interim meeting requests awaiting announcement'''
meetings = Meeting.objects.filter(type='interim',session__status='scheda')
meetings = Meeting.objects.filter(type='interim', session__status='scheda')
menu_entries = get_menu_entries(request)
selected_menu_entry = 'announce'
return render(request, "meeting/interim_announce.html", {
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
'meetings' :meetings})
'meetings': meetings})
@role_required('Secretariat',)
def interim_send_announcement(request,number):
def interim_send_announcement(request, number):
'''View for sending the announcement of a new interim meeting'''
meeting = get_object_or_404(Meeting,number=number)
meeting = get_object_or_404(Meeting, number=number)
group = meeting.session_set.first().group
if request.method == 'POST':
form = InterimAnnounceForm(request.POST, initial=get_announcement_initial(meeting))
form = InterimAnnounceForm(request.POST,
initial=get_announcement_initial(meeting))
if form.is_valid():
message = form.save(user=request.user)
message.related_groups.add(group)
meeting.session_set.update(status_id='sched')
send_mail_message(request,message)
send_mail_message(request, message)
messages.success(request, 'Interim meeting announcement sent')
return redirect(interim_announce)
@ -947,91 +953,178 @@ def interim_send_announcement(request,number):
return render(request, "meeting/interim_send_announcement.html", {
'meeting': meeting,
'form': form})
'form': form})
@role_required('Area Director','Secretariat','IRTF Chair','WG Chair','RG Chair')
@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 = Meeting.objects.filter(type='interim', session__status='apprw')
menu_entries = get_menu_entries(request)
selected_menu_entry = 'pending'
meetings = [ m for m in meetings if can_view_interim_request(m,request.user)]
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):
if can_approve_interim_request(meeting, request.user):
meeting.can_approve = True
return render(request, "meeting/interim_pending.html", {
'menu_entries': menu_entries,
'selected_menu_entry':selected_menu_entry,
'selected_menu_entry': selected_menu_entry,
'meetings': meetings})
@role_required('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair')
@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)
SessionFormset = inlineformset_factory(
Meeting,
Session,
form=InterimSessionModelForm,
can_delete=False, extra=2)
if request.method == 'POST':
form = InterimRequestForm(request, data=request.POST)
formset = SessionFormset(data=request.POST)
#person = request.user.person
form = InterimMeetingModelForm(request, data=request.POST)
formset = SessionFormset(instance=Meeting(), data=request.POST)
if form.is_valid() and formset.is_valid():
group = form.cleaned_data.get('group')
is_approved = form.cleaned_data.get('approved', False)
meeting_type = form.cleaned_data.get('meeting_type')
# pre create meeting
if meeting_type in ('single','multi-day'):
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_from_forms(form,f)
f.save(request,group,meeting,is_approved)
messages.success(request,'Interim meeting request submitted')
if meeting_type in ('single', 'multi-day'):
meeting = form.save(date=get_earliest_session_date(formset))
# need to use curry here to pass custom variable to form init
SessionFormset.form = staticmethod(curry(
InterimSessionModelForm,
user=request.user,
group=group,
is_approved=is_approved))
formset = SessionFormset(instance=meeting, data=request.POST)
formset.is_valid()
formset.save()
# post save
sessions_post_save(formset)
# series require special handling, each session gets it's own
# meeting object we won't see this on edit because series are
# subsequently dealt with individually
elif meeting_type == 'series':
SessionFormset.form = staticmethod(curry(
InterimSessionModelForm,
user=request.user,
group=group,
is_approved=is_approved))
formset = SessionFormset(instance=Meeting(), data=request.POST)
formset.is_valid() # re-validate
for session_form in formset.forms:
if not session_form.has_changed():
continue
# create meeting
form = InterimMeetingModelForm(request, data=request.POST)
form.is_valid()
meeting = form.save(date=session_form.cleaned_data['date'])
# create save session
session = session_form.save(commit=False)
session.meeting = meeting
session.save()
# post save
sessions_post_save([session_form])
messages.success(request, 'Interim meeting request submitted')
return redirect(upcoming)
else:
assert False, (form.errors, formset.errors)
else:
form = InterimRequestForm(request=request,initial={'meeting_type':'single','timezone':'UTC'})
form = InterimMeetingModelForm(request=request,
initial={'meeting_type': 'single'})
formset = SessionFormset()
return render(request, "meeting/interim_request.html", {"form":form, "formset":formset})
return render(request, "meeting/interim_request.html", {
"form": form,
"formset": formset})
@role_required('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair')
@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)
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)
can_edit = can_view_interim_request(meeting, request.user)
can_approve = can_approve_interim_request(meeting, request.user)
if request.method == 'POST':
if request.POST.get('approve'):
meeting.session_set.update(status_id='scheda')
messages.success(request,'Interim meeting approved')
messages.success(request, 'Interim meeting approved')
if has_role(request.user, 'Secretariat'):
return redirect(interim_send_announcement, number=number)
if request.POST.get('disapprove'):
meeting.session_set.update(status_id='disappr')
messages.success(request,'Interim meeting disapproved')
messages.success(request, 'Interim meeting disapproved')
if request.POST.get('cancel'):
if meeting.session_set.first().status.slug == 'sched':
meeting.session_set.update(status_id='canceled')
else:
meeting.session_set.update(status_id='canceledpa')
messages.success(request,'Interim meeting cancelled')
messages.success(request, 'Interim meeting cancelled')
return render(request, "meeting/interim_request_details.html", {
"meeting": meeting,
"sessions": sessions,
"can_edit": can_edit,
"can_approve": can_approve})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair',
'RG Chair')
def interim_request_edit(request, number):
'''Edit details of an interim meeting reqeust'''
meeting = get_object_or_404(Meeting, number=number)
SessionFormset = inlineformset_factory(
Meeting,
Session,
form=InterimSessionModelForm,
can_delete=False,
extra=1)
if request.method == 'POST':
form = InterimMeetingModelForm(request=request, instance=meeting,
data=request.POST)
group = Group.objects.get(pk=form.data['group'])
is_approved = is_meeting_approved(meeting)
SessionFormset.form = staticmethod(curry(
InterimSessionModelForm,
user=request.user,
group=group,
is_approved=is_approved))
formset = SessionFormset(instance=meeting,
data=request.POST)
if form.is_valid() and formset.is_valid():
meeting = form.save(date=get_earliest_session_date(formset))
formset.save()
sessions_post_save(formset)
messages.success(request, 'Interim meeting request saved')
return redirect(interim_request_details, number=number)
else:
assert False, (form.errors, formset.errors)
else:
form = InterimMeetingModelForm(request=request, instance=meeting)
formset = SessionFormset(instance=meeting)
return render(request, "meeting/interim_request_edit.html", {
"meeting": meeting,
"form": form,
"formset": formset})
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'''
@ -1042,14 +1135,18 @@ def ical_upcoming(request):
"meetings": meetings,
}, content_type="text/calendar")
def upcoming(request):
'''List of upcoming meetings'''
today = datetime.datetime.today()
meetings = Meeting.objects.filter(date__gte=today,session__status__in=('sched','canceled')).order_by('date')
meetings = Meeting.objects.filter(
date__gte=today,
session__status__in=('sched', 'canceled')).order_by('date')
# extract groups hierarchy for display filter
seen = set()
groups = [ m.session_set.first().group for m in meetings.filter(type='interim') ]
groups = [m.session_set.first().group for m
in meetings.filter(type='interim')]
group_parents = []
for g in groups:
if g.parent.acronym not in seen:
@ -1069,16 +1166,16 @@ def upcoming(request):
# add menu entries
menu_entries = get_menu_entries(request)
selected_menu_entry = 'upcoming'
# add menu actions
actions = []
if can_request_interim_meeting(request.user):
actions.append(("Request new interim meeting", reverse("ietf.meeting.views.interim_request")))
return render(request, "meeting/upcoming.html",
{ 'meetings':meetings,
'menu_actions': actions,
'menu_entries': menu_entries,
'selected_menu_entry':selected_menu_entry,
'group_parents':group_parents,
})
actions.append(("Request new interim meeting",
reverse("ietf.meeting.views.interim_request")))
return render(request, "meeting/upcoming.html", {
'meetings': meetings,
'menu_actions': actions,
'menu_entries': menu_entries,
'selected_menu_entry': selected_menu_entry,
'group_parents': group_parents})

View file

@ -6,32 +6,32 @@ var interimRequest = {
interimRequest.form = $(this);
interimRequest.addButton = $('#add_session');
interimRequest.inPerson = $('#id_in_person');
interimRequest.timezone = $('#id_time_zone');
// bind functions
$('.select2-field').select2();
interimRequest.addButton.click(interimRequest.addSession);
$('.btn-delete').click(interimRequest.deleteSession);
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$="-requested_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);
interimRequest.timezone.change(interimRequest.timezoneChange);
// init
interimRequest.inPerson.each(interimRequest.toggleLocation);
interimRequest.checkAddButton();
interimRequest.checkHelpText();
//interimRequest.showRequired();
$('input[name$="-time"]').each(interimRequest.calculateEndTime);
$('input[name$="-time"]').each(interimRequest.updateInfo);
//interimRequest.form.submit(interimRequest.onSubmit);
//$('input[name$="-time"]').blur(interimRequest.setUTC);
},
addSession : function() {
//var templateData = interimRequest.sessionTemplate.clone();
var template = interimRequest.form.find('.fieldset.template');
var el = template.clone(true);
var totalField = $('#id_form-TOTAL_FORMS');
var totalField = $('#id_session_set-TOTAL_FORMS');
var total = +totalField.val();
var meeting_type = $('input[name="meeting_type"]:checked').val();
@ -59,15 +59,8 @@ var interimRequest = {
// 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);
}
$('.btn-delete').removeClass("hidden");
},
@ -81,13 +74,14 @@ var interimRequest = {
},
updateInfo : function() {
//var url = liaisonForm.form.data("ajaxInfoUrl");
//alert('called update');
// makes ajax call to server and sets UTC field
var time = $(this).val();
if(!time){
return;
}
var url = "/meeting/ajax/get-utc";
var fieldset = $(this).parents(".fieldset");
var time = $(this).val();
var timezone_field = fieldset.find('[name$="timezone"]');
var timezone = timezone_field.val();
var timezone = interimRequest.timezone.val();
var name = $(this).attr("id") + "_utc";
var utc = fieldset.find("#" + name);
$.ajax({
@ -113,7 +107,7 @@ var interimRequest = {
var fieldset = $(this).parents(".fieldset");
var start_time = fieldset.find("input[name$='-time']");
var end_time = fieldset.find("input[name$='-end_time']");
var duration = fieldset.find("input[name$='-duration']");
var duration = fieldset.find("input[name$='-requested_duration']");
if(!start_time.val() || !duration.val()){
return;
}
@ -125,8 +119,6 @@ var interimRequest = {
end_time.val(interimRequest.get_formatted_time(d2));
end_time.trigger('change');
//interimRequest.updateInfo(end_time);
//alert( d2 );
//alert( end_time.attr("id") );
},
checkAddButton : function() {
@ -209,22 +201,11 @@ var interimRequest = {
var utc = fieldset.find("#" + name);
var d = new Date(2000,1,1,values[0],values[1]);
utc.val(interimRequest.get_formatted_utc_time(d) + " UTC");
//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");
var start_time = fieldset.find("input[name$='-time']");
var end_time = fieldset.find("input[name$='-end_time']");
start_time.trigger('blur');
end_time.trigger('change');
$("input[name$='-time']").trigger('blur');
$("input[name$='-end_time']").trigger('change');
},
toggleLocation : function() {

View file

@ -61,30 +61,31 @@
</div>
</div>
{{ formset.management_form }}
{% for form in formset %}
<div class="fieldset{% if forloop.last %} template{% endif %}" >
<div class="form-group">
<label for="id_form-{{ forloop.counter0 }}-city" class="col-md-2 control-label">Location</label>
<label for="id_session_set-{{ forloop.counter0 }}-city" class="col-md-2 control-label">Location</label>
<div class="col-md-10 form-inline">
{% render_field form.city class="form-control location" placeholder="City" %}
{% render_field form.country class="form-control location" placeholder="Country" %}
{% render_field form.timezone class="form-control" %}
{% render_field form.time_zone class="form-control" %}
</div>
</div>
{{ formset.management_form }}
{% for form in formset %}
<div class="fieldset{% if forloop.last %} template{% endif %}" >
<div class="form-group">
<label for="id_form-{{ forloop.counter0 }}-date" class="col-md-2 control-label required">Date</label>
<label for="id_session_set-{{ forloop.counter0 }}-date" class="col-md-2 control-label required">Date</label>
<div class="col-md-2">{% render_field form.date class="form-control" %}</div>
</div>
<div class="form-group">
<label for="id_form-{{ forloop.counter0 }}-time" class="col-md-2 control-label required">Start Time</label>
<label for="id_session_set-{{ forloop.counter0 }}-time" class="col-md-2 control-label required">Start Time</label>
<div class="col-md-3 form-inline">
{% 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" %}
</div>
<label for="id_form-{{ forloop.counter0 }}-end_time" class="col-md-2 control-label">End Time</label>
<label for="id_session_set-{{ forloop.counter0 }}-end_time" class="col-md-2 control-label">End Time</label>
<div class="col-md-4 form-inline">
{% 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" %}
@ -92,25 +93,25 @@
</div>
<div class="form-group">
<label for="id_duration" class="col-md-2 control-label required">Duration</label>
<div class="col-md-2">{% render_field form.duration class="form-control time-field" placeholder="HH:MM" %}</div>
<label for="id_session_set-{{ forloop.counter0 }}-requested_duration" class="col-md-2 control-label required">Duration</label>
<div class="col-md-2">{% render_field form.requested_duration class="form-control time-field" placeholder="HH:MM" %}</div>
</div>
<div class="form-group">
<label for="id_remote_instructions" class="col-md-2 control-label">Remote Instructions</label>
<label for="id_session_set-{{ forloop.counter0 }}-remote_instructions" class="col-md-2 control-label">Remote Instructions</label>
<div class="col-md-10">{% render_field form.remote_instructions class="form-control" placeholder="ie. Webex address" %}</div>
</div>
<div class="form-group">
<label for="agenda" class="col-md-2 control-label">Agenda</label>
<label for="id_session_set-{{ forloop.counter0 }}-agenda" class="col-md-2 control-label">Agenda</label>
<div class="col-md-10">{% render_field form.agenda class="form-control" rows="6" placeholder="paste agenda here" %}</div>
</div>
<div class="form-group">
<label for="agenda_note" class="col-md-2 control-label">Agenda Note</label>
<label for="id_session_set-{{ forloop.counter0 }}-agenda_note" class="col-md-2 control-label">Agenda Note</label>
<div class="col-md-10">{% render_field form.agenda_note class="form-control" placeholder="Note" %}</div>
</div>
<button name="id_form-{{ forloop.counter0 }}-delete-button" type="button" class="btn btn-default hidden btn-delete">Delete</button>
<button name="id_session_set-{{ forloop.counter0 }}-delete-button" type="button" class="btn btn-default hidden btn-delete">Delete</button>
</div> <!-- fieldset -->
{% endfor %}

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load staticfiles bootstrap3 widget_tweaks %}
{% load staticfiles bootstrap3 widget_tweaks ietf_filters %}
{% block title %}Interim Request Details{% endblock %}
@ -32,7 +32,7 @@
<dt>Start Time</dt>
<dd>{{ session.official_timeslotassignment.timeslot.time|date:"H:i" }}
<dt>Duration</dt>
<dd>{{ session.requested_duration }}
<dd>{{ session.requested_duration|format_timedelta }}
<dt>Remote Instructions</dt>
<dd>{{ session.remote_instructions }}
<dt>Agenda Note</dt>
@ -44,7 +44,7 @@
{% csrf_token %}
{% if can_edit %}
{% if sessions.0.status.slug == 'apprw' or sessions.0.status.slug == 'scheda' %}
<a class="btn btn-default" href="">Edit</a>
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_edit' number=meeting.number %}">Edit</a>
{% endif %}
{% endif %}
{% if can_approve and sessions.0.status.slug == 'apprw' %}

View file

@ -0,0 +1,129 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load staticfiles bootstrap3 widget_tweaks %}
{% block title %}Edit Interim Request{% endblock %}
{% block pagehead %}
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
{% endblock %}
{% block content %}
{% origin %}
<h1>Edit Interim Meeting Request</h1>
<form id="interim-request-form" role="form" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_field form.group layout='horizontal' %}
<input type="hidden" name="group" value="{{ form.group.value }}">
<div class="form-group form-inline">
<div class="col-md-offset-2">
<div class="col-md-2">
<label class="checkbox-inline">{% render_field form.in_person %}<strong>In Person</strong></label>
</div>
<div class="col-md-2">
<label class="checkbox-inline">{% render_field form.approved %}<strong>Preapproved by AD</strong></label>
</div>
<!--
<div class="col-md-2 radio-inline"><strong>Meeting Type:</strong></div>
<label class="radio-inline">
<input type="radio" value="single" checked="checked" name="meeting_type">Single
</label>
<label class="radio-inline">
<input type="radio" value="multi-day" name="meeting_type">Multi-Day
</label>
<label class="radio-inline">
<input type="radio" value="series" name="meeting_type">Series
</label>
-->
</div> <!-- col-md-offset-2 -->
</div> <!-- form-group form-inline -->
<div class="form-group">
<label for="id_form-{{ forloop.counter0 }}-city" class="col-md-2 control-label">Location</label>
<div class="col-md-10 form-inline">
{% render_field form.city class="form-control location" placeholder="City" %}
{% render_field form.country class="form-control location" placeholder="Country" %}
{% render_field form.time_zone class="form-control" %}
</div>
</div>
{{ formset.management_form }}
{% for form in formset %}
<div class="fieldset{% if forloop.last %} template{% endif %}" >
<input id="id_session_set-{{ forloop.counter0 }}-id" name="session_set-{{ forloop.counter0 }}-id" type="hidden" value="{{ form.instance.pk|default_if_none:"" }}">
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-date" class="col-md-2 control-label required">Date</label>
<div class="col-md-2">{% render_field form.date class="form-control" %}</div>
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-time" class="col-md-2 control-label required">Start Time</label>
<div class="col-md-3 form-inline">
{% 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" %}
</div>
<label for="id_session_set-{{ forloop.counter0 }}-end_time" class="col-md-2 control-label">End Time</label>
<div class="col-md-4 form-inline">
{% 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" %}
</div>
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-requested_duration" class="col-md-2 control-label required">Duration</label>
<div class="col-md-2">{% render_field form.requested_duration class="form-control time-field" placeholder="HH:MM" %}</div>
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-remote_instructions" class="col-md-2 control-label">Remote Instructions</label>
<div class="col-md-10">{% render_field form.remote_instructions class="form-control" placeholder="ie. Webex address" %}</div>
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-agenda" class="col-md-2 control-label">Agenda</label>
<div class="col-md-10">{% render_field form.agenda class="form-control" rows="6" placeholder="paste agenda here" %}</div>
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-agenda_note" class="col-md-2 control-label">Agenda Note</label>
<div class="col-md-10">{% render_field form.agenda_note class="form-control" placeholder="Note" %}</div>
</div>
<button name="id_session_set-{{ forloop.counter0 }}-delete-button" type="button" class="btn btn-default hidden btn-delete">Delete</button>
</div> <!-- fieldset -->
{% endfor %}
<div class="form-group">
<div class="col-md-10">
<button id="add_session" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span>Add Session</button>
</div>
</div>
<div class="form-group"
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url 'ietf.meeting.views.upcoming' %}">Back</a>
{% endbuttons %}
</div>
</form>
{% endblock %}
{% block js %}
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'select2/select2.min.js' %}"></script>
<script src="{% static 'ietf/js/meeting-interim-request.js' %}"></script>
{% endblock %}