Model field with custom form field


I would like to have a model field that saves durations as whole minutes:

class Example(models.Model):

    duration = models.IntegerField(help_text="The duration in minutes.")

However, in the Django Admin I would like to represent the duration in HH:MM form and allow the user to enter durations in this form. For example, 90 minutes would be represented as 1:30 in a forms.CharField. Entering and saving an input of 100:00 would save 6000 minutes.

To achieve this, I wrote a custom form field:

class ZeitspanneField(forms.Field):

    def __init__(self, *, min_value=None, max_value=None, **kwargs):
        # ...

    def prepare_value(self, value):
        # Converts `int` to HH:MM strings.
        if isinstance(value, int):
            return minutes_to_HHMM(value)
        # ...
        return value

    def to_python(self, value):
        # Converts `value` (e. g. "1:30") into an integer.
        # ...
        return convert_HHMM_to_minutes(value)

    def validate(self, value):
        # ...

And, following the documentation, a custom model field:

class ZeitspanneModelField(models.IntegerField):

    def formfield(self, **kwargs):
        defaults = {"form_class": MyForms.ZeitspanneField}

        return super().formfield(**defaults)

Using both in the updated model class:

class Example(models.Model):

    duration = ZeitspanneModelField(help_text="The duration in minutes.")

The problem is: When I use this in the Django Admin (using class Example with admin.TabularInline), it doesn’t work: The input fields are rendered with HTML type="number", as if Example.duration was still defined with models.IntegerField instead of the ZeitspanneModelField. The fields that should already have data are also empty, no value is rendered.

It looks as if the widget was taken from the base class of the model field (which is models.IntegerField) rather than the default widget that comes with the form field (which should be text).

What am I doing wrong?

Hey there!
I think that if this is the default behavior for your field, you may want to implement the formfield method on your custom field.
If that also doesnt work, maybe you need to define formfield_overrides on your inline class.


But I did? Please see the code in my above post.

Yes, I’ve seen that, but I would prefer a more general solution that works with all ModelForms created for class Example. Also, it would be great to learn and understand what is actually wrong with the code as posted.

For completeness and confirmation, if I use a custom ModelForm like this:

class ExampleForm(forms.ModelForm):

    zeit = ZeitspanneField(required=False, min_value=0)

    class Meta:
        model = Example
        fields = '__all__'

then everything works as expected.