Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.0.X] Added a lot more explanation about form field validation, inc…

…luding

expanded examples. Fixed #5843, #6652, #7428.

Backport of r9177 from trunk.


git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@9180 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit e4b28a9b61e8b2ed0b2c4eff441a0f4772702c75 1 parent 6a30f5a
Malcolm Tredinnick authored October 06, 2008

Showing 1 changed file with 208 additions and 12 deletions. Show diff stats Hide diff stats

  1. 220  docs/ref/forms/validation.txt
220  docs/ref/forms/validation.txt
@@ -25,7 +25,8 @@ The three types of cleaning methods are:
25 25
     * The ``clean()`` method on a Field subclass. This is responsible
26 26
       for cleaning the data in a way that is generic for that type of field.
27 27
       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.
29 30
 
30 31
     * The ``clean_<fieldname>()`` method in a form subclass -- where
31 32
       ``<fieldname>`` is replaced with the name of the form field attribute.
@@ -44,6 +45,10 @@ The three types of cleaning methods are:
44 45
       formfield-specific piece of validation and, possibly,
45 46
       cleaning/normalizing the data.
46 47
 
  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
+
47 52
     * The Form subclass's ``clean()`` method. This method can perform
48 53
       any validation that requires access to multiple fields from the form at
49 54
       once. This is where you might put in things to check that if field ``A``
@@ -56,7 +61,9 @@ The three types of cleaning methods are:
56 61
       Note that any errors raised by your ``Form.clean()`` override will not
57 62
       be associated with any field in particular. They go into a special
58 63
       "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`_.
60 67
 
61 68
 These methods are run in the order given above, one field at a time.  That is,
62 69
 for each field in the form (in the order they are declared in the form
@@ -64,8 +71,10 @@ definition), the ``Field.clean()`` method (or its override) is run, then
64 71
 ``clean_<fieldname>()``. Finally, once those two methods are run for every
65 72
 field, the ``Form.clean()`` method, or its override, is executed.
66 73
 
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
  74
+Examples of each of these methods are provided below.
  75
+
  76
+As mentioned, any of these methods can raise a ``ValidationError``. For any
  77
+field, if the ``Field.clean()`` method raises a ``ValidationError``, any
69 78
 field-specific cleaning method is not called. However, the cleaning methods
70 79
 for all remaining fields are still executed.
71 80
 
@@ -78,32 +87,219 @@ should iterate through ``self.cleaned_data.items()``, possibly considering the
78 87
 ``_errors`` dictionary attribute on the form as well. In this way, you will
79 88
 already know which fields have passed their individual validation requirements.
80 89
 
81  
-A simple example
82  
-~~~~~~~~~~~~~~~~
  90
+.. _described later:
  91
+
  92
+Form subclasses and modifying field errors
  93
+==========================================
  94
+
  95
+Sometimes, in a form's ``clean()`` method, you will want to add an error
  96
+message to a particular field in the form. This won't always be appropriate
  97
+and the more typical situation is to raise a ``ValidationError`` from
  98
+``Form.clean()``, which is turned into a form-wide error that is available
  99
+through the ``Form.non_field_errors()`` method.
  100
+
  101
+When you really do need to attach the error to a particular field, you should
  102
+store (or amend) a key in the `Form._errors` attribute. This attribute is an
  103
+instance of a ``django.form.utils.ErrorDict`` class. Essentially, though, it's
  104
+just a dictionary. There is a key in the dictionary for each field in the form
  105
+that has an error. Each value in the dictionary is a
  106
+``django.form.utils.ErrorList`` instance, which is a list that knows how to
  107
+display itself in different ways. So you can treat `_errors` as a dictionary
  108
+mapping field names to lists.
  109
+
  110
+If you want to add a new error to a particular field, you should check whether
  111
+the key already exists in `self._errors` or not. If not, create a new entry
  112
+for the given key, holding an empty ``ErrorList`` instance. In either case,
  113
+you can then append your error message to the list for the field name in
  114
+question and it will be displayed when the form is displayed.
  115
+
  116
+There 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
+
  136
+Using validation in practice
  137
+=============================
83 138
 
84  
-Here's a simple example of a custom field that validates its input is a string
  139
+The previous sections explained how validation works in general for forms.
  140
+Since it can sometimes be easier to put things into place by seeing each
  141
+feature in use, here are a series of small examples that use each of the
  142
+previous features.
  143
+
  144
+Form field default cleaning
  145
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  146
+
  147
+Let's firstly create a custom form field that validates its input is a string
85 148
 containing comma-separated e-mail addresses, with at least one address. We'll
86 149
 keep 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::
88 151
 
89 152
     from django import forms
90 153
 
91 154
     class MultiEmailField(forms.Field):
92 155
         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
+            """
93 160
             if not value:
94 161
                 raise forms.ValidationError('Enter at least one e-mail address.')
95 162
             emails = value.split(',')
96 163
             for email in emails:
97 164
                 if not is_valid_email(email):
98 165
                     raise forms.ValidationError('%s is not a valid e-mail address.' % email)
  166
+
  167
+            # Always return the cleaned data.
99 168
             return emails
100 169
 
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::
  170
+Every form that uses this field will have this ``clean()`` method run before
  171
+anything else can be done with the field's data. This is cleaning that is
  172
+specific to this type of field, regardless of how it is subsequently used.
  173
+
  174
+Let's create a simple ``ContactForm`` to demonstrate how you'd use this
  175
+field::
104 176
 
105 177
     class ContactForm(forms.Form):
106 178
         subject = forms.CharField(max_length=100)
107 179
         message = forms.CharField()
108  
-        senders = MultiEmailField()
  180
+        sender = forms.EmailField()
  181
+        recipients = MultiEmailField()
109 182
         cc_myself = forms.BooleanField(required=False)
  183
+
  184
+Simply use ``MultiEmailField`` like any other form field. When the
  185
+``is_valid()`` method is called on the form, the ``MultiEmailField.clean()``
  186
+method will be run as part of the cleaning process.
  187
+
  188
+Cleaning a specific field attribute
  189
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  190
+
  191
+Continuing on from the previous example, suppose that in our ``ContactForm``,
  192
+we 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
  194
+don't want to put it into the general ``MultiEmailField`` class. Instead, we
  195
+write 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
+
  210
+Cleaning and validating fields that depend on each other
  211
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  212
+
  213
+Suppose we add another requirement to our contact form: if the ``cc_myself``
  214
+field is ``True``, the ``subject`` must contain the word ``"help"``. We are
  215
+performing 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
  217
+the ``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
  219
+difference clear when working out where to validate things. Fields are single
  220
+data points, forms are a collection of fields.
  221
+
  222
+By the time the form's ``clean()`` method is called, all the individual field
  223
+clean methods will have been run (the previous two sections), so
  224
+``self.cleaned_data`` will be populated with any data that has survived so
  225
+far. So you also need to remember to allow for the fact that the fields you
  226
+are wanting to validate might not have survived the initial individual field
  227
+checks.
  228
+
  229
+There are two way to report any errors from this step. Probably the most
  230
+common method is to display the error at the top of the form. To create such
  231
+an error, you can raise a ``ValidationError`` from the ``clean()`` method. For
  232
+example::
  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
+
  252
+In this code, if the validation error is raised, the form will display an
  253
+error message at the top of the form (normally) describing the problem.
  254
+
  255
+The second approach might involve assigning the error message to one of the
  256
+fields. 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,
  258
+since it can lead to confusing form output. We're showing what is possible
  259
+here and leaving it up to you and your designers to work out what works
  260
+effectively in your particular situation. Our new code (replacing the previous
  261
+sample) 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
+
  289
+As you can see, this approach requires a bit more effort, not withstanding the
  290
+extra design effort to create a sensible form display. The details are worth
  291
+noting, however. Firstly, earlier we mentioned that you might need to check if
  292
+the field name keys already exist in the ``_errors`` dictionary. In this case,
  293
+since we know the fields exist in ``self.cleaned_data``, they must have been
  294
+valid when cleaned as individual fields, so there will be no corresonding
  295
+entries in ``_errors``.
  296
+
  297
+Secondly, once we have decided that the combined data in the two fields we are
  298
+considering aren't valid, we must remember to remove them from the
  299
+``cleaned_data``.
  300
+
  301
+In fact, Django will currently completely wipe out the ``cleaned_data``
  302
+dictionary if there are any errors in the form. However, this behaviour may
  303
+change in the future, so it's not a bad idea to clean up after yourself in the
  304
+first place.
  305
+

0 notes on commit e4b28a9

Please sign in to comment.
Something went wrong with that request. Please try again.