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:
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 %}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With