Copy-edited docs from [303] and [304]
git-svn-id: http://code.djangoproject.com/svn/django/trunk@306 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
1c947e50c9
commit
f6c4395329
339
docs/forms.txt
339
docs/forms.txt
|
@ -4,37 +4,37 @@ Forms, fields, and manipulators
|
|||
|
||||
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.
|
||||
code. It is, and this document explains how the framework works.
|
||||
|
||||
.. admonition:: A note to the lazy
|
||||
|
||||
If all you want to do is present forms for a user to create and/or
|
||||
update a given object, don't read any further but instead click thyself
|
||||
over to the `generic views`_ documentation. The following exercises are
|
||||
for those interested in how Django's form framework works and those
|
||||
needing to do more than simple create/update.
|
||||
|
||||
We'll take a top-down approach to examining Django's form validation framework
|
||||
since much of the time you won't need to use the lower-level APIs. Throughout
|
||||
If all you want to do is present forms for a user to create and/or
|
||||
update a given object, don't read any further. Instead, click thyself
|
||||
to the `generic views`_ documentation. The following exercises are
|
||||
for those interested in how Django's form framework works and those
|
||||
needing to do more than simple creation/updating.
|
||||
|
||||
We'll take a top-down approach to examining Django's form validation framework,
|
||||
becuase 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::
|
||||
|
||||
|
||||
PLACE_TYPES = (
|
||||
(1, 'Bar'),
|
||||
(2, 'Restaurant'),
|
||||
(3, 'Movie Theater'),
|
||||
(4, 'Secret Hideout'),
|
||||
)
|
||||
|
||||
|
||||
class Place(meta.Model):
|
||||
fields = (
|
||||
meta.CharField('name', 'name', maxlength=100),
|
||||
meta.CharField('address', 'address', maxlength=100, blank=True),
|
||||
meta.CharField('city', 'city', maxlength=50, blank=True),
|
||||
meta.USStateField('state', 'state'),
|
||||
meta.CharField('zip_code', 'zip code', maxlength=5, blank=True),
|
||||
meta.IntegerField('place_type', 'place type', choices=PLACE_TYPES)
|
||||
meta.CharField('name', maxlength=100),
|
||||
meta.CharField('address', maxlength=100, blank=True),
|
||||
meta.CharField('city', maxlength=50, blank=True),
|
||||
meta.USStateField('state'),
|
||||
meta.CharField('zip_code', maxlength=5, blank=True),
|
||||
meta.IntegerField('place_type', choices=PLACE_TYPES)
|
||||
)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -47,8 +47,8 @@ Manipulators
|
|||
The highest-level interface for object creation and modification is the
|
||||
**Manipulator** framework. A 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. Manipulators come in two flavors:
|
||||
``AddManipulators`` and ``ChangeManipulators``. Functionally they are quite
|
||||
validate data for the object. 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 later modifies existing instances. Both types of classes are automatically
|
||||
created when you define a new class::
|
||||
|
@ -58,10 +58,10 @@ created when you define a new class::
|
|||
<class django.models.places.PlaceManipulatorAdd at 0x4c1540>
|
||||
>>> places.ChangeManipulator
|
||||
<class django.models.places.PlaceManipulatorChange at 0x4c1630>
|
||||
|
||||
|
||||
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::
|
||||
|
||||
|
@ -74,61 +74,63 @@ POSTed data from the browser and creates a new ``Place`` object::
|
|||
|
||||
def naive_create_place(request):
|
||||
"""A naive approach to creating places; don't actually use this!"""
|
||||
# Create the AddManipulator
|
||||
# Create the AddManipulator.
|
||||
manipulator = places.AddManipulator()
|
||||
|
||||
|
||||
# Make a copy of the POSTed data so that do_html2python can
|
||||
# modify it in place (request.POST is immutable)
|
||||
# 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
|
||||
# appropriate Python types for those fields.
|
||||
manipulator.do_html2python(new_data)
|
||||
|
||||
# Save the new object
|
||||
|
||||
# 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 (somewhat), but as you probably can
|
||||
tell, there's all sorts of problems (some more subtle than others) with this view:
|
||||
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
|
||||
* 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.
|
||||
because that field is required. Ugly.
|
||||
|
||||
* Even if you *do* perform validation, there's still no way to give that
|
||||
information to the user is any sort of useful way.
|
||||
|
||||
* You'll have to separate create a form (and view) that submits to this
|
||||
page, which is a pain and is redundant.
|
||||
|
||||
* Even if you *do* perform validation, there's still no way to give that information
|
||||
to the user is any sort of useful way.
|
||||
|
||||
* You'll have to separate 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 which the template can use; more
|
||||
# on what the second two arguments to FormWrapper do later.
|
||||
# Create a FormWrapper object that the template can use. Ignore
|
||||
# the last two arguments to FormWrapper for now.
|
||||
form = formfields.FormWrapper(places.AddManipulator(), {}, {})
|
||||
|
||||
# Create a template, context, and response
|
||||
|
||||
# Create a template, context and response.
|
||||
t = template_loader.get_template('places/naive_create_form')
|
||||
c = Context(request, {'form' : form})
|
||||
c = Context(request, {
|
||||
'form': form
|
||||
})
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
(This view, as well as all the following ones, have the same imports as the
|
||||
first example above does.)
|
||||
(This view, as well as all the following ones, has the same imports as in the
|
||||
first example above.)
|
||||
|
||||
The ``formfields.FormWrapper`` object is a wrapper that templates can
|
||||
easily deal with to create forms; here's the ``naive_create_form`` template::
|
||||
easily deal with to create forms. Here's the ``naive_create_form`` template::
|
||||
|
||||
{% extends "base" %}
|
||||
|
||||
|
||||
{% 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>
|
||||
|
@ -139,36 +141,38 @@ easily deal with to create forms; here's the ``naive_create_form`` template::
|
|||
<input type="submit" />
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
Before we get back to the problems with these naive set of views, let's go over
|
||||
|
||||
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 needs to be
|
||||
designed differently; Django doesn't force you into any type of mould.
|
||||
If you must use tables, use tables; if you're a semantic purist you can
|
||||
probably find better HTML than the above template.
|
||||
|
||||
* To avoid name conflicts, the ``id``s of form elements take the form
|
||||
* 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``s 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; if you enter bad data into any of the . Let's revise the validation
|
||||
issue by writing a new creation view that takes into account validation::
|
||||
|
||||
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 = places.AddManipulator()
|
||||
new_data = request.POST.copy()
|
||||
|
||||
|
||||
# Check for validation errors
|
||||
errors = manipulator.get_validation_errors(new_data)
|
||||
if errors:
|
||||
t = template_loader.get_template('places/errors')
|
||||
c = Context(request, {'errors' : errors}
|
||||
c = Context(request, {
|
||||
'errors': errors
|
||||
}
|
||||
return HttpResponse(t.render(c))
|
||||
else:
|
||||
manipulator.do_html2python(request.POST)
|
||||
|
@ -180,84 +184,87 @@ handles all the validation for you -- and those errors can be nicely presented
|
|||
on an error page (templated, of course)::
|
||||
|
||||
{% extends "base" %}
|
||||
|
||||
|
||||
{% 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 now has its own problems:
|
||||
Still, this has its own problems:
|
||||
|
||||
* There's still the issue of creating a seperate (redundant) view for the
|
||||
* There's still the issue of creating a separate (redundant) view for the
|
||||
submission form.
|
||||
|
||||
* Errors, though nicely presented are on a seperate page, so the user will have
|
||||
to use the "back" button to fix errors -- not exactly usable!
|
||||
|
||||
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 (should it the data be
|
||||
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.
|
||||
* 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, POST creates the new object.
|
||||
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 = places.AddManipulator()
|
||||
|
||||
|
||||
if request.POST:
|
||||
# If data was POSTed, we're trying to create a new Place
|
||||
# If data was POSTed, we're trying to create a new Place.
|
||||
new_data = request.POST.copy()
|
||||
|
||||
# Check for errors
|
||||
|
||||
# Check for errors.
|
||||
errors = manipulator.get_validation_errors(new_data)
|
||||
|
||||
|
||||
if not errors:
|
||||
# No errors -- this means we can save the data!
|
||||
# No errors. This means we can save the data!
|
||||
manipulator.do_html2python(new_data)
|
||||
new_place = manipulator.save(new_data)
|
||||
|
||||
# Redirect to the object's "edit" page (so that reloads
|
||||
# don't accidentally create duplicate entries)
|
||||
|
||||
# Redirect to the object's "edit" page. Always use a redirect
|
||||
# after POST data, so that reloads don't accidently create
|
||||
# duplicate entires, 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
|
||||
# No POST, so we want a brand new form without any data or errors.
|
||||
errors = new_data = {}
|
||||
|
||||
# Create the FormWrapper, template, context, response
|
||||
|
||||
# Create the FormWrapper, template, context, response.
|
||||
form = formfields.FormWrapper(manipulator, new_data, errors)
|
||||
t = template_loader.get_template("places/create_form")
|
||||
c = Context(request, {
|
||||
'form' : form,
|
||||
'form': form,
|
||||
})
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
and here's the ``create_form`` template::
|
||||
|
||||
{% extends "base" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h1>Create a place:</h1>
|
||||
|
||||
|
||||
{% if form.has_errors %}
|
||||
<h2>Please correct the following error{{ errors|pluralize }}:</h2>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<form method="post" action=".">
|
||||
<p>
|
||||
<label for="id_name">Name:</label> {{ form.name }}
|
||||
<label for="id_name">Name:</label> {{ form.name }}
|
||||
{% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
|
@ -287,104 +294,103 @@ and here's the ``create_form`` template::
|
|||
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 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.
|
||||
``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.
|
||||
|
||||
Using the ``ChangeManipulator``
|
||||
-------------------------------
|
||||
|
||||
So: the above has covered using the ``AddManipulator`` to create a new object;
|
||||
what about editing an existing one? It's rather shockingly similar to creating
|
||||
a new one::
|
||||
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
|
||||
# Get the place in question from the database and create a
|
||||
# ChangeManipulator at the same time.
|
||||
try:
|
||||
manipulator = places.ChangeManipulator(place_id)
|
||||
except places.PlaceDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
# Grab the Place object is question for future use
|
||||
|
||||
# Grab the Place object is question for future use.
|
||||
place = manipulator.original_object
|
||||
|
||||
|
||||
if request.POST:
|
||||
new_data = request.POST.copy()
|
||||
errors = manipulator.get_validation_errors(new_data)
|
||||
if not errors:
|
||||
manipulator.do_html2python(new_data)
|
||||
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 = place.__dict__
|
||||
|
||||
|
||||
form = formfields.FormWrapper(manipulator, new_data, errors)
|
||||
t = template_loader.get_template("places/edit_form")
|
||||
c = Context(request, {
|
||||
'form' : form,
|
||||
'place' : place,
|
||||
'form': form,
|
||||
'place': place,
|
||||
})
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
The only real differences here are:
|
||||
|
||||
* A ``ChangeManipulator`` instead of an ``AddManipulator`` is created;
|
||||
The argument to any ``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.
|
||||
|
||||
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`` to the original object's ``__dict__``; this makes
|
||||
sure that the form fields contain the current values of the object.
|
||||
|
||||
* We set ``new_data`` to the original object's ``__dict__``. This makes
|
||||
sure the form fields contain the current values of the object.
|
||||
``FormWrapper`` does not modify ``new_data`` in any way, and templates
|
||||
cannot, so this is perfectly safe.
|
||||
|
||||
* The above example uses a different template so that 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 that 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.
|
||||
|
||||
* 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 `generic views`_ documentation if all she
|
||||
wishes to do is this type of simple create/update).
|
||||
wishes to do is this type of simple create/update.)
|
||||
|
||||
Custom forms and manipulators
|
||||
=============================
|
||||
|
||||
All the above is fine and dandy if you want to just use the automatically created
|
||||
manipulators, but the coolness doesn't end there: you can easily create your
|
||||
own custom manipulators for handling custom forms.
|
||||
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
|
||||
Custom manipulators are pretty simple. Here's a manipulator that you might use
|
||||
for a "contact" form on a website::
|
||||
|
||||
from django.core import formfields
|
||||
|
||||
|
||||
urgency_choices = (
|
||||
(1, "Extremely urgent"),
|
||||
(2, "Urgent"),
|
||||
(3, "Normal"),
|
||||
(4, "Unimportant"),
|
||||
)
|
||||
|
||||
|
||||
class ContactManipulator(formfields.Manipulator):
|
||||
def __init__(self):
|
||||
self.fields = (
|
||||
|
@ -393,14 +399,14 @@ for a "contact" form on a website::
|
|||
formfields.IntegerField(field_name="urgency", choices=urgency_choices),
|
||||
formfields.LargeTextField(field_name="contents", is_required=True),
|
||||
)
|
||||
|
||||
A certain similarity to Django's models should be apparent. The only required
|
||||
|
||||
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.core.formfields`` 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::
|
||||
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 = ContactFormManipulator()
|
||||
|
@ -409,27 +415,27 @@ here's a simple function that might drive the above form::
|
|||
errors = manipulator.get_validation_errors(new_data)
|
||||
if not errors:
|
||||
manipulator.do_html2python(new_data)
|
||||
|
||||
# send email using new_data here...
|
||||
|
||||
|
||||
# Send e-mail using new_data here...
|
||||
|
||||
return HttpResponseRedirect("/contact/thankyou/")
|
||||
else:
|
||||
errors = new_data = {}
|
||||
form = formfields.FormWrapper(manipulator, new_data, errors)
|
||||
t = template_loader.get_template("contact_form")
|
||||
c = Context(request, {
|
||||
'form' : form,
|
||||
'form': form,
|
||||
})
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
Validators
|
||||
==========
|
||||
|
||||
One extremely useful feature of manipulators is the automatic validation it
|
||||
performs. Validation is done using a simple validation API: a validator is
|
||||
simple a callable that raises a ``ValidationError`` if there's something wrong
|
||||
with the data. ``django.core.validators`` defines a whole host of validator
|
||||
functions, but defining your own couldn't be easier::
|
||||
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, but defining
|
||||
your own couldn't be easier::
|
||||
|
||||
from django.core import validators, formfields
|
||||
|
||||
|
@ -439,18 +445,21 @@ functions, but defining your own couldn't be easier::
|
|||
# ... snip fields as above ...
|
||||
formfields.EmailField(field_name="to", validator_list=[self.isValidToAddress])
|
||||
)
|
||||
|
||||
|
||||
def isValidToAddress(self, field_data, all_data):
|
||||
if not field_data.endswith("@example.com"):
|
||||
raise ValidationError("You can only send messages to example.com email 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``.
|
||||
raise 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 dict of all the
|
||||
data being validated. Note that at the point validators are called all data
|
||||
will still be strings (as ``do_html2python`` hasn't been called yet).
|
||||
is the value of the field in question, and ``all_data`` is a dictionary of all
|
||||
the data being validated. Note that 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.
|
||||
|
||||
.. _`generic views`: http://www.djangoproject.com/documentation/generic_views/
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
Using generic views
|
||||
===================
|
||||
|
||||
Writing web applications can often be monotonous as we repeat certain patterns
|
||||
again and again. In Django, the most common of these patterns have been abstracted into
|
||||
"generic views" that let you quickly provide common views of object without actually
|
||||
needing to write any views.
|
||||
Writing Web applications can be monotonous, because we repeat certain patterns
|
||||
again and again. In Django, the most common of these patterns have been
|
||||
abstracted into "generic views" that let you quickly provide common views of
|
||||
an object without actually needing to write any views.
|
||||
|
||||
Django's generic views contain the following:
|
||||
|
||||
* A set of views for doing list/detail interfaces (for example,
|
||||
* A set of views for doing list/detail interfaces (for example,
|
||||
Django's `documentation index`_ and `detail pages`_).
|
||||
|
||||
|
||||
* A set of views for year/month/day archive pages and associated
|
||||
detail and "latest" pages (for example, the Django weblog's year_,
|
||||
month_, day_, detail_, and latest_ pages).
|
||||
|
||||
|
||||
* A set of views for creating, editing, and deleting objects.
|
||||
|
||||
.. _`documentation index`: http://www.djangoproject.com/documentation/
|
||||
|
@ -25,20 +25,20 @@ Django's generic views contain the following:
|
|||
.. _day: http://www.djangoproject.com/weblog/2005/jul/20/
|
||||
.. _detail: http://www.djangoproject.com/weblog/2005/jul/20/autoreload/
|
||||
.. _latest: http://www.djangoproject.com/weblog/
|
||||
|
||||
All of these views are used by creating configuration dictionaries in
|
||||
your urlconfig files and passing those dicts as the third member of the
|
||||
urlconf tuple. For example, here's the urlconf for the simple weblog
|
||||
app that drives the blog on djangoproject.com::
|
||||
|
||||
All of these views are used by creating configuration dictionaries in
|
||||
your URLconf files and passing those dictionaries as the third member of the
|
||||
URLconf tuple. For example, here's the URLconf for the simple weblog app that
|
||||
drives the blog on djangoproject.com::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
info_dict = {
|
||||
'app_label': 'blog',
|
||||
'app_label': 'blog',
|
||||
'module_name': 'entries',
|
||||
'date_field': 'pub_date',
|
||||
'date_field': 'pub_date',
|
||||
}
|
||||
|
||||
|
||||
urlpatterns = patterns('django.views.generic.date_based',
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>\w+)/$', 'object_detail', dict(info_dict, slug_field='slug')),
|
||||
(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', info_dict),
|
||||
|
@ -47,48 +47,50 @@ app that drives the blog on djangoproject.com::
|
|||
(r'^/?$', 'archive_index', info_dict),
|
||||
)
|
||||
|
||||
As you can see, this urlconf defines a few options in ``info_dict`` that tell
|
||||
As you can see, this URLconf defines a few options in ``info_dict`` that tell
|
||||
the generic view which model to use (``blog.entries`` in this case), as well as
|
||||
some extra information.
|
||||
|
||||
Documentation of each generic view follows along with a list of all keyword arguments
|
||||
that a generic view expects. Remember that as in the example above, arguments may
|
||||
either come from the URL pattern (as ``month``, ``day``, ``year``, etc. do above) or
|
||||
from the additional information dict (as for ``app_label``, ``module_name``, etc.).
|
||||
Documentation of each generic view follows, along with a list of all keyword
|
||||
arguments that a generic view expects. Remember that as in the example above,
|
||||
arguments may either come from the URL pattern (as ``month``, ``day``,
|
||||
``year``, etc. do above) or from the additional-information dictionary (as for
|
||||
``app_label``, ``module_name``, etc.).
|
||||
|
||||
All the generic views that follow require the ``app_label`` and ``module_name`` keys.
|
||||
These values are easiest to explain through example::
|
||||
|
||||
|
||||
>>> from django.models.blog import entries
|
||||
|
||||
In the above line, ``blog`` is the ``app_label`` (this is the name of the file that
|
||||
holds all your model definitions) and ``entries`` is the ``module_name`` (this is
|
||||
either a pluralized, lowercased version of the model class name or the value of
|
||||
the ``module_name`` option of your model). In the docs below, these keys will not
|
||||
be repeated, but each generic view requires them.
|
||||
|
||||
In the above line, ``blog`` is the ``app_label`` (the name of the file that
|
||||
holds all your model definitions) and ``entries`` is the ``module_name``
|
||||
(either a pluralized, lowercased version of the model class name, or the value
|
||||
of the ``module_name`` option of your model). In the docs below, these keys
|
||||
will not be repeated, but each generic view requires them.
|
||||
|
||||
Using date-based generic views
|
||||
==============================
|
||||
|
||||
Date-based generic views (in the module ``django.views.generic.date_based``)
|
||||
export six functions for dealing with date-based data. Besides ``app_label``
|
||||
and ``module_name``, all date-based generic views require that the ``date_field``
|
||||
argument to passed to them; this is the name of the field that stores the date
|
||||
the objects should key off of.
|
||||
feature six functions for dealing with date-based data. Besides ``app_label``
|
||||
and ``module_name``, all date-based generic views require that the
|
||||
``date_field`` argument be passed to them. This is the name of the field that
|
||||
stores the date the objects should key off of.
|
||||
|
||||
Additional, all date-based generic views have the following optional arguments:
|
||||
Additionally, all date-based generic views have the following optional
|
||||
arguments:
|
||||
|
||||
======================= ==================================================
|
||||
Argument Description
|
||||
======================= ==================================================
|
||||
``template_name`` Override the default template name used for the
|
||||
``template_name`` Overrides the default template name used for the
|
||||
view.
|
||||
|
||||
|
||||
``extra_lookup_kwargs`` A dictionary of extra lookup parameters (see
|
||||
the `database API docs`_).
|
||||
|
||||
``extra_context`` A dict of extra data to put into the template's
|
||||
context.
|
||||
|
||||
``extra_context`` A dictionary of extra data to put into the
|
||||
template's context.
|
||||
======================= ==================================================
|
||||
|
||||
.. _`database API docs`: http://www.djangoproject.com/documentation/db_api/
|
||||
|
@ -96,99 +98,99 @@ Additional, all date-based generic views have the following optional arguments:
|
|||
The date-based generic functions are:
|
||||
|
||||
``archive_index``
|
||||
A top-level index page showing the "latest" objects. Has an optional argument,
|
||||
``num_latest`` which is the number of items to display on the page (defaults
|
||||
to 15).
|
||||
|
||||
A top-level index page showing the "latest" objects. Has an optional
|
||||
argument, ``num_latest``, which is the number of items to display on the
|
||||
page (defaults to 15).
|
||||
|
||||
Uses the template ``app_label/module_name_archive`` by default.
|
||||
|
||||
|
||||
Has the following template context:
|
||||
|
||||
|
||||
``date_list``
|
||||
List of years with objects
|
||||
``latest``
|
||||
Latest objects by date
|
||||
|
||||
``archive_year``
|
||||
Yearly archive. Requires that the ``year`` argument be present in the URL
|
||||
Yearly archive. Requires that the ``year`` argument be present in the URL
|
||||
pattern.
|
||||
|
||||
|
||||
Uses the template ``app_label/module_name__archive_year`` by default.
|
||||
|
||||
|
||||
Has the following template context:
|
||||
|
||||
``date_list``
|
||||
List of months in this year with objects
|
||||
List of months in the given year with objects
|
||||
``year``
|
||||
This year
|
||||
|
||||
The given year (an integer)
|
||||
|
||||
``archive_month``
|
||||
Monthly archive; requires that ``year`` and ``month`` arguments be given.
|
||||
|
||||
Monthly archive. Requires that ``year`` and ``month`` arguments be given.
|
||||
|
||||
Uses the template ``app_label/module_name__archive_month`` by default.
|
||||
|
||||
|
||||
Has the following template context:
|
||||
|
||||
``month``
|
||||
(datetime object) this month
|
||||
The given month (a datetime.datetime object)
|
||||
``object_list``
|
||||
list of objects published in the given month
|
||||
List of objects published in the given month
|
||||
|
||||
``archive_day``
|
||||
Daily archive; requires that ``year``, ``month``, and ``day`` arguments
|
||||
be given.
|
||||
|
||||
Daily archive. Requires that ``year``, ``month``, and ``day`` arguments be
|
||||
given.
|
||||
|
||||
Uses the template ``app_label/module_name__archive_day`` by default.
|
||||
|
||||
Has the following template context:
|
||||
|
||||
``object_list``
|
||||
list of objects published this day
|
||||
``day``
|
||||
(datetime) the day
|
||||
``previous_day``
|
||||
(datetime) the previous day
|
||||
``next_day``
|
||||
(datetime) the next day, or None if the current day is today
|
||||
|
||||
``object_list``
|
||||
List of objects published this day
|
||||
``day``
|
||||
The given day (a datetime.datetime object)
|
||||
``previous_day``
|
||||
The previous day (a datetime.datetime object)
|
||||
``next_day``
|
||||
The next day (a datetime.datetime object), or None if the given
|
||||
day is today
|
||||
|
||||
``archive_today``
|
||||
List of objects for today; exactly the same as ``archive_day``, except
|
||||
that the year/month/day arguments are not given and today's date is
|
||||
used instead.
|
||||
|
||||
List of objects for today. Exactly the same as ``archive_day``, except
|
||||
the year/month/day arguments are not given, and today's date is used
|
||||
instead.
|
||||
|
||||
``object_detail``
|
||||
Individual object page; requires ``year``/``month``/``day`` arguments like
|
||||
``archive_day``. This function can be used with two types of URLs: either
|
||||
Individual object page. Requires ``year``/``month``/``day`` arguments like
|
||||
``archive_day``. This function can be used with two types of URLs: either
|
||||
``/year/month/day/slug/`` or ``/year/month/day/object_id/``.
|
||||
|
||||
|
||||
If you're using the slug-style URLs, you'll need to have a ``slug`` item in
|
||||
your urlconf, and you'll need to pass a ``slug_field`` key in your info
|
||||
dict to indicate the name of the slug field.
|
||||
|
||||
If your using the object_id-style URLs, you'll just need to have the URL
|
||||
pattern have an ``object_id`` field.
|
||||
|
||||
your URLconf, and you'll need to pass a ``slug_field`` key in your info
|
||||
dictionary to indicate the name of the slug field.
|
||||
|
||||
If your using the object_id-style URLs, you'll just need to give the URL
|
||||
pattern an ``object_id`` field.
|
||||
|
||||
You can also pass the ``template_name_field`` argument to indicate that the
|
||||
the object stores the name of its template in a field on the object itself.
|
||||
|
||||
|
||||
Using list/detail generic views
|
||||
===============================
|
||||
|
||||
The list-detail generic views (in the ``django.views.generic.list_detail`` module)
|
||||
are similar to the data-based ones, except the list-detail views simply have two
|
||||
views: a list of objects, and an individual object page.
|
||||
The list-detail generic views (in the ``django.views.generic.list_detail``
|
||||
module) are similar to the data-based ones, except the list-detail views simply
|
||||
have two views: a list of objects, and an individual object page.
|
||||
|
||||
All these views take the same three optional arguments as the date-based ones do
|
||||
All these views take the same three optional arguments as the date-based ones
|
||||
(and they obviously do not accept or require the date field argument).
|
||||
|
||||
Individual views are:
|
||||
|
||||
``object_list``
|
||||
List of objects.
|
||||
|
||||
|
||||
Takes the following optional arguments:
|
||||
|
||||
|
||||
======================= =================================================
|
||||
Argument Description
|
||||
======================= =================================================
|
||||
|
@ -196,106 +198,108 @@ Individual views are:
|
|||
objects with ``paginate_by`` objects per page.
|
||||
The view will expect a ``page`` GET param with
|
||||
the (zero-indexed) page number.
|
||||
|
||||
``allow_empty`` If ``False`` and there are no objects to display
|
||||
|
||||
``allow_empty`` If ``False`` and there are no objects to display,
|
||||
the view will raise a 404 instead of displaying
|
||||
an empty index page.
|
||||
an empty index page. ``False`` is default.
|
||||
======================= =================================================
|
||||
|
||||
Uses the template ``app_label/module_name__list`` by default.
|
||||
|
||||
|
||||
Has the following template context:
|
||||
|
||||
``object_list``
|
||||
list of objects
|
||||
List of objects
|
||||
``is_paginated``
|
||||
are the results paginated?
|
||||
|
||||
Are the results paginated? Either True or False
|
||||
|
||||
If the results are paginated, the context will have some extra variables:
|
||||
|
||||
|
||||
``results_per_page``
|
||||
number of objects per page
|
||||
Number of objects per page
|
||||
``has_next``
|
||||
is there a next page?
|
||||
Is there a next page?
|
||||
``has_previous``
|
||||
is there a prev page?
|
||||
Is there a previous page?
|
||||
``page``
|
||||
the current page
|
||||
The current page number
|
||||
``next``
|
||||
the next page
|
||||
The next page number
|
||||
``previous``
|
||||
the previous page
|
||||
The previous page
|
||||
``pages``
|
||||
number of pages, total
|
||||
Number of pages total
|
||||
|
||||
``object_detail``
|
||||
Object detail page. This works like and takes the same arguments as
|
||||
the date-based ``object_detail`` above, except this one obviously
|
||||
Object detail page. This works like and takes the same arguments as
|
||||
the date-based ``object_detail`` above, except this one, obviously,
|
||||
does not take the year/month/day arguments.
|
||||
|
||||
|
||||
Using create/update/delete generic views
|
||||
========================================
|
||||
|
||||
The ``django.views.generic.create_update`` module contains a set of functions
|
||||
for creating, editing, and deleting objects. These views take the same global
|
||||
arguments as the above sets of generic views; they also have a
|
||||
for creating, editing and deleting objects. These views take the same global
|
||||
arguments as the above sets of generic views. They also have a
|
||||
``login_required`` argument which, if ``True``, requires the user to be logged
|
||||
in to have access to the page (``login_required`` defaults to ``False``).
|
||||
|
||||
in to have access to the page. (``login_required`` defaults to ``False``.)
|
||||
|
||||
The create/update/delete views are:
|
||||
|
||||
``create_object``
|
||||
Create a new object. Has an extra optional argument, ``post_save_redirect``,
|
||||
which is a URL that the view will redirect to after saving the object
|
||||
(defaults to ``object.get_absolute_url()``).
|
||||
|
||||
``post_save_redirect`` may contain dictionary string formatting which will
|
||||
be interpolated against the object's dict (so you could use
|
||||
``post_save_redirect="/polls/%(slug)s/"``, for example).
|
||||
|
||||
Uses the template ``app_label/module_name__form`` by default (this is the
|
||||
same template as the ``update_object`` view below; your template can tell
|
||||
the different by the presence or absence of ``{{ object }}`` in the context.
|
||||
|
||||
Create a new object. Has an extra optional argument, ``post_save_redirect``,
|
||||
which is a URL to which the view will redirect after saving the object.
|
||||
It defaults to ``object.get_absolute_url()``.
|
||||
|
||||
``post_save_redirect`` may contain dictionary string formatting, which will
|
||||
be interpolated against the object's field attributes. For example, you
|
||||
could use ``post_save_redirect="/polls/%(slug)s/"``.
|
||||
|
||||
Uses the template ``app_label/module_name__form`` by default. This is the
|
||||
same template as the ``update_object`` view below. Your template can tell
|
||||
the different by the presence or absence of ``{{ object }}`` in the
|
||||
context.
|
||||
|
||||
Has the following template context:
|
||||
|
||||
form
|
||||
the form wrapper for the object
|
||||
|
||||
The form wrapper for the object
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
|
||||
See the `manipulator and formfield documentation`_ for more information
|
||||
about using form wrappers in templates.
|
||||
|
||||
.. _`manipulator and formfield documentation`: http://www.djangoproject.com/documentation/forms/
|
||||
|
||||
``update_object``
|
||||
Edit an existing object. Has the same extra slug/ID parameters as
|
||||
``list_detail.object_detail`` does (see above), and the same ``post_save_redirect``
|
||||
as ``create_object`` does.
|
||||
Edit an existing object. Has the same extra slug/ID parameters as
|
||||
``list_detail.object_detail`` does (see above), and the same
|
||||
``post_save_redirect`` as ``create_object`` does.
|
||||
|
||||
Uses the template ``app_label/module_name__form`` by default.
|
||||
|
||||
|
||||
Has the following template context:
|
||||
|
||||
form
|
||||
the form wrapper for the object
|
||||
The form wrapper for the object
|
||||
object
|
||||
the original object being edited
|
||||
The original object being edited
|
||||
|
||||
``delete_object``
|
||||
Delete an existing object. The given object will only actually be deleted if
|
||||
the request method is POST; if this view is fetched with GET it will display
|
||||
a confirmation page that should contain a form that POSTs to the same URL.
|
||||
|
||||
You must provide the ``post_delete_redirect`` argument to this function so
|
||||
Delete an existing object. The given object will only actually be deleted
|
||||
if the request method is POST. If this view is fetched with GET, it will
|
||||
display a confirmation page that should contain a form that POSTs to the
|
||||
same URL.
|
||||
|
||||
You must provide the ``post_delete_redirect`` argument to this function, so
|
||||
that the view knows where to go after the object is deleted.
|
||||
|
||||
If fetched with GET, uses the template
|
||||
``app_label/module_name_s_confirm_delete`` by default (uses no template if
|
||||
POSTed; simply deletes the object).
|
||||
|
||||
|
||||
If fetched with GET, it uses the template
|
||||
``app_label/module_name_s_confirm_delete`` by default. It uses no template
|
||||
if POSTed -- it simply deletes the object and redirects.
|
||||
|
||||
Has the following template context:
|
||||
|
||||
|
||||
object
|
||||
the object about to be deleted
|
||||
The object about to be deleted
|
||||
|
|
Loading…
Reference in New Issue