Durationfield django

user4743662 picture user4743662 · Apr 2, 2015 · Viewed 8.5k times · Source

Difficulty using the new DurationField in Django 1.8

I'm having a bit of trouble with Django's new DurationField for models.

I want the user to be able to choose if the duration of an event on my webapp is 1 day, 3 days, or 5 days, with the default choice being 3 days.

At the beginning of my model, I declare the choices:

SHORT = datetime.timedelta(days=1)
MEDIUM = datetime.timedelta(days=3)
LONG = datetime.timedelta(days=5)
DURATION_CHOICES = ((SHORT, '1 day'),(MEDIUM, '3 days'), (LONG, '5 days'),)

Then below, I declare the DurationField:

duration = models.DurationField(choices = DURATION_CHOICES, default = MEDIUM)

I created a ModelForm for the model, and rendered it on the appropriate template. On the form, "3 days" was the preselected choice in the dropdown, and "1 day" and "5 days" are options as well. However, when I submit the form, I get the form validation error "Select a valid choice. 3 days, 0:00:00 is not one of the available choices."

However, when I remove the choices from DurationField and leave the default:

duration = models.DurationField(default = MEDIUM)

I can submit without any issues. What am I doing wrong here?

Answer

Fabrizio Ettore Messina picture Fabrizio Ettore Messina · Jun 2, 2015

Did had the same problem the problem is explained in this bugfix ticket

https://code.djangoproject.com/ticket/24897

best way to fix this is to use this custom field while waiting the django team to fix that:

    """
    This is a temp DurationField with a bugfix
    """
    standard_duration_re = re.compile(
        r'^'
        r'(?:(?P<days>-?\d+) (days, )?)?'
        r'((?:(?P<hours>\d+):)(?=\d+:\d+))?'
        r'(?:(?P<minutes>\d+):)?'
        r'(?P<seconds>\d+)'
        r'(?:\.(?P<microseconds>\d{1,6})\d{0,6})?'
        r'$'
    )

    # Support the sections of ISO 8601 date representation that are accepted by
    # timedelta
    iso8601_duration_re = re.compile(
        r'^P'
        r'(?:(?P<days>\d+(.\d+)?)D)?'
        r'(?:T'
        r'(?:(?P<hours>\d+(.\d+)?)H)?'
        r'(?:(?P<minutes>\d+(.\d+)?)M)?'
        r'(?:(?P<seconds>\d+(.\d+)?)S)?'
        r')?'
        r'$'
    )
    def parse_duration(value):
        """Parses a duration string and returns a datetime.timedelta.

        The preferred format for durations in Django is '%d %H:%M:%S.%f'.

        Also supports ISO 8601 representation.
        """
        match = standard_duration_re.match(value)
        if not match:
            match = iso8601_duration_re.match(value)
        if match:
            kw = match.groupdict()
            if kw.get('microseconds'):
                kw['microseconds'] = kw['microseconds'].ljust(6, '0')
            kw = {k: float(v) for k, v in six.iteritems(kw) if v is not None}
            return datetime.timedelta(**kw)

    class DurationField(CoreDurationField):
        def to_python(self, value):
            if value is None:
                return value
            if isinstance(value, datetime.timedelta):
                return value
            try:
                parsed = parse_duration(value)
            except ValueError:
                pass
            else:
                if parsed is not None:
                    return parsed
            raise exceptions.ValidationError(
                self.error_messages['invalid'],
                code='invalid',
                params={'value': value},
            )