123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- .. _obsolete-forms:
- ===============================
- Forms, fields, and manipulators
- ===============================
- Forwards-compatibility note
- ===========================
- The legacy forms/manipulators system described in this document is going to be
- replaced in the next Django release. If you're starting from scratch, we
- strongly encourage you not to waste your time learning this. Instead, learn and
- use the new :ref:`forms library <topics-forms-index>`.
- Introduction
- ============
- Once you've got a chance to play with Django's admin interface, you'll probably
- wonder if the fantastic form validation framework it uses is available to user
- code. It is, and this document explains how the framework works.
- We'll take a top-down approach to examining Django's form validation framework,
- because much of the time you won't need to use the lower-level APIs. Throughout
- this document, we'll be working with the following model, a "place" object::
- from django.db import models
- PLACE_TYPES = (
- (1, 'Bar'),
- (2, 'Restaurant'),
- (3, 'Movie Theater'),
- (4, 'Secret Hideout'),
- )
- class Place(models.Model):
- name = models.CharField(max_length=100)
- address = models.CharField(max_length=100, blank=True)
- city = models.CharField(max_length=50, blank=True)
- state = models.USStateField()
- zip_code = models.CharField(max_length=5, blank=True)
- place_type = models.IntegerField(choices=PLACE_TYPES)
- class Admin:
- pass
- def __unicode__(self):
- return self.name
- Defining the above class is enough to create an admin interface to a ``Place``,
- but what if you want to allow public users to submit places?
- Automatic Manipulators
- ======================
- The highest-level interface for object creation and modification is the
- **automatic Manipulator** framework. An automatic manipulator is a utility
- class tied to a given model that "knows" how to create or modify instances of
- that model and how to validate data for the object. Automatic Manipulators come
- in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally
- they are quite similar, but the former knows how to create new instances of the
- model, while the latter modifies existing instances. Both types of classes are
- automatically created when you define a new class::
- >>> from mysite.myapp.models import Place
- >>> Place.AddManipulator
- <class 'django.models.manipulators.AddManipulator'>
- >>> Place.ChangeManipulator
- <class 'django.models.manipulators.ChangeManipulator'>
- Using the ``AddManipulator``
- ----------------------------
- We'll start with the ``AddManipulator``. Here's a very simple view that takes
- POSTed data from the browser and creates a new ``Place`` object::
- from django.shortcuts import render_to_response
- from django.http import Http404, HttpResponse, HttpResponseRedirect
- from django import oldforms as forms
- from mysite.myapp.models import Place
- def naive_create_place(request):
- """A naive approach to creating places; don't actually use this!"""
- # Create the AddManipulator.
- manipulator = Place.AddManipulator()
- # Make a copy of the POSTed data so that do_html2python can
- # modify it in place (request.POST is immutable).
- new_data = request.POST.copy()
- # Convert the request data (which will all be strings) into the
- # appropriate Python types for those fields.
- manipulator.do_html2python(new_data)
- # Save the new object.
- new_place = manipulator.save(new_data)
- # It worked!
- return HttpResponse("Place created: %s" % new_place)
- The ``naive_create_place`` example works, but as you probably can tell, this
- view has a number of problems:
- * No validation of any sort is performed. If, for example, the ``name`` field
- isn't given in ``request.POST``, the save step will cause a database error
- because that field is required. Ugly.
- * Even if you *do* perform validation, there's still no way to give that
- information to the user in any sort of useful way.
- * You'll have to separately create a form (and view) that submits to this
- page, which is a pain and is redundant.
- Let's dodge these problems momentarily to take a look at how you could create a
- view with a form that submits to this flawed creation view::
- def naive_create_place_form(request):
- """Simplistic place form view; don't actually use anything like this!"""
- # Create a FormWrapper object that the template can use. Ignore
- # the last two arguments to FormWrapper for now.
- form = forms.FormWrapper(Place.AddManipulator(), {}, {})
- return render_to_response('places/naive_create_form.html', {'form': form})
- (This view, as well as all the following ones, has the same imports as in the
- first example above.)
- The ``forms.FormWrapper`` object is a wrapper that templates can
- easily deal with to create forms. Here's the ``naive_create_form.html``
- template::
- {% extends "base.html" %}
- {% block content %}
- <h1>Create a place:</h1>
- <form method="post" action="../do_new/">
- <p><label for="id_name">Name:</label> {{ form.name }}</p>
- <p><label for="id_address">Address:</label> {{ form.address }}</p>
- <p><label for="id_city">City:</label> {{ form.city }}</p>
- <p><label for="id_state">State:</label> {{ form.state }}</p>
- <p><label for="id_zip_code">Zip:</label> {{ form.zip_code }}</p>
- <p><label for="id_place_type">Place type:</label> {{ form.place_type }}</p>
- <input type="submit" />
- </form>
- {% endblock %}
- Before we get back to the problems with these naive set of views, let's go over
- some salient points of the above template:
- * Field "widgets" are handled for you: ``{{ form.field }}`` automatically
- creates the "right" type of widget for the form, as you can see with the
- ``place_type`` field above.
- * There isn't a way just to spit out the form. You'll still need to define
- how the form gets laid out. This is a feature: Every form should be
- designed differently. Django doesn't force you into any type of mold.
- If you must use tables, use tables. If you're a semantic purist, you can
- probably find better HTML than in the above template.
- * To avoid name conflicts, the ``id`` values of form elements take the
- form "id_*fieldname*".
- By creating a creation form we've solved problem number 3 above, but we still
- don't have any validation. Let's revise the validation issue by writing a new
- creation view that takes validation into account::
- def create_place_with_validation(request):
- manipulator = Place.AddManipulator()
- new_data = request.POST.copy()
- # Check for validation errors
- errors = manipulator.get_validation_errors(new_data)
- manipulator.do_html2python(new_data)
- if errors:
- return render_to_response('places/errors.html', {'errors': errors})
- else:
- new_place = manipulator.save(new_data)
- return HttpResponse("Place created: %s" % new_place)
- In this new version, errors will be found -- ``manipulator.get_validation_errors``
- handles all the validation for you -- and those errors can be nicely presented
- on an error page (templated, of course)::
- {% extends "base.html" %}
- {% block content %}
- <h1>Please go back and correct the following error{{ errors|pluralize }}:</h1>
- <ul>
- {% for e in errors.items %}
- <li>Field "{{ e.0 }}": {{ e.1|join:", " }}</li>
- {% endfor %}
- </ul>
- {% endblock %}
- Still, this has its own problems:
- * There's still the issue of creating a separate (redundant) view for the
- submission form.
- * Errors, though nicely presented, are on a separate page, so the user will
- have to use the "back" button to fix errors. That's ridiculous and unusable.
- The best way to deal with these issues is to collapse the two views -- the form
- and the submission -- into a single view. This view will be responsible for
- creating the form, validating POSTed data, and creating the new object (if the
- data is valid). An added bonus of this approach is that errors and the form will
- both be available on the same page, so errors with fields can be presented in
- context.
- .. admonition:: Philosophy:
- Finally, for the HTTP purists in the audience (and the authorship), this
- nicely matches the "true" meanings of HTTP GET and HTTP POST: GET fetches
- the form, and POST creates the new object.
- Below is the finished view::
- def create_place(request):
- manipulator = Place.AddManipulator()
- if request.method == 'POST':
- # If data was POSTed, we're trying to create a new Place.
- new_data = request.POST.copy()
- # Check for errors.
- errors = manipulator.get_validation_errors(new_data)
- manipulator.do_html2python(new_data)
- if not errors:
- # No errors. This means we can save the data!
- new_place = manipulator.save(new_data)
- # Redirect to the object's "edit" page. Always use a redirect
- # after POST data, so that reloads don't accidentally create
- # duplicate entries, and so users don't see the confusing
- # "Repost POST data?" alert box in their browsers.
- return HttpResponseRedirect("/places/edit/%i/" % new_place.id)
- else:
- # No POST, so we want a brand new form without any data or errors.
- errors = new_data = {}
- # Create the FormWrapper, template, context, response.
- form = forms.FormWrapper(manipulator, new_data, errors)
- return render_to_response('places/create_form.html', {'form': form})
- and here's the ``create_form`` template::
- {% extends "base.html" %}
- {% block content %}
- <h1>Create a place:</h1>
- {% if form.has_errors %}
- <h2>Please correct the following error{{ form.error_dict|pluralize }}:</h2>
- {% endif %}
- <form method="post" action=".">
- <p>
- <label for="id_name">Name:</label> {{ form.name }}
- {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
- </p>
- <p>
- <label for="id_address">Address:</label> {{ form.address }}
- {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
- </p>
- <p>
- <label for="id_city">City:</label> {{ form.city }}
- {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
- </p>
- <p>
- <label for="id_state">State:</label> {{ form.state }}
- {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
- </p>
- <p>
- <label for="id_zip_code">Zip:</label> {{ form.zip_code }}
- {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
- </p>
- <p>
- <label for="id_place_type">Place type:</label> {{ form.place_type }}
- {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
- </p>
- <input type="submit" />
- </form>
- {% endblock %}
- The second two arguments to ``FormWrapper`` (``new_data`` and ``errors``)
- deserve some mention.
- The first is any "default" data to be used as values for the fields. Pulling
- the data from ``request.POST``, as is done above, makes sure that if there are
- errors, the values the user put in aren't lost. If you try the above example,
- you'll see this in action.
- The second argument is the error list retrieved from
- ``manipulator.get_validation_errors``. When passed into the ``FormWrapper``,
- this gives each field an ``errors`` item (which is a list of error messages
- associated with the field) as well as a ``html_error_list`` item, which is a
- ``<ul>`` of error messages. The above template uses these error items to
- display a simple error message next to each field. The error list is saved as
- an ``error_dict`` attribute of the ``FormWrapper`` object.
- Using the ``ChangeManipulator``
- -------------------------------
- The above has covered using the ``AddManipulator`` to create a new object. What
- about editing an existing one? It's shockingly similar to creating a new one::
- def edit_place(request, place_id):
- # Get the place in question from the database and create a
- # ChangeManipulator at the same time.
- try:
- manipulator = Place.ChangeManipulator(place_id)
- except Place.DoesNotExist:
- raise Http404
- # Grab the Place object in question for future use.
- place = manipulator.original_object
- if request.method == 'POST':
- new_data = request.POST.copy()
- errors = manipulator.get_validation_errors(new_data)
- manipulator.do_html2python(new_data)
- if not errors:
- manipulator.save(new_data)
- # Do a post-after-redirect so that reload works, etc.
- return HttpResponseRedirect("/places/edit/%i/" % place.id)
- else:
- errors = {}
- # This makes sure the form accurate represents the fields of the place.
- new_data = manipulator.flatten_data()
- form = forms.FormWrapper(manipulator, new_data, errors)
- return render_to_response('places/edit_form.html', {'form': form, 'place': place})
- The only real differences are:
- * We create a ``ChangeManipulator`` instead of an ``AddManipulator``.
- The argument to a ``ChangeManipulator`` is the ID of the object
- to be changed. As you can see, the initializer will raise an
- ``ObjectDoesNotExist`` exception if the ID is invalid.
- * ``ChangeManipulator.original_object`` stores the instance of the
- object being edited.
- * We set ``new_data`` based upon ``flatten_data()`` from the manipulator.
- ``flatten_data()`` takes the data from the original object under
- manipulation, and converts it into a data dictionary that can be used
- to populate form elements with the existing values for the object.
- * The above example uses a different template, so create and edit can be
- "skinned" differently if needed, but the form chunk itself is completely
- identical to the one in the create form above.
- The astute programmer will notice the add and create functions are nearly
- identical and could in fact be collapsed into a single view. This is left as an
- exercise for said programmer.
- (However, the even-more-astute programmer will take heed of the note at the top
- of this document and check out the :ref:`generic views <ref-generic-views>`
- documentation if all she wishes to do is this type of simple create/update.)
- Custom forms and manipulators
- =============================
- All the above is fine and dandy if you just want to use the automatically
- created manipulators. But the coolness doesn't end there: You can easily create
- your own custom manipulators for handling custom forms.
- Custom manipulators are pretty simple. Here's a manipulator that you might use
- for a "contact" form on a website::
- from django import oldforms as forms
- urgency_choices = (
- (1, "Extremely urgent"),
- (2, "Urgent"),
- (3, "Normal"),
- (4, "Unimportant"),
- )
- class ContactManipulator(forms.Manipulator):
- def __init__(self):
- self.fields = (
- forms.EmailField(field_name="from", is_required=True),
- forms.TextField(field_name="subject", length=30, max_length=200, is_required=True),
- forms.SelectField(field_name="urgency", choices=urgency_choices),
- forms.LargeTextField(field_name="contents", is_required=True),
- )
- A certain similarity to Django's models should be apparent. The only required
- method of a custom manipulator is ``__init__`` which must define the fields
- present in the manipulator. See the ``django.forms`` module for
- all the form fields provided by Django.
- You use this custom manipulator exactly as you would use an auto-generated one.
- Here's a simple function that might drive the above form::
- def contact_form(request):
- manipulator = ContactManipulator()
- if request.method == 'POST':
- new_data = request.POST.copy()
- errors = manipulator.get_validation_errors(new_data)
- manipulator.do_html2python(new_data)
- if not errors:
- # Send e-mail using new_data here...
- return HttpResponseRedirect("/contact/thankyou/")
- else:
- errors = new_data = {}
- form = forms.FormWrapper(manipulator, new_data, errors)
- return render_to_response('contact_form.html', {'form': form})
- Implementing ``flatten_data`` for custom manipulators
- ------------------------------------------------------
- It is possible (although rarely needed) to replace the default automatically
- created manipulators on a model with your own custom manipulators. If you do
- this and you are intending to use those models in generic views, you should
- also define a ``flatten_data`` method in any ``ChangeManipulator`` replacement.
- This should act like the default ``flatten_data`` and return a dictionary
- mapping field names to their values, like so::
- def flatten_data(self):
- obj = self.original_object
- return dict(
- from = obj.from,
- subject = obj.subject,
- ...
- )
- In this way, your new change manipulator will act exactly like the default
- version.
- ``FileField`` and ``ImageField`` special cases
- ==============================================
- Dealing with ``FileField`` and ``ImageField`` objects is a little more
- complicated.
- First, you'll need to make sure that your ``<form>`` element correctly defines
- the ``enctype`` as ``"multipart/form-data"``, in order to upload files::
- <form enctype="multipart/form-data" method="post" action="/foo/">
- Next, you'll need to treat the field in the template slightly differently. A
- ``FileField`` or ``ImageField`` is represented by *two* HTML form elements.
- For example, given this field in a model::
- photo = model.ImageField('/path/to/upload/location')
- You'd need to display two formfields in the template::
- <p><label for="id_photo">Photo:</label> {{ form.photo }}{{ form.photo_file }}</p>
- The first bit (``{{ form.photo }}``) displays the currently-selected file,
- while the second (``{{ form.photo_file }}``) actually contains the file upload
- form field. Thus, at the validation layer you need to check the ``photo_file``
- key.
- Finally, in your view, make sure to access ``request.FILES``, rather than
- ``request.POST``, for the uploaded files. This is necessary because
- ``request.POST`` does not contain file-upload data.
- For example, following the ``new_data`` convention, you might do something like
- this::
- new_data = request.POST.copy()
- new_data.update(request.FILES)
- Validators
- ==========
- One useful feature of manipulators is the automatic validation. Validation is
- done using a simple validation API: A validator is a callable that raises a
- ``ValidationError`` if there's something wrong with the data.
- ``django.core.validators`` defines a host of validator functions (see below),
- but defining your own couldn't be easier::
- from django.core import validators
- from django import oldforms as forms
- class ContactManipulator(forms.Manipulator):
- def __init__(self):
- self.fields = (
- # ... snip fields as above ...
- forms.EmailField(field_name="to", validator_list=[self.isValidToAddress])
- )
- def isValidToAddress(self, field_data, all_data):
- if not field_data.endswith("@example.com"):
- raise validators.ValidationError("You can only send messages to example.com e-mail addresses.")
- Above, we've added a "to" field to the contact form, but required that the "to"
- address end with "@example.com" by adding the ``isValidToAddress`` validator to
- the field's ``validator_list``.
- The arguments to a validator function take a little explanation. ``field_data``
- is the value of the field in question, and ``all_data`` is a dictionary of all
- the data being validated.
- .. admonition:: Note::
- At the point validators are called all data will still be
- strings (as ``do_html2python`` hasn't been called yet).
- Also, because consistency in user interfaces is important, we strongly urge you
- to put punctuation at the end of your validation messages.
- When are validators called?
- ---------------------------
- After a form has been submitted, Django validates each field in turn. First,
- if the field is required, Django checks that it is present and non-empty. Then,
- if that test passes *and the form submission contained data* for that field, all
- the validators for that field are called in turn. The emphasized portion in the
- last sentence is important: if a form field is not submitted (because it
- contains no data -- which is normal HTML behavior), the validators are not
- run against the field.
- This feature is particularly important for models using
- ``models.BooleanField`` or custom manipulators using things like
- ``forms.CheckBoxField``. If the checkbox is not selected, it will not
- contribute to the form submission.
- If you would like your validator to run *always*, regardless of whether its
- attached field contains any data, set the ``always_test`` attribute on the
- validator function. For example::
- def my_custom_validator(field_data, all_data):
- # ...
- my_custom_validator.always_test = True
- This validator will always be executed for any field it is attached to.
- Ready-made validators
- ---------------------
- Writing your own validator is not difficult, but there are some situations
- that come up over and over again. Django comes with a number of validators
- that can be used directly in your code. All of these functions and classes
- reside in ``django/core/validators.py``.
- The following validators should all be self-explanatory. Each one provides a
- check for the given property:
- * isAlphaNumeric
- * isAlphaNumericURL
- * isSlug
- * isLowerCase
- * isUpperCase
- * isCommaSeparatedIntegerList
- * isCommaSeparatedEmailList
- * isValidIPAddress4
- * isNotEmpty
- * isOnlyDigits
- * isNotOnlyDigits
- * isInteger
- * isOnlyLetters
- * isValidANSIDate
- * isValidANSITime
- * isValidEmail
- * isValidFloat
- * isValidImage
- * isValidImageURL
- * isValidPhone
- * isValidQuicktimeVideoURL
- * isValidURL
- * isValidHTML
- * isWellFormedXml
- * isWellFormedXmlFragment
- * isExistingURL
- * isValidUSState
- * hasNoProfanities
- There are also a group of validators that are slightly more flexible. For
- these validators, you create a validator instance, passing in the parameters
- described below. The returned object is a callable that can be used as a
- validator.
- For example::
- from django.core import validators
- from django import oldforms as forms
- power_validator = validators.IsAPowerOf(2)
- class InstallationManipulator(forms.Manipulator)
- def __init__(self):
- self.fields = (
- ...
- forms.IntegerField(field_name = "size", validator_list=[power_validator])
- )
- Here, ``validators.IsAPowerOf(...)`` returned something that could be used as
- a validator (in this case, a check that a number was a power of 2).
- Each of the standard validators that take parameters have an optional final
- argument (``error_message``) that is the message returned when validation
- fails. If no message is passed in, a default message is used.
- ``AlwaysMatchesOtherField``
- Takes a field name and the current field is valid if and only if its value
- matches the contents of the other field.
- ``ValidateIfOtherFieldEquals``
- Takes three parameters: ``other_field``, ``other_value`` and
- ``validator_list``, in that order. If ``other_field`` has a value of
- ``other_value``, then the validators in ``validator_list`` are all run
- against the current field.
- ``RequiredIfOtherFieldGiven``
- Takes a field name of the current field is only required if the other
- field has a value.
- ``RequiredIfOtherFieldsGiven``
- Similar to ``RequiredIfOtherFieldGiven``, except that it takes a list of
- field names and if any one of the supplied fields has a value provided,
- the current field being validated is required.
- ``RequiredIfOtherFieldNotGiven``
- Takes the name of the other field and this field is only required if the
- other field has no value.
- ``RequiredIfOtherFieldEquals`` and ``RequiredIfOtherFieldDoesNotEqual``
- Each of these validator classes takes a field name and a value (in that
- order). If the given field does (or does not have, in the latter case) the
- given value, then the current field being validated is required.
- An optional ``other_label`` argument can be passed which, if given, is used
- in error messages instead of the value. This allows more user friendly error
- messages if the value itself is not descriptive enough.
- Note that because validators are called before any ``do_html2python()``
- functions, the value being compared against is a string. So
- ``RequiredIfOtherFieldEquals('choice', '1')`` is correct, whilst
- ``RequiredIfOtherFieldEquals('choice', 1)`` will never result in the
- equality test succeeding.
- ``IsLessThanOtherField``
- Takes a field name and validates that the current field being validated
- has a value that is less than (or equal to) the other field's value.
- Again, comparisons are done using strings, so be cautious about using
- this function to compare data that should be treated as another type. The
- string "123" is less than the string "2", for example. If you don't want
- string comparison here, you will need to write your own validator.
- ``NumberIsInRange``
- Takes two boundary numbers, ``lower`` and ``upper``, and checks that the
- field is greater than ``lower`` (if given) and less than ``upper`` (if
- given).
- Both checks are inclusive. That is, ``NumberIsInRange(10, 20)`` will allow
- values of both 10 and 20. This validator only checks numeric values
- (e.g., float and integer values).
- ``IsAPowerOf``
- Takes an integer argument and when called as a validator, checks that the
- field being validated is a power of the integer.
- ``IsValidDecimal``
- Takes a maximum number of digits and number of decimal places (in that
- order) and validates whether the field is a decimal with no more than the
- maximum number of digits and decimal places.
- ``MatchesRegularExpression``
- Takes a regular expression (a string) as a parameter and validates the
- field value against it.
- ``AnyValidator``
- Takes a list of validators as a parameter. At validation time, if the
- field successfully validates against any one of the validators, it passes
- validation. The validators are tested in the order specified in the
- original list.
- ``URLMimeTypeCheck``
- Used to validate URL fields. Takes a list of MIME types (such as
- ``text/plain``) at creation time. At validation time, it verifies that the
- field is indeed a URL and then tries to retrieve the content at the URL.
- Validation succeeds if the content could be retrieved and it has a content
- type from the list used to create the validator.
- ``RelaxNGCompact``
- Used to validate an XML document against a Relax NG compact schema. Takes a
- file path to the location of the schema and an optional root element (which
- is wrapped around the XML fragment before validation, if supplied). At
- validation time, the XML fragment is validated against the schema using the
- executable specified in the ``JING_PATH`` setting (see the :ref:`settings
- <ref-settings>` document for more details).
|