Django

Code

root/django/trunk/docs/forms.txt

Revision 7294, 27.5 kB (checked in by mtredinnick, 2 months ago)

Added "svn:eol-style native" to every text file in the tree (*.txt, *.html,
*.py, *.xml and AUTHORS, etc). Added "svn:ignore *.pyc" to some directories in
tests/regressiontests/ that were previously missing it.

Fixed #6545, #6801.

  • Property svn:eol-style set to native
Line 
1 ===============================
2 Forms, fields, and manipulators
3 ===============================
4
5 Forwards-compatibility note
6 ===========================
7
8 The legacy forms/manipulators system described in this document is going to be
9 replaced in the next Django release. If you're starting from scratch, we
10 strongly encourage you not to waste your time learning this. Instead, learn and
11 use the django.newforms system, which we have begun to document in the
12 `newforms documentation`_.
13
14 If you have legacy form/manipulator code, read the "Migration plan" section in
15 that document to understand how we're making the switch.
16
17 .. _newforms documentation: ../newforms/
18
19 Introduction
20 ============
21
22 Once you've got a chance to play with Django's admin interface, you'll probably
23 wonder if the fantastic form validation framework it uses is available to user
24 code. It is, and this document explains how the framework works.
25
26 We'll take a top-down approach to examining Django's form validation framework,
27 because much of the time you won't need to use the lower-level APIs. Throughout
28 this document, we'll be working with the following model, a "place" object::
29
30     from django.db import models
31
32     PLACE_TYPES = (
33         (1, 'Bar'),
34         (2, 'Restaurant'),
35         (3, 'Movie Theater'),
36         (4, 'Secret Hideout'),
37     )
38
39     class Place(models.Model):
40         name = models.CharField(max_length=100)
41         address = models.CharField(max_length=100, blank=True)
42         city = models.CharField(max_length=50, blank=True)
43         state = models.USStateField()
44         zip_code = models.CharField(max_length=5, blank=True)
45         place_type = models.IntegerField(choices=PLACE_TYPES)
46
47         class Admin:
48             pass
49
50         def __unicode__(self):
51             return self.name
52
53 Defining the above class is enough to create an admin interface to a ``Place``,
54 but what if you want to allow public users to submit places?
55
56 Automatic Manipulators
57 ======================
58
59 The highest-level interface for object creation and modification is the
60 **automatic Manipulator** framework. An automatic manipulator is a utility
61 class tied to a given model that "knows" how to create or modify instances of
62 that model and how to validate data for the object. Automatic Manipulators come
63 in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally
64 they are quite similar, but the former knows how to create new instances of the
65 model, while the latter modifies existing instances. Both types of classes are
66 automatically created when you define a new class::
67
68     >>> from mysite.myapp.models import Place
69     >>> Place.AddManipulator
70     <class 'django.models.manipulators.AddManipulator'>
71     >>> Place.ChangeManipulator
72     <class 'django.models.manipulators.ChangeManipulator'>
73
74 Using the ``AddManipulator``
75 ----------------------------
76
77 We'll start with the ``AddManipulator``.  Here's a very simple view that takes
78 POSTed data from the browser and creates a new ``Place`` object::
79
80     from django.shortcuts import render_to_response
81     from django.http import Http404, HttpResponse, HttpResponseRedirect
82     from django import forms
83     from mysite.myapp.models import Place
84
85     def naive_create_place(request):
86         """A naive approach to creating places; don't actually use this!"""
87         # Create the AddManipulator.
88         manipulator = Place.AddManipulator()
89
90         # Make a copy of the POSTed data so that do_html2python can
91         # modify it in place (request.POST is immutable).
92         new_data = request.POST.copy()
93
94         # Convert the request data (which will all be strings) into the
95         # appropriate Python types for those fields.
96         manipulator.do_html2python(new_data)
97
98         # Save the new object.
99         new_place = manipulator.save(new_data)
100
101         # It worked!
102         return HttpResponse("Place created: %s" % new_place)
103
104 The ``naive_create_place`` example works, but as you probably can tell, this
105 view has a number of problems:
106
107     * No validation of any sort is performed. If, for example, the ``name`` field
108       isn't given in ``request.POST``, the save step will cause a database error
109       because that field is required. Ugly.
110
111     * Even if you *do* perform validation, there's still no way to give that
112       information to the user in any sort of useful way.
113
114     * You'll have to separately create a form (and view) that submits to this
115       page, which is a pain and is redundant.
116
117 Let's dodge these problems momentarily to take a look at how you could create a
118 view with a form that submits to this flawed creation view::
119
120     def naive_create_place_form(request):
121         """Simplistic place form view; don't actually use anything like this!"""
122         # Create a FormWrapper object that the template can use. Ignore
123         # the last two arguments to FormWrapper for now.
124         form = forms.FormWrapper(Place.AddManipulator(), {}, {})
125         return render_to_response('places/naive_create_form.html', {'form': form})
126
127 (This view, as well as all the following ones, has the same imports as in the
128 first example above.)
129
130 The ``forms.FormWrapper`` object is a wrapper that templates can
131 easily deal with to create forms. Here's the ``naive_create_form.html``
132 template::
133
134     {% extends "base.html" %}
135
136     {% block content %}
137     <h1>Create a place:</h1>
138
139     <form method="post" action="../do_new/">
140     <p><label for="id_name">Name:</label> {{ form.name }}</p>
141     <p><label for="id_address">Address:</label> {{ form.address }}</p>
142     <p><label for="id_city">City:</label> {{ form.city }}</p>
143     <p><label for="id_state">State:</label> {{ form.state }}</p>
144     <p><label for="id_zip_code">Zip:</label> {{ form.zip_code }}</p>
145     <p><label for="id_place_type">Place type:</label> {{ form.place_type }}</p>
146     <input type="submit" />
147     </form>
148     {% endblock %}
149
150 Before we get back to the problems with these naive set of views, let's go over
151 some salient points of the above template:
152
153     * Field "widgets" are handled for you: ``{{ form.field }}`` automatically
154       creates the "right" type of widget for the form, as you can see with the
155       ``place_type`` field above.
156
157     * There isn't a way just to spit out the form. You'll still need to define
158       how the form gets laid out. This is a feature: Every form should be
159       designed differently. Django doesn't force you into any type of mold.
160       If you must use tables, use tables. If you're a semantic purist, you can
161       probably find better HTML than in the above template.
162
163     * To avoid name conflicts, the ``id`` values of form elements take the
164       form "id_*fieldname*".
165
166 By creating a creation form we've solved problem number 3 above, but we still
167 don't have any validation. Let's revise the validation issue by writing a new
168 creation view that takes validation into account::
169
170     def create_place_with_validation(request):
171         manipulator = Place.AddManipulator()
172         new_data = request.POST.copy()
173
174         # Check for validation errors
175         errors = manipulator.get_validation_errors(new_data)
176         manipulator.do_html2python(new_data)
177         if errors:
178             return render_to_response('places/errors.html', {'errors': errors})
179         else:
180             new_place = manipulator.save(new_data)
181             return HttpResponse("Place created: %s" % new_place)
182
183 In this new version, errors will be found -- ``manipulator.get_validation_errors``
184 handles all the validation for you -- and those errors can be nicely presented
185 on an error page (templated, of course)::
186
187     {% extends "base.html" %}
188
189     {% block content %}
190
191     <h1>Please go back and correct the following error{{ errors|pluralize }}:</h1>
192     <ul>
193         {% for e in errors.items %}
194         <li>Field "{{ e.0 }}": {{ e.1|join:", " }}</li>
195         {% endfor %}
196     </ul>
197
198     {% endblock %}
199
200 Still, this has its own problems:
201
202     * There's still the issue of creating a separate (redundant) view for the
203       submission form.
204
205     * Errors, though nicely presented, are on a separate page, so the user will
206       have to use the "back" button to fix errors. That's ridiculous and unusable.
207
208 The best way to deal with these issues is to collapse the two views -- the form
209 and the submission -- into a single view.  This view will be responsible for
210 creating the form, validating POSTed data, and creating the new object (if the
211 data is valid). An added bonus of this approach is that errors and the form will
212 both be available on the same page, so errors with fields can be presented in
213 context.
214
215 .. admonition:: Philosophy:
216
217     Finally, for the HTTP purists in the audience (and the authorship), this
218     nicely matches the "true" meanings of HTTP GET and HTTP POST: GET fetches
219     the form, and POST creates the new object.
220
221 Below is the finished view::
222
223     def create_place(request):
224         manipulator = Place.AddManipulator()
225
226         if request.method == 'POST':
227             # If data was POSTed, we're trying to create a new Place.
228             new_data = request.POST.copy()
229
230             # Check for errors.
231             errors = manipulator.get_validation_errors(new_data)
232             manipulator.do_html2python(new_data)
233
234             if not errors:
235                 # No errors. This means we can save the data!
236                 new_place = manipulator.save(new_data)
237
238                 # Redirect to the object's "edit" page. Always use a redirect
239                 # after POST data, so that reloads don't accidently create
240                 # duplicate entires, and so users don't see the confusing
241                 # "Repost POST data?" alert box in their browsers.
242                 return HttpResponseRedirect("/places/edit/%i/" % new_place.id)
243         else:
244             # No POST, so we want a brand new form without any data or errors.
245             errors = new_data = {}
246
247         # Create the FormWrapper, template, context, response.
248         form = forms.FormWrapper(manipulator, new_data, errors)
249         return render_to_response('places/create_form.html', {'form': form})
250
251 and here's the ``create_form`` template::
252
253     {% extends "base.html" %}
254
255     {% block content %}
256     <h1>Create a place:</h1>
257
258     {% if form.has_errors %}
259     <h2>Please correct the following error{{ form.error_dict|pluralize }}:</h2>
260     {% endif %}
261
262     <form method="post" action=".">
263     <p>
264         <label for="id_name">Name:</label> {{ form.name }}
265         {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
266     </p>
267     <p>
268         <label for="id_address">Address:</label> {{ form.address }}
269         {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
270     </p>
271     <p>
272         <label for="id_city">City:</label> {{ form.city }}
273         {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
274     </p>
275     <p>
276         <label for="id_state">State:</label> {{ form.state }}
277         {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
278     </p>
279     <p>
280         <label for="id_zip_code">Zip:</label> {{ form.zip_code }}
281         {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
282     </p>
283     <p>
284         <label for="id_place_type">Place type:</label> {{ form.place_type }}
285         {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
286     </p>
287     <input type="submit" />
288     </form>
289     {% endblock %}
290
291 The second two arguments to ``FormWrapper`` (``new_data`` and ``errors``)
292 deserve some mention.
293
294 The first is any "default" data to be used as values for the fields. Pulling
295 the data from ``request.POST``, as is done above, makes sure that if there are
296 errors, the values the user put in aren't lost. If you try the above example,
297 you'll see this in action.
298
299 The second argument is the error list retrieved from
300 ``manipulator.get_validation_errors``.  When passed into the ``FormWrapper``,
301 this gives each field an ``errors`` item (which is a list of error messages
302 associated with the field) as well as a ``html_error_list`` item, which is a
303 ``<ul>`` of error messages. The above template uses these error items to
304 display a simple error message next to each field. The error list is saved as
305 an ``error_dict`` attribute of the ``FormWrapper`` object.
306
307 Using the ``ChangeManipulator``
308 -------------------------------
309
310 The above has covered using the ``AddManipulator`` to create a new object. What
311 about editing an existing one? It's shockingly similar to creating a new one::
312
313     def edit_place(request, place_id):
314         # Get the place in question from the database and create a
315         # ChangeManipulator at the same time.
316         try:
317             manipulator = Place.ChangeManipulator(place_id)
318         except Place.DoesNotExist:
319             raise Http404
320
321         # Grab the Place object in question for future use.
322         place = manipulator.original_object
323
324         if request.method == 'POST':
325             new_data = request.POST.copy()
326             errors = manipulator.get_validation_errors(new_data)
327             manipulator.do_html2python(new_data)
328             if not errors:
329                 manipulator.save(new_data)
330
331                 # Do a post-after-redirect so that reload works, etc.
332                 return HttpResponseRedirect("/places/edit/%i/" % place.id)
333         else:
334             errors = {}
335             # This makes sure the form accurate represents the fields of the place.
336             new_data = manipulator.flatten_data()
337
338         form = forms.FormWrapper(manipulator, new_data, errors)
339         return render_to_response('places/edit_form.html', {'form': form, 'place': place})
340
341 The only real differences are:
342
343     * We create a ``ChangeManipulator`` instead of an ``AddManipulator``.
344       The argument to a ``ChangeManipulator`` is the ID of the object
345       to be changed. As you can see, the initializer will raise an
346       ``ObjectDoesNotExist`` exception if the ID is invalid.
347
348     * ``ChangeManipulator.original_object`` stores the instance of the
349       object being edited.
350
351     * We set ``new_data`` based upon ``flatten_data()`` from the manipulator.
352       ``flatten_data()`` takes the data from the original object under
353       manipulation, and converts it into a data dictionary that can be used
354       to populate form elements with the existing values for the object.
355
356     * The above example uses a different template, so create and edit can be
357       "skinned" differently if needed, but the form chunk itself is completely
358       identical to the one in the create form above.
359
360 The astute programmer will notice the add and create functions are nearly
361 identical and could in fact be collapsed into a single view. This is left as an
362 exercise for said programmer.
363
364 (However, the even-more-astute programmer will take heed of the note at the top
365 of this document and check out the `generic views`_ documentation if all she
366 wishes to do is this type of simple create/update.)
367
368 Custom forms and manipulators
369 =============================
370
371 All the above is fine and dandy if you just want to use the automatically
372 created manipulators. But the coolness doesn't end there: You can easily create
373 your own custom manipulators for handling custom forms.
374
375 Custom manipulators are pretty simple. Here's a manipulator that you might use
376 for a "contact" form on a website::
377
378     from django import forms
379
380     urgency_choices = (
381         (1, "Extremely urgent"),
382         (2, "Urgent"),
383         (3, "Normal"),
384         (4, "Unimportant"),
385     )
386
387     class ContactManipulator(forms.Manipulator):
388         def __init__(self):
389             self.fields = (
390                 forms.EmailField(field_name="from", is_required=True),
391                 forms.TextField(field_name="subject", length=30, max_length=200, is_required=True),
392                 forms.SelectField(field_name="urgency", choices=urgency_choices),
393                 forms.LargeTextField(field_name="contents", is_required=True),
394             )
395
396 A certain similarity to Django's models should be apparent. The only required
397 method of a custom manipulator is ``__init__`` which must define the fields
398 present in the manipulator.  See the ``django.forms`` module for
399 all the form fields provided by Django.
400
401 You use this custom manipulator exactly as you would use an auto-generated one.
402 Here's a simple function that might drive the above form::
403
404     def contact_form(request):
405         manipulator = ContactManipulator()
406         if request.method == 'POST':
407             new_data = request.POST.copy()
408             errors = manipulator.get_validation_errors(new_data)
409             manipulator.do_html2python(new_data)
410             if not errors:
411
412                 # Send e-mail using new_data here...
413
414                 return HttpResponseRedirect("/contact/thankyou/")
415         else:
416             errors = new_data = {}
417         form = forms.FormWrapper(manipulator, new_data, errors)
418         return render_to_response('contact_form.html', {'form': form})
419
420 Implementing ``flatten_data`` for custom manipulators
421 ------------------------------------------------------
422
423 It is possible (although rarely needed) to replace the default automatically
424 created manipulators on a model with your own custom manipulators. If you do
425 this and you are intending to use those models in generic views, you should
426 also define a ``flatten_data`` method in any ``ChangeManipulator`` replacement.
427 This should act like the default ``flatten_data`` and return a dictionary
428 mapping field names to their values, like so::
429
430     def flatten_data(self):
431         obj = self.original_object
432         return dict(
433             from = obj.from,
434             subject = obj.subject,
435             ...
436         )
437
438 In this way, your new change manipulator will act exactly like the default
439 version.
440
441 ``FileField`` and ``ImageField`` special cases
442 ==============================================
443
444 Dealing with ``FileField`` and ``ImageField`` objects is a little more
445 complicated.
446
447 First, you'll need to make sure that your ``<form>`` element correctly defines
448 the ``enctype`` as ``"multipart/form-data"``, in order to upload files::
449
450   <form enctype="multipart/form-data" method="post" action="/foo/">
451
452 Next, you'll need to treat the field in the template slightly differently. A
453 ``FileField`` or ``ImageField`` is represented by *two* HTML form elements.
454
455 For example, given this field in a model::
456
457    photo = model.ImageField('/path/to/upload/location')
458
459 You'd need to display two formfields in the template::
460
461    <p><label for="id_photo">Photo:</label> {{ form.photo }}{{ form.photo_file }}</p>
462
463 The first bit (``{{ form.photo }}``) displays the currently-selected file,
464 while the second (``{{ form.photo_file }}``) actually contains the file upload
465 form field. Thus, at the validation layer you need to check the ``photo_file``
466 key.
467
468 Finally, in your view, make sure to access ``request.FILES``, rather than
469 ``request.POST``, for the uploaded files. This is necessary because
470 ``request.POST`` does not contain file-upload data.
471
472 For example, following the ``new_data`` convention, you might do something like
473 this::
474
475    new_data = request.POST.copy()
476    new_data.update(request.FILES)
477
478 Validators
479 ==========
480
481 One useful feature of manipulators is the automatic validation. Validation is
482 done using a simple validation API: A validator is a callable that raises a
483 ``ValidationError`` if there's something wrong with the data.
484 ``django.core.validators`` defines a host of validator functions (see below),
485 but defining your own couldn't be easier::
486
487     from django.core import validators
488     from django import forms
489
490     class ContactManipulator(forms.Manipulator):
491         def __init__(self):
492             self.fields = (
493                 # ... snip fields as above ...
494                 forms.EmailField(field_name="to", validator_list=[self.isValidToAddress])
495             )
496
497         def isValidToAddress(self, field_data, all_data):
498             if not field_data.endswith("@example.com"):
499                 raise validators.ValidationError("You can only send messages to example.com e-mail addresses.")
500
501 Above, we've added a "to" field to the contact form, but required that the "to"
502 address end with "@example.com" by adding the ``isValidToAddress`` validator to
503 the field's ``validator_list``.
504
505 The arguments to a validator function take a little explanation.  ``field_data``
506 is the value of the field in question, and ``all_data`` is a dictionary of all
507 the data being validated.
508
509 .. admonition:: Note::
510
511     At the point validators are called all data will still be
512     strings (as ``do_html2python`` hasn't been called yet).
513
514 Also, because consistency in user interfaces is important, we strongly urge you
515 to put punctuation at the end of your validation messages.
516
517 When are validators called?
518 ---------------------------
519
520 After a form has been submitted, Django validates each field in turn. First,
521 if the field is required, Django checks that it is present and non-empty. Then,
522 if that test passes *and the form submission contained data* for that field, all
523 the validators for that field are called in turn. The emphasized portion in the
524 last sentence is important: if a form field is not submitted (because it
525 contains no data -- which is normal HTML behavior), the validators are not
526 run against the field.
527
528 This feature is particularly important for models using
529 ``models.BooleanField`` or custom manipulators using things like
530 ``forms.CheckBoxField``. If the checkbox is not selected, it will not
531 contribute to the form submission.
532
533 If you would like your validator to run *always*, regardless of whether its
534 attached field contains any data, set the ``always_test`` attribute on the
535 validator function. For example::
536
537     def my_custom_validator(field_data, all_data):
538         # ...
539     my_custom_validator.always_test = True
540
541 This validator will always be executed for any field it is attached to.
542
543 Ready-made validators
544 ---------------------
545
546 Writing your own validator is not difficult, but there are some situations
547 that come up over and over again. Django comes with a number of validators
548 that can be used directly in your code. All of these functions and classes
549 reside in ``django/core/validators.py``.
550
551 The following validators should all be self-explanatory. Each one provides a
552 check for the given property:
553
554     * isAlphaNumeric
555     * isAlphaNumericURL
556     * isSlug
557     * isLowerCase
558     * isUpperCase
559     * isCommaSeparatedIntegerList
560     * isCommaSeparatedEmailList
561     * isValidIPAddress4
562     * isNotEmpty
563     * isOnlyDigits
564     * isNotOnlyDigits
565     * isInteger
566     * isOnlyLetters
567     * isValidANSIDate
568     * isValidANSITime
569     * isValidEmail
570     * isValidFloat
571     * isValidImage
572     * isValidImageURL
573     * isValidPhone
574     * isValidQuicktimeVideoURL
575     * isValidURL
576     * isValidHTML
577     * isWellFormedXml
578     * isWellFormedXmlFragment
579     * isExistingURL
580     * isValidUSState
581     * hasNoProfanities
582
583 There are also a group of validators that are slightly more flexible. For
584 these validators, you create a validator instance, passing in the parameters
585 described below. The returned object is a callable that can be used as a
586 validator.
587
588 For example::
589
590     from django.core import validators
591     from django import forms
592
593     power_validator = validators.IsAPowerOf(2)
594
595     class InstallationManipulator(forms.Manipulator)
596         def __init__(self):
597             self.fields = (
598                 ...
599                 forms.IntegerField(field_name = "size", validator_list=[power_validator])
600             )
601
602 Here, ``validators.IsAPowerOf(...)`` returned something that could be used as
603 a validator (in this case, a check that a number was a power of 2).
604
605 Each of the standard validators that take parameters have an optional final
606 argument (``error_message``) that is the message returned when validation
607 fails. If no message is passed in, a default message is used.
608
609 ``AlwaysMatchesOtherField``
610     Takes a field name and the current field is valid if and only if its value
611     matches the contents of the other field.
612
613 ``ValidateIfOtherFieldEquals``
614     Takes three parameters: ``other_field``, ``other_value`` and
615     ``validator_list``, in that order. If ``other_field`` has a value of
616     ``other_value``, then the validators in ``validator_list`` are all run
617     against the current field.
618
619 ``RequiredIfOtherFieldGiven``
620     Takes a field name of the current field is only required if the other
621     field has a value.
622
623 ``RequiredIfOtherFieldsGiven``
624     Similar to ``RequiredIfOtherFieldGiven``, except that it takes a list of
625     field names and if any one of the supplied fields has a value provided,
626     the current field being validated is required.
627
628 ``RequiredIfOtherFieldNotGiven``
629     Takes the name of the other field and this field is only required if the
630     other field has no value.
631
632 ``RequiredIfOtherFieldEquals`` and ``RequiredIfOtherFieldDoesNotEqual``
633     Each of these validator classes takes a field name and a value (in that
634     order). If the given field does (or does not have, in the latter case) the
635     given value, then the current field being validated is required.
636
637     An optional ``other_label`` argument can be passed which, if given, is used
638     in error messages instead of the value. This allows more user friendly error
639     messages if the value itself is not descriptive enough.
640
641     Note that because validators are called before any ``do_html2python()``
642     functions, the value being compared against is a string. So
643     ``RequiredIfOtherFieldEquals('choice', '1')`` is correct, whilst
644     ``RequiredIfOtherFieldEquals('choice', 1)`` will never result in the
645     equality test succeeding.
646
647 ``IsLessThanOtherField``
648     Takes a field name and validates that the current field being validated
649     has a value that is less than (or equal to) the other field's value.
650     Again, comparisons are done using strings, so be cautious about using
651     this function to compare data that should be treated as another type. The
652     string "123" is less than the string "2", for example. If you don't want
653     string comparison here, you will need to write your own validator.
654
655 ``NumberIsInRange``
656     Takes two boundary numbers, ``lower`` and ``upper``, and checks that the
657     field is greater than ``lower`` (if given) and less than ``upper`` (if
658     given).
659
660     Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow
661     values of both 10 and 20. This validator only checks numeric values
662     (e.g., float and integer values).
663
664 ``IsAPowerOf``
665     Takes an integer argument and when called as a validator, checks that the
666     field being validated is a power of the integer.
667
668 ``IsValidDecimal``
669     Takes a maximum number of digits and number of decimal places (in that
670     order) and validates whether the field is a decimal with no more than the
671     maximum number of digits and decimal places.
672
673 ``MatchesRegularExpression``
674     Takes a regular expression (a string) as a parameter and validates the
675     field value against it.
676
677 ``AnyValidator``
678     Takes a list of validators as a parameter. At validation time, if the
679     field successfully validates against any one of the validators, it passes
680     validation. The validators are tested in the order specified in the
681     original list.
682
683 ``URLMimeTypeCheck``
684     Used to validate URL fields. Takes a list of MIME types (such as
685     ``text/plain``) at creation time. At validation time, it verifies that the
686     field is indeed a URL and then tries to retrieve the content at the URL.
687     Validation succeeds if the content could be retrieved and it has a content
688     type from the list used to create the validator.
689
690 ``RelaxNGCompact``
691     Used to validate an XML document against a Relax NG compact schema. Takes
692     a file path to the location of the schema and an optional root element
693     (which is wrapped around the XML fragment before validation, if supplied).
694     At validation time, the XML fragment is validated against the schema using
695     the executable specified in the ``JING_PATH`` setting (see the settings_
696     document for more details).
697
698 .. _`generic views`: ../generic_views/
699 .. _`models API`: ../model-api/
700 .. _settings: ../settings/
Note: See TracBrowser for help on using the browser.