From 5318081608050a639f756e790b7d1fb33633bced Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Thu, 14 Oct 2021 13:21:56 +0000 Subject: [PATCH] Allow non-WG-like groups to request additional sessions/durations and bypass approval - Legacy-Id: 19424 --- ietf/meeting/forms.py | 73 ++++++++++----- ietf/secr/sreq/forms.py | 7 +- ietf/secr/sreq/views.py | 90 +++++-------------- .../includes/sessions_request_form.html | 9 +- .../includes/sessions_request_view.html | 2 +- ietf/secr/templates/sreq/confirm.html | 2 +- 6 files changed, 83 insertions(+), 100 deletions(-) diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py index e63256e679..68bd8f8530 100644 --- a/ietf/meeting/forms.py +++ b/ietf/meeting/forms.py @@ -531,27 +531,49 @@ def _day_choices(days): class DurationChoiceField(forms.ChoiceField): - duration_choices = (('3600', '60 minutes'), ('7200', '120 minutes')) - - # todo expand range of choices and make configurable - def __init__(self, *args, **kwargs): + def __init__(self, durations=None, *args, **kwargs): + if durations is None: + durations = (3600, 7200) super().__init__( - choices=(('','--Please select'), *self.duration_choices), + choices=self._make_choices(durations), *args, **kwargs, ) def prepare_value(self, value): """Converts incoming value into string used for the option value""" if value: - return str(int(value.total_seconds())) if hasattr(value, 'total_seconds') else str(value) + return str(int(value.total_seconds())) if isinstance(value, datetime.timedelta) else str(value) return '' - def clean(self, value): - """Convert option value string back to a timedelta""" - val = super().clean(value) - if val == '': - return None - return datetime.timedelta(seconds=int(val)) + def to_python(self, value): + return datetime.timedelta(seconds=round(float(value))) if value not in self.empty_values else None + + def valid_value(self, value): + return super().valid_value(self.prepare_value(value)) + + def _format_duration_choice(self, dur): + seconds = int(dur.total_seconds()) if isinstance(dur, datetime.timedelta) else int(dur) + hours = int(seconds / 3600) + minutes = round((seconds - 3600 * hours) / 60) + hr_str = '{} hour{}'.format(hours, '' if hours == 1 else 's') + min_str = '{} minute{}'.format(minutes, '' if minutes == 1 else 's') + if hours > 0 and minutes > 0: + time_str = ' '.join((hr_str, min_str)) + elif hours > 0: + time_str = hr_str + else: + time_str = min_str + return (str(seconds), time_str) + + def _make_choices(self, durations): + return ( + ('','--Please select'), + *[self._format_duration_choice(dur) for dur in durations]) + + def _set_durations(self, durations): + self.choices = self._make_choices(durations) + + durations = property(None, _set_durations) class SessionDetailsForm(forms.ModelForm): @@ -573,6 +595,8 @@ def __init__(self, group, *args, **kwargs): }), }) self.fields['purpose'].queryset = SessionPurposeName.objects.filter(pk__in=session_purposes) + if not group.features.acts_like_wg: + self.fields['requested_duration'].durations = [datetime.timedelta(minutes=m) for m in range(30, 241, 30)] class Meta: model = Session @@ -606,7 +630,7 @@ def __init__(self, group, meeting, queryset=None, *args, **kwargs): def save_new(self, form, commit=True): form.instance.meeting = self._meeting - super().save_new(form, commit) + return super().save_new(form, commit) def save(self, commit=True): existing_instances = set(form.instance for form in self.forms if form.instance.pk) @@ -619,14 +643,15 @@ def forms_to_keep(self): """Get the not-deleted forms""" return [f for f in self.forms if f not in self.deleted_forms] -SessionDetailsFormSet = forms.inlineformset_factory( - Group, - Session, - formset=SessionDetailsInlineFormset, - form=SessionDetailsForm, - can_delete=True, - can_order=False, - min_num=1, - max_num=3, - extra=3, -) \ No newline at end of file +def sessiondetailsformset_factory(min_num=1, max_num=3): + return forms.inlineformset_factory( + Group, + Session, + formset=SessionDetailsInlineFormset, + form=SessionDetailsForm, + can_delete=True, + can_order=False, + min_num=min_num, + max_num=max_num, + extra=max_num, # only creates up to max_num total + ) \ No newline at end of file diff --git a/ietf/secr/sreq/forms.py b/ietf/secr/sreq/forms.py index a0b25c4e12..f64e1320cd 100644 --- a/ietf/secr/sreq/forms.py +++ b/ietf/secr/sreq/forms.py @@ -8,7 +8,7 @@ from ietf.name.models import TimerangeName, ConstraintName from ietf.group.models import Group -from ietf.meeting.forms import SessionDetailsFormSet +from ietf.meeting.forms import sessiondetailsformset_factory from ietf.meeting.models import ResourceAssociation, Constraint from ietf.person.fields import SearchablePersonsField from ietf.utils.html import clean_text_field @@ -90,9 +90,12 @@ def __init__(self, group, meeting, data=None, *args, **kwargs): self.hidden = False self.group = group - self.session_forms = SessionDetailsFormSet(group=self.group, meeting=meeting, data=data) + formset_class = sessiondetailsformset_factory(max_num=3 if group.features.acts_like_wg else 12) + self.session_forms = formset_class(group=self.group, meeting=meeting, data=data) super(SessionForm, self).__init__(data=data, *args, **kwargs) + if not self.group.features.acts_like_wg: + self.fields['num_session'].choices = ((n, str(n)) for n in range(1, 13)) self.fields['comments'].widget = forms.Textarea(attrs={'rows':'3','cols':'65'}) other_groups = list(allowed_conflicting_groups().exclude(pk=group.pk).values_list('acronym', 'acronym').order_by('acronym')) diff --git a/ietf/secr/sreq/views.py b/ietf/secr/sreq/views.py index 0c912b73f1..f3a806acd7 100644 --- a/ietf/secr/sreq/views.py +++ b/ietf/secr/sreq/views.py @@ -61,7 +61,7 @@ def get_initial_session(sessions, prune_conflicts=False): conflicts = constraints.filter(name__is_group_conflict=True) # only the group conflict constraints # even if there are three sessions requested, the old form has 2 in this field - initial['num_session'] = min(sessions.count(), 2) + initial['num_session'] = min(sessions.count(), 2) if group.features.acts_like_wg else sessions.count() initial['attendees'] = sessions[0].attendees def valid_conflict(conflict): @@ -259,6 +259,13 @@ def cancel(request, acronym): messages.success(request, 'The %s Session Request has been cancelled' % group.acronym) return redirect('ietf.secr.sreq.views.main') + +def status_slug_for_new_session(session, session_number): + if session.group.features.acts_like_wg and session_number == 2: + return 'apprw' + return 'schedw' + + @role_required(*AUTHORIZED_ROLES) def confirm(request, acronym): ''' @@ -311,7 +318,6 @@ def confirm(request, acronym): # Create new session records # Should really use sess_form.save(), but needs data from the main form as well. Need to sort that out properly. for count, sess_form in enumerate(form.session_forms[:num_sessions]): - slug = 'apprw' if count == 3 else 'schedw' new_session = Session.objects.create( meeting=meeting, group=group, @@ -324,7 +330,7 @@ def confirm(request, acronym): ) SchedulingEvent.objects.create( session=new_session, - status=SessionStatusName.objects.get(slug=slug), + status=SessionStatusName.objects.get(slug=status_slug_for_new_session(new_session, count)), by=login, ) if 'resources' in form.data: @@ -412,7 +418,6 @@ def edit(request, acronym, num=None): ).filter( Q(current_status__isnull=True) | ~Q(current_status__in=['canceled', 'notmeet', 'deleted']) ).order_by('id') - sessions_count = sessions.count() initial = get_initial_session(sessions) FormClass = get_session_form_class() @@ -442,68 +447,16 @@ def edit(request, acronym, num=None): form = FormClass(group, meeting, request.POST, initial=initial) if form.is_valid(): if form.has_changed(): - form.session_forms.save() # todo maintain event creation in commented-out old code!! - # might be cleaner to simply delete and rewrite all records (but maintain submitter?) - # adjust duration or add sessions - # session 1 - # if 'length_session1' in form.changed_data: - # session = sessions[0] - # session.requested_duration = datetime.timedelta(0,int(form.cleaned_data['length_session1'])) - # session.save() - # session_changed(session) - # - # # session 2 - # if 'length_session2' in form.changed_data: - # length_session2 = form.cleaned_data['length_session2'] - # if length_session2 == '': - # sessions[1].delete() - # elif sessions_count < 2: - # duration = datetime.timedelta(0,int(form.cleaned_data['length_session2'])) - # new_session = Session.objects.create( - # meeting=meeting, - # group=group, - # attendees=form.cleaned_data['attendees'], - # requested_duration=duration, - # comments=form.cleaned_data['comments'], - # type_id='regular', - # ) - # SchedulingEvent.objects.create( - # session=new_session, - # status=SessionStatusName.objects.get(slug='schedw'), - # by=request.user.person, - # ) - # else: - # duration = datetime.timedelta(0,int(form.cleaned_data['length_session2'])) - # session = sessions[1] - # session.requested_duration = duration - # session.save() - # - # # session 3 - # if 'length_session3' in form.changed_data: - # length_session3 = form.cleaned_data['length_session3'] - # if length_session3 == '': - # sessions[2].delete() - # elif sessions_count < 3: - # duration = datetime.timedelta(0,int(form.cleaned_data['length_session3'])) - # new_session = Session.objects.create( - # meeting=meeting, - # group=group, - # attendees=form.cleaned_data['attendees'], - # requested_duration=duration, - # comments=form.cleaned_data['comments'], - # type_id='regular', - # ) - # SchedulingEvent.objects.create( - # session=new_session, - # status=SessionStatusName.objects.get(slug='apprw'), - # by=request.user.person, - # ) - # else: - # duration = datetime.timedelta(0,int(form.cleaned_data['length_session3'])) - # session = sessions[2] - # session.requested_duration = duration - # session.save() - # session_changed(session) + changed_session_forms = [sf for sf in form.session_forms.forms_to_keep if sf.has_changed()] + form.session_forms.save() + for n, new_session in enumerate(form.session_forms.created_instances): + SchedulingEvent.objects.create( + session=new_session, + status_id=status_slug_for_new_session(new_session, n), + by=request.user.person, + ) + for sf in changed_session_forms: + session_changed(sf.instance) # New sessions may have been created, refresh the sessions list sessions = add_event_info_to_session_qs( @@ -528,7 +481,8 @@ def edit(request, acronym, num=None): session_changed(sessions[current_joint_for_session_idx]) sessions[new_joint_for_session_idx].joint_with_groups.set(new_joint_with_groups) session_changed(sessions[new_joint_for_session_idx]) - + + # Update sessions to match changes to shared form fields if 'attendees' in form.changed_data: sessions.update(attendees=form.cleaned_data['attendees']) if 'comments' in form.changed_data: @@ -660,7 +614,7 @@ def main(request): # add session status messages for use in template for group in scheduled_groups: - if len(group.meeting_sessions) < 3: + if not group.features.acts_like_wg or (len(group.meeting_sessions) < 3): group.status_message = group.meeting_sessions[0].current_status else: group.status_message = 'First two sessions: %s, Third session: %s' % (group.meeting_sessions[0].current_status, group.meeting_sessions[2].current_status) diff --git a/ietf/secr/templates/includes/sessions_request_form.html b/ietf/secr/templates/includes/sessions_request_form.html index 256c91670a..9ed17a84cf 100755 --- a/ietf/secr/templates/includes/sessions_request_form.html +++ b/ietf/secr/templates/includes/sessions_request_form.html @@ -7,12 +7,11 @@ Working Group Name:{{ group.name }} ({{ group.acronym }}) Area Name:{% if group.parent %}{{ group.parent.name }} ({{ group.parent.acronym }}){% endif %} Number of Sessions:*{{ form.num_session.errors }}{{ form.num_session }} - Session 1:*{% include 'meeting/session_details_form.html' with form=form.session_forms.0 only %} + {% if group.features.acts_like_wg %}Session 1:*{% include 'meeting/session_details_form.html' with form=form.session_forms.0 only %} Session 2:*{% include 'meeting/session_details_form.html' with form=form.session_forms.1 only %} {% if not is_virtual %} Time between two sessions:{{ form.session_time_relation.errors }}{{ form.session_time_relation }} {% endif %} - {% if group.type.slug == "wg" %} Additional Session Request:{{ form.third_session }} Check this box to request an additional session.
Additional slot may be available after agenda scheduling has closed and with the approval of an Area Director.
@@ -20,8 +19,10 @@ {% include 'meeting/session_details_form.html' with form=form.session_forms.2 only %}
- {% else %}{# else group.type.slug != "wg" #} - {% include 'meeting/session_details_form.html' with form=form.session_forms.2 hidden=True only %} + {% else %}{# else not group.features.acts_like_wg #} + {% for session_form in form.session_forms %} + Session {{ forloop.counter }}:*{% include 'meeting/session_details_form.html' with form=session_form only %} + {% endfor %} {% endif %} Number of Attendees:{% if not is_virtual %}*{% endif %}{{ form.attendees.errors }}{{ form.attendees }} People who must be present:{{ form.bethere.errors }}{{ form.bethere }} diff --git a/ietf/secr/templates/includes/sessions_request_view.html b/ietf/secr/templates/includes/sessions_request_view.html index 7d842eb7aa..3f85986fb2 100644 --- a/ietf/secr/templates/includes/sessions_request_view.html +++ b/ietf/secr/templates/includes/sessions_request_view.html @@ -18,7 +18,7 @@ {% endif %} - {% if forloop.counter == 2 and not is_virtual %} + {% if group.features.acts_like_wg and forloop.counter == 2 and not is_virtual %} Time between sessions:{% if session.session_time_relation_display %}{{ session.session_time_relation_display }}{% else %}No preference{% endif %} {% endif %} {% endif %}{% endfor %} diff --git a/ietf/secr/templates/sreq/confirm.html b/ietf/secr/templates/sreq/confirm.html index 141b5cbced..c458459f78 100755 --- a/ietf/secr/templates/sreq/confirm.html +++ b/ietf/secr/templates/sreq/confirm.html @@ -30,7 +30,7 @@

Sessions - Confirm

{% include "includes/sessions_request_view.html" %} - {% if form.session_forms.forms_to_keep|length > 2 %} + {% if group.features.acts_like_wg and form.session_forms.forms_to_keep|length > 2 %}

Note: Your request for a third session must be approved by an area director before being submitted to agenda@ietf.org. Click "Submit" below to email an approval