Django

Code

Changeset 9177

Show
Ignore:
Timestamp:
10/06/08 06:21:11 (3 months ago)
Author:
mtredinnick
Message:

Added a lot more explanation about form field validation, including expanded
examples. Fixed #5843, #6652, #7428.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/docs/ref/forms/validation.txt

    r8511 r9177  
    2626      for cleaning the data in a way that is generic for that type of field. 
    2727      For example, a FloatField will turn the data into a Python ``float`` or 
    28       raise a ``ValidationError``. 
     28      raise a ``ValidationError``. This method returns the clean data, which 
     29      is then inserted into the ``cleaned_data`` dictionary of the form. 
    2930 
    3031    * The ``clean_<fieldname>()`` method in a form subclass -- where 
     
    4546      cleaning/normalizing the data. 
    4647 
     48      Just like the general field ``clean()`` method, above, this method 
     49      should return the cleaned data, regardless of whether it changed 
     50      anything or not. 
     51 
    4752    * The Form subclass's ``clean()`` method. This method can perform 
    4853      any validation that requires access to multiple fields from the form at 
     
    5762      be associated with any field in particular. They go into a special 
    5863      "field" (called ``__all__``), which you can access via the 
    59       ``non_field_errors()`` method if you need to. 
     64      ``non_field_errors()`` method if you need to. If you want to attach 
     65      errors to a specific field in the form, you will need to access the 
     66      `_errors` attribute on the form, which is `described later`_. 
    6067 
    6168These methods are run in the order given above, one field at a time.  That is, 
     
    6572field, the ``Form.clean()`` method, or its override, is executed. 
    6673 
    67 As mentioned above, any of these methods can raise a ``ValidationError``. For 
    68 any field, if the ``Field.clean()`` method raises a ``ValidationError``, any 
     74Examples of each of these methods are provided below. 
     75 
     76As mentioned, any of these methods can raise a ``ValidationError``. For any 
     77field, if the ``Field.clean()`` method raises a ``ValidationError``, any 
    6978field-specific cleaning method is not called. However, the cleaning methods 
    7079for all remaining fields are still executed. 
     
    7988already know which fields have passed their individual validation requirements. 
    8089 
    81 A simple example 
    82 ~~~~~~~~~~~~~~~~ 
    83  
    84 Here's a simple example of a custom field that validates its input is a string 
     90.. _described later: 
     91 
     92Form subclasses and modifying field errors 
     93========================================== 
     94 
     95Sometimes, in a form's ``clean()`` method, you will want to add an error 
     96message to a particular field in the form. This won't always be appropriate 
     97and the more typical situation is to raise a ``ValidationError`` from 
     98``Form.clean()``, which is turned into a form-wide error that is available 
     99through the ``Form.non_field_errors()`` method. 
     100 
     101When you really do need to attach the error to a particular field, you should 
     102store (or amend) a key in the `Form._errors` attribute. This attribute is an 
     103instance of a ``django.form.utils.ErrorDict`` class. Essentially, though, it's 
     104just a dictionary. There is a key in the dictionary for each field in the form 
     105that has an error. Each value in the dictionary is a 
     106``django.form.utils.ErrorList`` instance, which is a list that knows how to 
     107display itself in different ways. So you can treat `_errors` as a dictionary 
     108mapping field names to lists. 
     109 
     110If you want to add a new error to a particular field, you should check whether 
     111the key already exists in `self._errors` or not. If not, create a new entry 
     112for the given key, holding an empty ``ErrorList`` instance. In either case, 
     113you can then append your error message to the list for the field name in 
     114question and it will be displayed when the form is displayed. 
     115 
     116There is an example of modifying `self._errors` in the following section. 
     117 
     118.. admonition:: What's in a name? 
     119 
     120    You may be wondering why is this attribute called ``_errors`` and not 
     121    ``errors``. Normal Python practice is to prefix a name with an underscore 
     122    if it's not for external usage. In this case, you are subclassing the 
     123    ``Form`` class, so you are essentially writing new internals. In effect, 
     124    you are given permission to access some of the internals of ``Form``. 
     125     
     126    Of course, any code outside your form should never access ``_errors`` 
     127    directly. The data is available to external code through the ``errors`` 
     128    property, which populates ``_errors`` before returning it). 
     129 
     130    Another reason is purely historical: the attribute has been called 
     131    ``_errors`` since the early days of the forms module and changing it now 
     132    (particularly since ``errors`` is used for the read-only property name) 
     133    would be inconvenient for a number of reasons. You can use whichever 
     134    explanation makes you feel more comfortable. The result is the same. 
     135 
     136Using validation in practice 
     137============================= 
     138 
     139The previous sections explained how validation works in general for forms. 
     140Since it can sometimes be easier to put things into place by seeing each 
     141feature in use, here are a series of small examples that use each of the 
     142previous features. 
     143 
     144Form field default cleaning 
     145~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     146 
     147Let's firstly create a custom form field that validates its input is a string 
    85148containing comma-separated e-mail addresses, with at least one address. We'll 
    86149keep it simple and assume e-mail validation is contained in a function called 
    87 ``is_valid_email()``. The full class:: 
     150``is_valid_email()``. The full class looks like this:: 
    88151 
    89152    from django import forms 
     
    91154    class MultiEmailField(forms.Field): 
    92155        def clean(self, value): 
     156            """ 
     157            Check that the field contains one or more comma-separated emails 
     158            and normalizes the data to a list of the email strings. 
     159            """ 
    93160            if not value: 
    94161                raise forms.ValidationError('Enter at least one e-mail address.') 
     
    97164                if not is_valid_email(email): 
    98165                    raise forms.ValidationError('%s is not a valid e-mail address.' % email) 
     166 
     167            # Always return the cleaned data. 
    99168            return emails 
    100169 
    101 Let's alter the ongoing ``ContactForm`` example to demonstrate how you'd use 
    102 this in a form. Simply use ``MultiEmailField`` instead of ``forms.EmailField``, 
    103 like so:: 
     170Every form that uses this field will have this ``clean()`` method run before 
     171anything else can be done with the field's data. This is cleaning that is 
     172specific to this type of field, regardless of how it is subsequently used. 
     173 
     174Let's create a simple ``ContactForm`` to demonstrate how you'd use this 
     175field:: 
    104176 
    105177    class ContactForm(forms.Form): 
    106178        subject = forms.CharField(max_length=100) 
    107179        message = forms.CharField() 
    108         senders = MultiEmailField() 
     180        sender = forms.EmailField() 
     181        recipients = MultiEmailField() 
    109182        cc_myself = forms.BooleanField(required=False) 
     183 
     184Simply use ``MultiEmailField`` like any other form field. When the 
     185``is_valid()`` method is called on the form, the ``MultiEmailField.clean()`` 
     186method will be run as part of the cleaning process. 
     187 
     188Cleaning a specific field attribute 
     189~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     190 
     191Continuing on from the previous example, suppose that in our ``ContactForm``, 
     192we want to make sure that the ``recipients`` field always contains the address 
     193``"fred@example.com"``. This is validation that is specific to our form, so we 
     194don't want to put it into the general ``MultiEmailField`` class. Instead, we 
     195write a cleaning method that operates on the ``recipients`` field, like so:: 
     196 
     197    class ContactForm(forms.Form): 
     198        # Everything as before. 
     199        ... 
     200 
     201        def clean_recipients(self): 
     202            data = self.cleaned_data['recipients'] 
     203            if "fred@example.com" not in data: 
     204                raise forms.ValidationError("You have forgotten about Fred!") 
     205 
     206            # Always return the cleaned data, whether you have changed it or 
     207            # not. 
     208            return data 
     209 
     210Cleaning and validating fields that depend on each other 
     211~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     212 
     213Suppose we add another requirement to our contact form: if the ``cc_myself`` 
     214field is ``True``, the ``subject`` must contain the word ``"help"``. We are 
     215performing validation on more than one field at a time, so the form's 
     216``clean()`` method is a good spot to do this. Notice that we are talking about 
     217the ``clean()`` method on the form here, whereas earlier we were writing a 
     218``clean()`` method on a field. It's important to keep the field and form 
     219difference clear when working out where to validate things. Fields are single 
     220data points, forms are a collection of fields. 
     221 
     222By the time the form's ``clean()`` method is called, all the individual field 
     223clean methods will have been run (the previous two sections), so 
     224``self.cleaned_data`` will be populated with any data that has survived so 
     225far. So you also need to remember to allow for the fact that the fields you 
     226are wanting to validate might not have survived the initial individual field 
     227checks. 
     228 
     229There are two way to report any errors from this step. Probably the most 
     230common method is to display the error at the top of the form. To create such 
     231an error, you can raise a ``ValidationError`` from the ``clean()`` method. For 
     232example:: 
     233 
     234    class ContactForm(forms.Form): 
     235        # Everything as before. 
     236        ... 
     237 
     238        def clean(self): 
     239            cleaned_data = self.cleaned_data 
     240            cc_myself = cleaned_data.get("cc_myself") 
     241            subject = cleaned_data.get("subject") 
     242 
     243            if cc_myself and subject: 
     244                # Only do something if both fields are valid so far. 
     245                if "help" not in subject: 
     246                    raise forms.ValidationError("Did not send for 'help' in " 
     247                            "the subject despite CC'ing yourself.") 
     248 
     249            # Always return the full collection of cleaned data. 
     250            return cleaned_data 
     251 
     252In this code, if the validation error is raised, the form will display an 
     253error message at the top of the form (normally) describing the problem. 
     254 
     255The second approach might involve assigning the error message to one of the 
     256fields. In this case, let's assign an error message to both the "subject" and 
     257"cc_myself" rows in the form display. Be careful when doing this in practice, 
     258since it can lead to confusing form output. We're showing what is possible 
     259here and leaving it up to you and your designers to work out what works 
     260effectively in your particular situation. Our new code (replacing the previous 
     261sample) looks like this:: 
     262 
     263    from django.forms.utils import ErrorList 
     264 
     265    class ContactForm(forms.Form): 
     266        # Everything as before. 
     267        ... 
     268 
     269        def clean(self): 
     270            cleaned_data = self.cleaned_data 
     271            cc_myself = cleaned_data.get("cc_myself") 
     272            subject = cleaned_data.get("subject") 
     273 
     274            if cc_myself and subject and "help" not in subject: 
     275                # We know these are not in self._errors now (see discussion 
     276                # below). 
     277                msg = u"Must put 'help' in subject when cc'ing yourself." 
     278                self._errors["cc_myself"] = ErrorList([msg]) 
     279                self._errors["subject"] = ErrorList([msg]) 
     280 
     281                # These fields are no longer valid. Remove them from the 
     282                # cleaned data. 
     283                del cleaned_data["cc_myself"] 
     284                del cleaned_data["subject"] 
     285 
     286            # Always return the full collection of cleaned data. 
     287            return cleaned_data 
     288 
     289As you can see, this approach requires a bit more effort, not withstanding the 
     290extra design effort to create a sensible form display. The details are worth 
     291noting, however. Firstly, earlier we mentioned that you might need to check if 
     292the field name keys already exist in the ``_errors`` dictionary. In this case, 
     293since we know the fields exist in ``self.cleaned_data``, they must have been 
     294valid when cleaned as individual fields, so there will be no corresonding 
     295entries in ``_errors``. 
     296 
     297Secondly, once we have decided that the combined data in the two fields we are 
     298considering aren't valid, we must remember to remove them from the 
     299``cleaned_data``. 
     300 
     301In fact, Django will currently completely wipe out the ``cleaned_data`` 
     302dictionary if there are any errors in the form. However, this behaviour may 
     303change in the future, so it's not a bad idea to clean up after yourself in the 
     304first place. 
     305