From 67b2fc110764e43dcb0d5092dee19e5de05383e1 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Wed, 21 Dec 2016 15:26:32 +0000 Subject: [PATCH] Extended the form fields used for duration to accept additional formats, including formats the secretariat are currently using, in order to avoid trouble with the TimedeltaField --> DurationField transition. - Legacy-Id: 12598 --- .../migrations/0043_auto_20161219_1345.py | 4 +- ietf/meeting/models.py | 4 +- ietf/secr/meetings/forms.py | 12 ++++- ietf/utils/fields.py | 54 +++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/ietf/meeting/migrations/0043_auto_20161219_1345.py b/ietf/meeting/migrations/0043_auto_20161219_1345.py index 641d64e22..c2c7deea4 100644 --- a/ietf/meeting/migrations/0043_auto_20161219_1345.py +++ b/ietf/meeting/migrations/0043_auto_20161219_1345.py @@ -15,12 +15,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='meeting', name='xidsubmit_cutoff_time_utc', - field=models.DurationField(default=datetime.timedelta(0, 86399), help_text=b'The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.', blank=True), + field=models.DurationField(default=datetime.timedelta(0, 86399), help_text=b"The time of day (UTC) after which submission will be closed. Use for example 23:59:59.", blank=True), ), migrations.AddField( model_name='meeting', name='xidsubmit_cutoff_warning_days', - field=models.DurationField(default=datetime.timedelta(21), help_text=b'How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.', blank=True), + field=models.DurationField(default=datetime.timedelta(21), help_text=b"How long before the 00 cutoff to start showing cutoff warnings. Use for example '21' or '21 days'.", blank=True), ), migrations.AddField( model_name='session', diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index e36884b6a..99b8fdcb8 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -68,10 +68,10 @@ class Meeting(models.Model): help_text = "The number of days before the meeting start date when the submission of -01 drafts etc. will be closed.") idsubmit_cutoff_time_utc = models.DurationField(blank=True, default=settings.IDSUBMIT_DEFAULT_CUTOFF_TIME_UTC, - help_text = "The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.") + help_text = "The time of day (UTC) after which submission will be closed. Use for example 23:59:59.") idsubmit_cutoff_warning_days = models.DurationField(blank=True, default=settings.IDSUBMIT_DEFAULT_CUTOFF_WARNING_DAYS, - help_text = "How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.") + help_text = "How long before the 00 cutoff to start showing cutoff warnings. Use for example '21' or '21 days'.") submission_start_day_offset = models.IntegerField(blank=True, default=settings.MEETING_MATERIALS_DEFAULT_SUBMISSION_START_DAYS, help_text = "The number of days before the meeting start date after which meeting materials will be accepted.") diff --git a/ietf/secr/meetings/forms.py b/ietf/secr/meetings/forms.py index 390b9bc8b..1b6df431e 100644 --- a/ietf/secr/meetings/forms.py +++ b/ietf/secr/meetings/forms.py @@ -6,7 +6,7 @@ from django import forms from ietf.group.models import Group from ietf.meeting.models import Meeting, Room, TimeSlot, Session, SchedTimeSessAssignment from ietf.name.models import TimeSlotTypeName - +import ietf.utils.fields DAYS_CHOICES = ((-1,'Saturday'), (0,'Sunday'), @@ -87,10 +87,18 @@ class TimeChoiceField(forms.ChoiceField): # Forms #---------------------------------------------------------- class MeetingModelForm(forms.ModelForm): + idsubmit_cutoff_time_utc = ietf.utils.fields.DurationField() + idsubmit_cutoff_warning_days = ietf.utils.fields.DurationField() class Meta: model = Meeting exclude = ('type', 'agenda', 'session_request_lock_message') + + def __init__(self,*args,**kwargs): + super(MeetingModelForm, self).__init__(*args,**kwargs) + for f in [ 'idsubmit_cutoff_warning_days', 'idsubmit_cutoff_time_utc', ]: + self.fields[f].help_text = kwargs['instance']._meta.get_field(f).help_text + def clean_number(self): number = self.cleaned_data['number'] if not number.isdigit(): @@ -174,7 +182,7 @@ class NonSessionEditForm(forms.Form): class TimeSlotForm(forms.Form): day = forms.ChoiceField(choices=DAYS_CHOICES) time = forms.TimeField() - duration = forms.DurationField(help_text="Enter duration as 'DD HH:MM:SS', or parts thereof. '3:42' means 3 minutes, 42 seconds, not 3 hours 42 minutes.") + duration = ietf.utils.fields.DurationField() name = forms.CharField(help_text='Name that appears on the agenda') class NonSessionForm(TimeSlotForm): diff --git a/ietf/utils/fields.py b/ietf/utils/fields.py index a5f0d458a..028c0e270 100644 --- a/ietf/utils/fields.py +++ b/ietf/utils/fields.py @@ -1,5 +1,15 @@ +# Copyright The IETF Trust 2007, All Rights Reserved + +import re +import six +import datetime + +import debug + from django import forms from django.core.validators import validate_email +from django.core.exceptions import ValidationError +from django.utils.dateparse import parse_duration class MultiEmailField(forms.Field): def to_python(self, value): @@ -64,3 +74,47 @@ class DatepickerDateField(forms.DateField): self.widget.attrs["placeholder"] = date_format for k, v in picker_settings.iteritems(): self.widget.attrs["data-date-%s" % k] = v + + +# This accepts any ordered combination of labelled days, hours, minutes, seconds +ext_duration_re = re.compile( + r'^' + r'(?:(?P-?\d+) ?(?:d|days))?' + r'(?:[, ]*(?P-?\d+) ?(?:h|hours))?' + r'(?:[, ]*(?P-?\d+) ?(?:m|minutes))?' + r'(?:[, ]*(?P-?\d+) ?(?:s|seconds))?' + r'$' +) +# This requires hours and minutes, and accepts optional X days and :SS +mix_duration_re = re.compile( + r'^' + r'(?:(?P-?\d+) ?(?:d|days)[, ]*)?' + r'(?:(?P-?\d+))' + r'(?::(?P-?\d+))' + r'(?::(?P-?\d+))?' + r'$' +) + +def parse_duration_ext(value): + if value.strip() != '': + match = ext_duration_re.match(value) + if not match: + match = mix_duration_re.match(value) + if not match: + return parse_duration(value) + else: + kw = match.groupdict() + kw = {k: float(v) for k, v in six.iteritems(kw) if v is not None} + return datetime.timedelta(**kw) + +class DurationField(forms.DurationField): + def to_python(self, value): + if value in self.empty_values: + return None + if isinstance(value, datetime.timedelta): + return value + value = parse_duration_ext(value) + if value is None: + raise ValidationError(self.error_messages['invalid'], code='invalid') + return value +