Reputation: 1179
In a form I am using a MultiValueField (MVF) with a MultiWidget that has several fields. If there is a validation error in one of the fields of the MVF, this gets handled (displayed) at the MVF level, rather than at the individual sub-fields, which can lead to:
* Ensure this value is greater than or equal to 1.
* Ensure this value is greater than or equal to -100.0.
Number of days: -1
...
...
Threshold: -200
Where the first error refers to the first field of the MVF and the second error the the last field of the MVF.
Is it possible to put those error messages "inside" the MVF at the field where they belong? (maybe in the format_output method of the MultiWidget?)
Upvotes: 3
Views: 791
Reputation: 9991
The following solution doesn't use a MultiValueField but instead:
__init__
_post_clean
Here is some test code that needs to be adapted to each case:
class MyMultiField(CharField):
def split(self, form):
name = 'test'
form.fields_backup[name] = form.fields[name]
del form.fields[name]
# here is where you define your individual fields:
for i in range(3):
form.fields[name + '_' + str(i)] = CharField()
# you need to extract the initial data for these fields
form.initial[name + '_' + str(i)] = somefunction(form.initial[name])
form.fields['test_1'] = DecimalField() # because I only want numbers in the 2nd field
def restore(self, form):
# here is where you describe how to joins the individual fields:
value = ''.join([unicode(v) for k, v in form.cleaned_data.items() if 'test_' in k])
# extra step to validate the combined value against the original field:
try:
restored_data = form.cleaned_data.copy()
restored_data["test"] = form.fields_backup["test"].clean(value)
for k in form.cleaned_data:
if k.startswith("test_"):
del restored_data[k]
form.cleaned_data = restored_data
except Exception, e:
form._errors[NON_FIELD_ERRORS] = form.error_class(e)
class MyForm(Form):
test = MyMultiField()
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
self.fields_backup = {}
self.fields['data'].split(self)
def _post_clean(self):
self.fields_backup['data'].restore(self)
return super(MyForm, self)._post_clean()
Before:
After (validating some input):
I'm not sure if it's possible to decouple this Field/Form code further using this approach. I'm also not quite satisfied with this code as the new field class needs to inherit from the original one.
Nonetheless, the basic idea is there and I successfully used it to individually validate form fields built from a dictionary stored in a single model field with PostgreSQL hstore.
Upvotes: 1