2

I have extended datetime.timedelta for some previous work to update the division operator to support floats (not supported before python 3.x). Having started using a DurationField in one of my models I would like to create a custom version which uses my extended timedelta class so that objects returned after a query include my custom timedelta class. What is the correct approach to implement this?

I've been editing the DurationField class itself whilst I figure out how to do the above before taking the next step to figure out how to add a custom model field. Code as follows (modified Django source code:

class TimeDeltaExtended(datetime.timedelta):
    def __div__(self, divisor):
        return TimeDeltaExtended(seconds=self.total_seconds()/divisor)

    def __mul__(self, multiplier):
        return TimeDeltaExtended(seconds=self.total_seconds()*multiplier)

    def __add__(self, other):
        if isinstance(other, int) and other is 0:
            return self
        else:
            datetime.timedelta.__add__(other)

class DurationField(Field):
...

    def to_python(self, value):
        if value is None:
            return value
        if isinstance(value, TimeDeltaExtended):
            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},
        )
...

However when I use this code queries still return normal TimeDelta objects. Is this even the area I should be looking at?

Environment Python 2.7, Django 1.11

n0rman0
  • 113
  • 11
  • I don't know unfortunately, I've been struggling to understand how it all hooks together, my edit is essentially based on spurious guess work at best! My only edit to the durationfield class is to add `TimeDeltaExtended` – n0rman0 Aug 18 '17 at 15:12

1 Answers1

1

This is how duration field will parse the serialised form of timedelta(seconds=123.456):

>>> DurationField().to_python('00:02:03.456000')
datetime.timedelta(0, 123, 456000)

You can just let the superclass do the heavy lifting of validation, exception handling, etc. Just convert to your specialised class as a post-processing step:

>>> class MyDurationField(DurationField):
...     def to_python(self, value):
...         timedelta = super(MyDurationField, self).to_python(value)
...         return TimeDeltaExtended(seconds=timedelta.total_seconds())
...     
>>> MyDurationField().to_python('00:02:03.456000')
TimeDeltaExtended(0, 123, 456000)
>>> MyDurationField().to_python('00:02:03.456000') / 2.0
TimeDeltaExtended(0, 61, 728000)
wim
  • 338,267
  • 99
  • 616
  • 750
  • Thanks, seems ideal (and elegant). This works for me when used as your example. However when I run a query for the model which contains `MyDurationField` the field is still represented by a normal timedelta, possibly because a different function other than `to_python` is used? – n0rman0 Aug 18 '17 at 15:44
  • 1
    You also need to define `from_db_value()` which is the method that converts the value returned by the db to a python object. You should be able to do as with `to_python()`: call `super` first, then convert the value. Docs [here](https://docs.djangoproject.com/en/1.11/howto/custom-model-fields/#converting-values-to-python-objects). Note also that you should add the case `value` is already a `TimeDeltaExtended` instance in your `to_python` method. – dirkgroten Aug 18 '17 at 17:14