Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Custom MultiWidget Retaining Old Values

Tags:

python

django

I have created a simple custom MultiValueField and MultiWidget for the purpose of entering a date of birth but allowing separate entry of the day, month and year. The rationale is that the app is for entry of historical data, so one or more of these fields could be unknown. There is also a checkbox that displays 'unknown' on the user-facing site.

I've found that everything works fine, except that when I save an instance of an object in the admin site and go to create a new instance, the blank admin form displays the date values from the previous entry:

Custom MultiWidget

All those fields should be blank for a new entry.

It looks like the field is not being reinitialised correctly, but I am not sure where this should take place. I have added a workaround by checking in the form whether to reinitialise the fields, but I feel that this should be done in the custom field/widget.

Sample code:

    class CustomDateWidget(forms.MultiWidget):
        """Custom MultiWidget to allow saving different date values."""
        template_name = 'myapp/widgets/custom_date_widget.html'

        def __init__(self, attrs=None):
            _widgets = (forms.NumberInput(attrs=({'placeholder': 'DD'})),
                        forms.NumberInput(attrs=({'placeholder': 'MM'})),
                        forms.NumberInput(attrs=({'placeholder': 'YYYY'})),
                        forms.CheckboxInput()) 
            super().__init__(_widgets, attrs)

        def decompress(self, value):
            if value:
                return value
            else:
                return '', '', '', False

        def value_from_datadict(self, data, files, name):
            value_list = [
                widget.value_from_datadict(data, files, name + '_%s' % i)
                for i, widget in enumerate(self.widgets)
            ]
            return value_list


    class CustomDateField(forms.MultiValueField):
        """Custom MultiValueField to allow saving different date values."""
        def __init__(self, **kwargs):
            fields = (forms.IntegerField(min_value=1, max_value=31), forms.IntegerField(min_value=1, max_value=12),
                      forms.IntegerField(min_value=1800, max_value=9999), forms.BooleanField())
            super().__init__(fields=fields, widget=CustomDateWidget, require_all_fields=True,
                             **kwargs)

        def compress(self, data_list):
            return data_list


    class PersonAdminForm(forms.ModelForm):
        """Custom Person admin form."""
        date_of_birth = CustomDateField(required=False)

        class Meta:
            model = Person
            fields = '__all__'

        def __init__(self, *args, **kwargs):
            super(PersonAdminForm, self).__init__(*args, **kwargs)
            # Dirty workaround?
            if not self.instance.pk:
                self.fields['date_of_birth'] = CustomDateField(required=False)

            ...

Widget template:

    <style>
        .custom-date-widget-container {
            padding-right: 15px;
        }
        .custom-date-widget td {
            width: 25%;
        }
        .custom-date-widget td > input {
            width: 100%;
            box-sizing: border-box;
        }
    </style>
    {% spaceless %}
        <div class="custom-date-widget-container">
            <table class="custom-date-widget">
                <thead>
                    <tr>
                        <th>Day</th>
                        <th>Month</th>
                        <th>Year</th>
                        <th>Unknown</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        {% for widget in widget.subwidgets %}
                            <td>{% include widget.template_name %}</td>
                        {% endfor %}
                    </tr>
                </tbody>
            </table>
        </div>
    {% endspaceless %}
like image 215
alstr Avatar asked Oct 16 '22 15:10

alstr


1 Answers

After some further digging I found the issue was not with the widget but rather in the PersonAdmin class.

Amongst other things, I was setting the initial values here if the object already existed:

def get_form(self, request, obj=None, change=False, **kwargs):
    form = super(PersonAdmin, self).get_form(request, obj, **kwargs)
    if obj and type(obj) == Person:
        person = obj
        form.base_fields['date_of_birth'].initial = [person.dob_day, person.dob_month, person.dob_year, person.dob_unknown]

What I didn't realise was that, if creating a new object, the fields didn't automatically set to empty, but retained the previously set values.

Adding the following resolved the issue:

    else:
        form.base_fields['date_of_birth'].initial = ['', '', '', False]

In the end it was simpler than I thought, just I sent myself down a rabbit hole thinking it was the widget.

like image 134
alstr Avatar answered Nov 15 '22 12:11

alstr