Django

Code

root/django/tags/releases/0.96/docs/forms.txt

Revision 4711, 27.3 kB (checked in by mtredinnick, 2 years ago)

Fixed #1049 -- Documented the need to implement flatten_data() in some cases for custom
ChangeManipulator? replacements. Thanks, Michael Radziej.

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(maxlength=100)
41         address = models.CharField(maxlength=100, blank=True)
42         city = models.CharField(maxlength=50, blank=True)
43         state = models.USStateField()
44         zip_code = models.CharField(maxlength=5, blank=True)
45         place_type = models.IntegerField(choices=PLACE_TYPES)
46
47         class Admin:
48             pass
49
50         def __str__(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, maxlength=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 first checks to see that all the
521 required fields are present and non-empty. For each field that passes that
522 test *and if the form submission contained data* for that field, all the
523 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     * isValidImage
571     * isValidImageURL
572     * isValidPhone
573     * isValidQuicktimeVideoURL
574     * isValidURL
575     * isValidHTML
576     * isWellFormedXml
577     * isWellFormedXmlFragment
578     * isExistingURL
579     * isValidUSState
580     * hasNoProfanities
581
582 There are also a group of validators that are slightly more flexible. For
583 these validators, you create a validator instance, passing in the parameters
584 described below. The returned object is a callable that can be used as a
585 validator.
586
587 For example::
588
589     from django.core import validators
590     from django import forms
591
592     power_validator = validators.IsAPowerOf(2)
593
594     class InstallationManipulator(forms.Manipulator)
595         def __init__(self):
596             self.fields = (
597                 ...
598                 forms.IntegerField(field_name = "size", validator_list=[power_validator])
599             )
600
601 Here, ``validators.IsAPowerOf(...)`` returned something that could be used as
602 a validator (in this case, a check that a number was a power of 2).
603
604 Each of the standard validators that take parameters have an optional final
605 argument (``error_message``) that is the message returned when validation
606 fails. If no message is passed in, a default message is used.
607
608 ``AlwaysMatchesOtherField``
609     Takes a field name and the current field is valid if and only if its value
610     matches the contents of the other field.
611
612 ``ValidateIfOtherFieldEquals``
613     Takes three parameters: ``other_field``, ``other_value`` and
614     ``validator_list``, in that order. If ``other_field`` has a value of
615     ``other_value``, then the validators in ``validator_list`` are all run
616     against the current field.
617
618 ``RequiredIfOtherFieldNotGiven``
619     Takes the name of the other field and this field is only required if the
620     other field has no value.
621
622 ``RequiredIfOtherFieldsNotGiven``
623     Similar to ``RequiredIfOtherFieldNotGiven``, except that it takes a list
624     of field names and if any one of the supplied fields does not have a value
625     provided, the field being validated is required.
626
627 ``RequiredIfOtherFieldEquals`` and ``RequiredIfOtherFieldDoesNotEqual``
628     Each of these validator classes takes a field name and a value (in that
629     order). If the given field does (or does not have, in the latter case) the
630     given value, then the current field being validated is required.
631
632     An optional ``other_label`` argument can be passed which, if given, is used
633     in error messages instead of the value. This allows more user friendly error
634     messages if the value itself is not descriptive enough.
635
636     Note that because validators are called before any ``do_html2python()``
637     functions, the value being compared against is a string. So
638     ``RequiredIfOtherFieldEquals('choice', '1')`` is correct, whilst
639     ``RequiredIfOtherFieldEquals('choice', 1)`` will never result in the
640     equality test succeeding.
641
642 ``IsLessThanOtherField``
643     Takes a field name and validates that the current field being validated
644     has a value that is less than (or equal to) the other field's value.
645     Again, comparisons are done using strings, so be cautious about using
646     this function to compare data that should be treated as another type. The
647     string "123" is less than the string "2", for example. If you don't want
648     string comparison here, you will need to write your own validator.
649
650 ``NumberIsInRange``
651     Takes two boundary numbers, ``lower`` and ``upper``, and checks that the
652     field is greater than ``lower`` (if given) and less than ``upper`` (if
653     given). 
654    
655     Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow
656     values of both 10 and 20. This validator only checks numeric values
657     (e.g., float and integer values).
658
659 ``IsAPowerOf``
660     Takes an integer argument and when called as a validator, checks that the
661     field being validated is a power of the integer.
662
663 ``IsValidFloat``
664     Takes a maximum number of digits and number of decimal places (in that
665     order) and validates whether the field is a float with less than the
666     maximum number of digits and decimal place.
667
668 ``MatchesRegularExpression``
669     Takes a regular expression (a string) as a parameter and validates the
670     field value against it.
671
672 ``AnyValidator``
673     Takes a list of validators as a parameter. At validation time, if the
674     field successfully validates against any one of the validators, it passes
675     validation. The validators are tested in the order specified in the
676     original list.
677
678 ``URLMimeTypeCheck``
679     Used to validate URL fields. Takes a list of MIME types (such as
680     ``text/plain``) at creation time. At validation time, it verifies that the
681     field is indeed a URL and then tries to retrieve the content at the URL.
682     Validation succeeds if the content could be retrieved and it has a content
683     type from the list used to create the validator.
684
685 ``RelaxNGCompact``
686     Used to validate an XML document against a Relax NG compact schema. Takes
687     a file path to the location of the schema and an optional root element
688     (which is wrapped around the XML fragment before validation, if supplied).
689     At validation time, the XML fragment is validated against the schema using
690     the executable specified in the ``JING_PATH`` setting (see the settings_
691     document for more details).
692
693 .. _`generic views`: ../generic_views/
694 .. _`models API`: ../model_api/
695 .. _settings: ../settings/
Note: See TracBrowser for help on using the browser.