From b28332cd462b3aec9fe595ae2c530cf23c949530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Wed, 12 Aug 2009 16:37:20 +0000 Subject: [PATCH] [soc2009/model-validation] Added note on ComplexValidators and multi field validation git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11436 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/validation.txt | 76 +++++++++++++++++------------------ 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index ed6f41e30a..0f2b545df5 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -293,53 +293,49 @@ Cleaning and validating fields that depend on each other Suppose we add another requirement to our contact form: if the ``cc_myself`` field is ``True``, the ``subject`` must contain the word ``"help"``. We are -performing validation on more than one field at a time, so the form's -``clean()`` method is a good spot to do this. Notice that we are talking about -the ``clean()`` method on the form here, whereas earlier we were writing a -``clean()`` method on a field. It's important to keep the field and form -difference clear when working out where to validate things. Fields are single -data points, forms are a collection of fields. +performing validation on more than one field at a time, so a +``ComplexValidator`` is a good start. The complex validators are run in the +form's ``clean()`` after the individual fields have been validated. This is the +main difference against simple validators. It is important to realize that, +even if defined in very similar way, simple and complex validators are run in +different places in the code. Simple validators on a field (single data point), +complex validator on a form (collection of fields). -By the time the form's ``clean()`` method is called, all the individual field -clean methods will have been run (the previous two sections), so -``self.cleaned_data`` will be populated with any data that has survived so -far. So you also need to remember to allow for the fact that the fields you -are wanting to validate might not have survived the initial individual field -checks. +By the time the field's complex validators are called, all the individual field +clean methods will have been run (the previous two sections), so the +validator's ``all_values`` argument will be populated with any data that has +survived so far. So you also need to remember to allow for the fact that the +fields you are wanting to validate might not have survived the initial +individual field checks. -There are two way to report any errors from this step. Probably the most -common method is to display the error at the top of the form. To create such -an error, you can raise a ``ValidationError`` from the ``clean()`` method. For -example:: +Complex validator is run on the form, but reports it's error with the field. As +with simple validators, all complex validators for a given field are run if the +field has passed cleaning and their errors are aggregated and reported. + +To create a complex validator, simply subclass ``ComplexValidator`` and supply +your validation logic in it's ``__call__()`` method, for example:: + + class ValidateHelpInSubjectIfCCingMyself(ComplexValidator): + def __call__(self, all_values={}, obj=None): + cc_myself = self.get_value('cc_myself', all_values, obj) + + if cc_myself and "help" not in value: + raise forms.ValidationError("Did not send for 'help' in " + "the subject despite CC'ing yourself.") + class ContactForm(forms.Form): - # Everything as before. + subject = forms.CharField(max_length=100, + validators=[ValidateHelpInSubjectIfCCingMyself()]) ... + # Everything as before. - def clean(self): - cleaned_data = self.cleaned_data - cc_myself = cleaned_data.get("cc_myself") - subject = cleaned_data.get("subject") - if cc_myself and subject: - # Only do something if both fields are valid so far. - if "help" not in subject: - raise forms.ValidationError("Did not send for 'help' in " - "the subject despite CC'ing yourself.") - - # Always return the full collection of cleaned data. - return cleaned_data - -In this code, if the validation error is raised, the form will display an -error message at the top of the form (normally) describing the problem. - -The second approach might involve assigning the error message to one of the -fields. In this case, let's assign an error message to both the "subject" and -"cc_myself" rows in the form display. Be careful when doing this in practice, -since it can lead to confusing form output. We're showing what is possible -here and leaving it up to you and your designers to work out what works -effectively in your particular situation. Our new code (replacing the previous -sample) looks like this:: +The second approach might involve assigning the error message to both fields +involved. To do this we will move the validation code to the form's ``clean()`` +method, which is a convenient place for form-wide validation and has access to +the form instance and it's ``_errors`` property. Our new code (replacing the +previous sample) looks like this:: from django.forms.util import ErrorList