Refs #7864 -- Updates to documentation for the oldforms/newforms switch.

* Moved forms.txt to oldforms.txt
 * Moved newforms.txt to forms.txt
 * Updated links and most references to "newforms" (there are a few sections that need a more significant rewrite).


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8020 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Gary Wilson Jr 2008-07-21 16:38:54 +00:00
parent f28474547b
commit 24aa08f486
16 changed files with 3148 additions and 3147 deletions

View File

@ -76,7 +76,7 @@ Requires the sites_ contrib package to be installed as well.
formtools
=========
A set of high-level abstractions for Django forms (django.newforms).
A set of high-level abstractions for Django forms (django.forms).
django.contrib.formtools.preview
--------------------------------

View File

@ -115,6 +115,6 @@ change:
.. _template language: ../templates/
.. _transactions: ../transactions/
.. _url dispatch: ../url_dispatch/
.. _forms and validation: ../forms/
.. _forms and validation: ../oldforms/
.. _serialization: ../serialization/
.. _authentication: ../authentication/

View File

@ -517,7 +517,7 @@ It's your responsibility to provide the login form in a template called
template context variables:
* ``form``: A ``Form`` object representing the login form. See the
`newforms documentation`_ for more on ``Form`` objects.
`forms documentation`_ for more on ``FormWrapper`` objects.
* ``next``: The URL to redirect to after successful login. This may contain
a query string, too.
* ``site_name``: The name of the current ``Site``, according to the
@ -557,7 +557,7 @@ block::
{% endblock %}
.. _newforms documentation: ../newforms/
.. _forms documentation: ../forms/
.. _site framework docs: ../sites/
Other built-in views

View File

@ -111,7 +111,7 @@ into the precise details of what ``Field`` can do later on; for now, suffice it
to say that everything descends from ``Field`` and then customizes key pieces
of the class behavior.
.. _form fields: ../newforms/#fields
.. _form fields: ../forms/#fields
It's important to realize that a Django field class is not what is stored in
your model attributes. The model attributes contain normal Python objects. The
@ -493,8 +493,8 @@ This assumes we're imported a ``MyFormField`` field class (which has its own
default widget). This document doesn't cover the details of writing custom form
fields.
.. _helper functions: ../newforms/#generating-forms-for-models
.. _forms documentation: ../newforms/
.. _helper functions: ../forms/#generating-forms-for-models
.. _forms documentation: ../forms/
``get_internal_type(self)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -20,13 +20,13 @@ For this reason, Django provides a few helper functions that let you create a
``form_for_model()``
--------------------
The method ``django.newforms.form_for_model()`` creates a form based on the
The method ``django.forms.form_for_model()`` creates a form based on the
definition of a specific model. Pass it the model class, and it will return a
``Form`` class that contains a form field for each model field.
For example::
>>> from django.newforms import form_for_model
>>> from django.forms import form_for_model
# Create the form class.
>>> ArticleForm = form_for_model(Article)
@ -93,11 +93,11 @@ the full list of conversions:
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
* ``ForeignKey`` is represented by ``django.forms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``django.forms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
@ -228,7 +228,7 @@ Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to add custom methods to the form generated by
``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
``form_for_model()``, write a class that extends ``django.forms.BaseForm``
and contains your custom methods. Then, use the ``form`` argument to
``form_for_model()`` to tell it to use your custom form as its base class.
For example::
@ -412,8 +412,8 @@ note is that the form display in the ``GET`` branch of the function
will use the values from the ``message`` instance as initial values for the
form field.
.. _contact form: ../newforms/#simple-view-example
.. _`simple example view`: ../newforms/#simple-view-example
.. _contact form: ../forms/#simple-view-example
.. _`simple example view`: ../forms/#simple-view-example
When should you use ``form_for_model()`` and ``form_for_instance()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -13,7 +13,7 @@ Python class.
Overview
=========
Given a ``django.newforms.Form`` subclass that you define, this application
Given a ``django.forms.Form`` subclass that you define, this application
takes care of the following workflow:
1. Displays the form as HTML on a Web page.
@ -65,7 +65,7 @@ How to use ``FormPreview``
from myapp.preview import SomeModelFormPreview
from myapp.models import SomeModel
from django import newforms as forms
from django import forms
...and add the following line to the appropriate model in your URLconf::

View File

@ -17,7 +17,7 @@ etc.
The term "wizard," in this context, is `explained on Wikipedia`_.
.. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29
.. _forms: ../newforms/
.. _forms: ../forms/
How it works
============
@ -41,7 +41,7 @@ Usage
This application handles as much machinery for you as possible. Generally, you
just have to do these things:
1. Define a number of ``django.newforms`` ``Form`` classes -- one per wizard
1. Define a number of ``django.forms`` ``Form`` classes -- one per wizard
page.
2. Create a ``FormWizard`` class that specifies what to do once all of your
forms have been submitted and validated. This also lets you override some
@ -55,8 +55,8 @@ Defining ``Form`` classes
=========================
The first step in creating a form wizard is to create the ``Form`` classes.
These should be standard ``django.newforms`` ``Form`` classes, covered in the
`newforms documentation`_.
These should be standard ``django.forms`` ``Form`` classes, covered in the
`forms documentation`_.
These classes can live anywhere in your codebase, but convention is to put them
in a file called ``forms.py`` in your application.
@ -65,7 +65,7 @@ For example, let's write a "contact form" wizard, where the first page's form
collects the sender's e-mail address and subject, and the second page collects
the message itself. Here's what the ``forms.py`` might look like::
from django import newforms as forms
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
@ -78,7 +78,7 @@ the message itself. Here's what the ``forms.py`` might look like::
data between pages, you may not include a ``FileField`` in any form except the
last one.
.. _newforms documentation: ../newforms/
.. _forms documentation: ../forms/
Creating a ``FormWizard`` class
===============================
@ -94,7 +94,7 @@ which specifies what should happen when the data for *every* form is submitted
and validated. This method is passed two arguments:
* ``request`` -- an HttpRequest_ object
* ``form_list`` -- a list of ``django.newforms`` ``Form`` classes
* ``form_list`` -- a list of ``django.forms`` ``Form`` classes
In this simplistic example, rather than perform any database operation, the
method simply renders a template of the validated data::
@ -209,7 +209,7 @@ Default implementation::
def prefix_for_step(self, step):
return str(step)
.. _form prefix documentation: ../newforms/#prefixes-for-forms
.. _form prefix documentation: ../forms/#prefixes-for-forms
``render_hash_failure``
~~~~~~~~~~~~~~~~~~~~~~~

File diff suppressed because it is too large Load Diff

View File

@ -984,12 +984,12 @@ In addition to ``extra_context``, the template's context will be:
<p>{{ form.address.label_tag }} {{ form.address }}</p>
</form>
See the `newforms documentation`_ for more information about using
See the `forms documentation`_ for more information about using
``Form`` objects in templates.
.. _authentication system: ../authentication/
.. _ModelForm docs: ../newforms/modelforms
.. _newforms documentation: ../newforms/
.. _forms documentation: ../forms/
``django.views.generic.create_update.update_object``
----------------------------------------------------

View File

@ -33,7 +33,7 @@ Reference
transactions
templates
templates_python
newforms
forms
modelforms
testing
sessions

View File

@ -11,7 +11,7 @@ Inside that package, country- or culture-specific code is organized into
subpackages, named using `ISO 3166 country codes`_.
Most of the ``localflavor`` add-ons are localized form components deriving from
the newforms_ framework -- for example, a ``USStateField`` that knows how to
the forms_ framework -- for example, a ``USStateField`` that knows how to
validate U.S. state abbreviations, and a ``FISocialSecurityNumber`` that knows
how to validate Finnish social security numbers.
@ -19,7 +19,7 @@ To use one of these localized components, just import the relevant subpackage.
For example, here's how you can create a form with a field representing a
French telephone number::
from django import newforms as forms
from django import forms
from django.contrib.localflavor import fr
class MyForm(forms.Form):
@ -58,10 +58,10 @@ Countries currently supported by ``localflavor`` are:
The ``localflavor`` package also includes a ``generic`` subpackage, containing
useful code that is not specific to one particular country or culture.
Currently, it defines date and datetime input fields based on those from
newforms_, but with non-US default formats. Here's an example of how to use
forms_, but with non-US default formats. Here's an example of how to use
them::
from django import newforms as forms
from django import forms
from django.contrib.localflavor import generic
class MyForm(forms.Form):
@ -92,7 +92,7 @@ them::
.. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_
.. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_
.. _United States of America: `United States of America (django.contrib.localflavor.us)`_
.. _newforms: ../newforms/
.. _forms: ../forms/
Adding flavors
==============

View File

@ -716,7 +716,7 @@ that takes the parameters ``field_data, all_data`` and raises
Django comes with quite a few validators. They're in ``django.core.validators``.
.. _validator docs: ../forms/#validators
.. _validator docs: ../oldforms/#validators
Verbose field names
-------------------

View File

@ -1,6 +1,6 @@
==========================
Using newforms with models
==========================
=======================
Using forms with models
=======================
``ModelForm``
=============
@ -16,7 +16,7 @@ class from a Django model.
For example::
>>> from django.newforms import ModelForm
>>> from django.forms import ModelForm
# Create the form class.
>>> class ArticleForm(ModelForm):
@ -86,11 +86,11 @@ the full list of conversions:
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
* ``ForeignKey`` is represented by ``django.forms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``django.forms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
@ -121,7 +121,7 @@ A full example
Consider this set of models::
from django.db import models
from django.newforms import ModelForm
from django.forms import ModelForm
TITLE_CHOICES = (
('MR', 'Mr.'),
@ -240,14 +240,14 @@ For example::
>>> new_author = f.save()
Other than the ``save()`` and ``save_m2m()`` methods, a ``ModelForm``
works exactly the same way as any other ``newforms`` form. For
works exactly the same way as any other ``forms`` form. For
example, the ``is_valid()`` method is used to check for validity, the
``is_multipart()`` method is used to determine whether a form requires
multipart file upload (and hence whether ``request.FILES`` must be
passed to the form), etc. See `the standard newforms documentation`_
passed to the form), etc. See `the standard forms documentation`_
for more information.
.. _the standard newforms documentation: ../newforms/
.. _the standard forms documentation: ../forms/
Using a subset of fields on the form
------------------------------------

File diff suppressed because it is too large Load Diff

700
docs/oldforms.txt Normal file
View File

@ -0,0 +1,700 @@
===============================
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 django.forms system, which we have begun to document in the
`forms documentation`_.
If you have legacy form/manipulator code, read the "Migration plan" section in
that document to understand how we're making the switch.
.. _forms documentation: ../forms/
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 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.
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 `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 settings_
document for more details).
.. _`generic views`: ../generic_views/
.. _`models API`: ../model-api/
.. _settings: ../settings/

View File

@ -17,7 +17,7 @@ Basic file uploads
Consider a simple form containing a ``FileField``::
from django import newforms as forms
from django import forms
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
@ -48,7 +48,7 @@ something like::
form = UploadFileForm()
return render_to_response('upload.html', {'form': form})
.. _binding uploaded files to a form: ../newforms/#binding-uploaded-files-to-a- form
.. _binding uploaded files to a form: ../forms/#binding-uploaded-files-to-a- form
Notice that we have to pass ``request.FILES`` into the form's constructor; this
is how file data gets bound into a form.