summaryrefslogtreecommitdiff
path: root/docs/newforms.txt
diff options
context:
space:
mode:
Diffstat (limited to 'docs/newforms.txt')
-rw-r--r--docs/newforms.txt640
1 files changed, 640 insertions, 0 deletions
diff --git a/docs/newforms.txt b/docs/newforms.txt
index 4240fe9985..530c9ce828 100644
--- a/docs/newforms.txt
+++ b/docs/newforms.txt
@@ -76,6 +76,9 @@ The library deals with these concepts:
* **Form** -- A collection of fields that knows how to validate itself and
display itself as HTML.
+ * **Media** -- A definition of the CSS and JavaScript resources that are
+ required to render a form.
+
The library is decoupled from the other Django components, such as the database
layer, views and templates. It relies only on Django settings, a couple of
``django.utils`` helper functions and Django's internationalization hooks (but
@@ -1864,6 +1867,643 @@ They've been deprecated, but you can still `view the documentation`_.
.. _ModelForms documentation: ../modelforms/
.. _view the documentation: ../form_for_model/
+Media
+=====
+
+Rendering an attractive and easy-to-use web form requires more than just
+HTML - it also requires CSS stylesheets, and if you want to use fancy
+"Web2.0" widgets, you may also need to include some JavaScript on each
+page. The exact combination of CSS and JavaScript that is required for
+any given page will depend upon the widgets that are in use on that page.
+
+This is where Django media definitions come in. Django allows you to
+associate different media files with the forms and widgets that require
+that media. For example, if you want to use a calendar to render DateFields,
+you can define a custom Calendar widget. This widget can then be associated
+with the CSS and Javascript that is required to render the calendar. When
+the Calendar widget is used on a form, Django is able to identify the CSS and
+JavaScript files that are required, and provide the list of file names
+in a form suitable for easy inclusion on your web page.
+
+.. admonition:: Media and Django Admin
+
+ The Django Admin application defines a number of customized widgets
+ for calendars, filtered selections, and so on. These widgets define
+ media requirements, and the Django Admin uses the custom widgets
+ in place of the Django defaults. The Admin templates will only include
+ those media files that are required to render the widgets on any
+ given page.
+
+ If you like the widgets that the Django Admin application uses,
+ feel free to use them in your own application! They're all stored
+ in ``django.contrib.admin.widgets``.
+
+.. admonition:: Which JavaScript toolkit?
+
+ Many JavaScript toolkits exist, and many of them include widgets (such
+ as calendar widgets) that can be used to enhance your application.
+ Django has deliberately avoided blessing any one JavaScript toolkit.
+ Each toolkit has its own relative strengths and weaknesses - use
+ whichever toolkit suits your requirements. Django is able to integrate
+ with any JavaScript toolkit.
+
+Media as a static definition
+----------------------------
+
+The easiest way to define media is as a static definition. Using this method,
+the media declaration is an inner class. The properties of the inner class
+define the media requirements.
+
+Here's a simple example::
+
+ class CalendarWidget(forms.TextInput):
+ class Media:
+ css = {
+ 'all': ('pretty.css',)
+ }
+ js = ('animations.js', 'actions.js')
+
+This code defines a ``CalendarWidget``, which will be based on ``TextInput``.
+Every time the CalendarWidget is used on a form, that form will be directed
+to include the CSS file ``pretty.css``, and the JavaScript files
+``animations.js`` and ``actions.js``.
+
+This static media definition is converted at runtime into a widget property
+named ``media``. The media for a CalendarWidget instance can be retrieved
+through this property::
+
+ >>> w = CalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+
+Here's a list of all possible ``Media`` options. There are no required options.
+
+``css``
+~~~~~~~
+
+A dictionary describing the CSS files required for various forms of output
+media.
+
+The values in the dictionary should be a tuple/list of file names. See
+`the section on media paths`_ for details of how to specify paths to media
+files.
+
+.. _the section on media paths: `Paths in media definitions`_
+
+The keys in the dictionary are the output media types. These are the same
+types accepted by CSS files in media declarations: 'all', 'aural', 'braille',
+'embossed', 'handheld', 'print', 'projection', 'screen', 'tty' and 'tv'. If
+you need to have different stylesheets for different media types, provide
+a list of CSS files for each output medium. The following example would
+provide two CSS options -- one for the screen, and one for print::
+
+ class Media:
+ css = {
+ 'screen': ('pretty.css',),
+ 'print': ('newspaper.css',)
+ }
+
+If a group of CSS files are appropriate for multiple output media types,
+the dictionary key can be a comma separated list of output media types.
+In the following example, TV's and projectors will have the same media
+requirements::
+
+ class Media:
+ css = {
+ 'screen': ('pretty.css',),
+ 'tv,projector': ('lo_res.css',),
+ 'print': ('newspaper.css',)
+ }
+
+If this last CSS definition were to be rendered, it would become the following HTML::
+
+ <link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
+ <link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
+ <link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
+
+``js``
+~~~~~~
+
+A tuple describing the required javascript files. See
+`the section on media paths`_ for details of how to specify paths to media
+files.
+
+``extend``
+~~~~~~~~~~
+
+A boolean defining inheritance behavior for media declarations.
+
+By default, any object using a static media definition will inherit all the
+media associated with the parent widget. This occurs regardless of how the
+parent defines its media requirements. For example, if we were to extend our
+basic Calendar widget from the example above::
+
+ class FancyCalendarWidget(CalendarWidget):
+ class Media:
+ css = {
+ 'all': ('fancy.css',)
+ }
+ js = ('whizbang.js',)
+
+ >>> w = FancyCalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+The FancyCalendar widget inherits all the media from it's parent widget. If
+you don't want media to be inherited in this way, add an ``extend=False``
+declaration to the media declaration::
+
+ class FancyCalendar(Calendar):
+ class Media:
+ extend = False
+ css = {
+ 'all': ('fancy.css',)
+ }
+ js = ('whizbang.js',)
+
+ >>> w = FancyCalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+If you require even more control over media inheritance, define your media
+using a `dynamic property`_. Dynamic properties give you complete control over
+which media files are inherited, and which are not.
+
+.. _dynamic property: `Media as a dynamic property`_
+
+Media as a dynamic property
+---------------------------
+
+If you need to perform some more sophisticated manipulation of media
+requirements, you can define the media property directly. This is done
+by defining a model property that returns an instance of ``forms.Media``.
+The constructor for ``forms.Media`` accepts ``css`` and ``js`` keyword
+arguments in the same format as that used in a static media definition.
+
+For example, the static media definition for our Calendar Widget could
+also be defined in a dynamic fashion::
+
+ class CalendarWidget(forms.TextInput):
+ def _media(self):
+ return forms.Media(css={'all': ('pretty.css',)},
+ js=('animations.js', 'actions.js'))
+ media = property(_media)
+
+See the section on `Media objects`_ for more details on how to construct
+return values for dynamic media properties.
+
+Paths in media definitions
+--------------------------
+
+Paths used to specify media can be either relative or absolute. If a path
+starts with '/', 'http://' or 'https://', it will be interpreted as an absolute
+path, and left as-is. All other paths will be prepended with the value of
+``settings.MEDIA_URL``. For example, if the MEDIA_URL for your site was
+``http://media.example.com/``::
+
+ class CalendarWidget(forms.TextInput):
+ class Media:
+ css = {
+ 'all': ('/css/pretty.css',),
+ }
+ js = ('animations.js', 'http://othersite.com/actions.js')
+
+ >>> w = CalendarWidget()
+ >>> print w.media
+ <link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://othersite.com/actions.js"></script>
+
+Media objects
+-------------
+
+When you interrogate the media attribute of a widget or form, the value that
+is returned is a ``forms.Media`` object. As we have already seen, the string
+representation of a Media object is the HTML required to include media
+in the ``<head>`` block of your HTML page.
+
+However, Media objects have some other interesting properties.
+
+Media subsets
+~~~~~~~~~~~~~
+
+If you only want media of a particular type, you can use the subscript operator
+to filter out a medium of interest. For example::
+
+ >>> w = CalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+
+ >>> print w.media['css']
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+
+When you use the subscript operator, the value that is returned is a new
+Media object -- but one that only contains the media of interest.
+
+Combining media objects
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Media objects can also be added together. When two media objects are added,
+the resulting Media object contains the union of the media from both files::
+
+ class CalendarWidget(forms.TextInput):
+ class Media:
+ css = {
+ 'all': ('pretty.css',)
+ }
+ js = ('animations.js', 'actions.js')
+
+ class OtherWidget(forms.TextInput):
+ class Media:
+ js = ('whizbang.js',)
+
+ >>> w1 = CalendarWidget()
+ >>> w2 = OtherWidget()
+ >>> print w1+w2
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+Media on Forms
+--------------
+
+Widgets aren't the only objects that can have media definitions -- forms
+can also define media. The rules for media definitions on forms are the
+same as the rules for widgets: declarations can be static or dynamic;
+path and inheritance rules for those declarations are exactly the same.
+
+Regardless of whether you define a media declaration, *all* Form objects
+have a media property. The default value for this property is the result
+of adding the media definitions for all widgets that are part of the form::
+
+ class ContactForm(forms.Form):
+ date = DateField(widget=CalendarWidget)
+ name = CharField(max_length=40, widget=OtherWidget)
+
+ >>> f = ContactForm()
+ >>> f.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+If you want to associate additional media with a form -- for example, CSS for form
+layout -- simply add a media declaration to the form::
+
+ class ContactForm(forms.Form):
+ date = DateField(widget=CalendarWidget)
+ name = CharField(max_length=40, widget=OtherWidget)
+
+ class Media:
+ css = {
+ 'all': ('layout.css',)
+ }
+
+ >>> f = ContactForm()
+ >>> f.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+Formsets
+========
+
+A formset is a layer of abstraction to working with multiple forms on the same
+page. It can be best compared to a data grid. Let's say you have the following
+form::
+
+ >>> from django import newforms as forms
+ >>> class ArticleForm(forms.Form):
+ ... title = forms.CharField()
+ ... pub_date = forms.DateField()
+
+You might want to allow the user to create several articles at once. To create
+a formset of ``ArticleForm``s you would do::
+
+ >>> from django.newforms.formsets import formset_factory
+ >>> ArticleFormSet = formset_factory(ArticleForm)
+
+You now have created a formset named ``ArticleFormSet``. The formset gives you
+the ability to iterate over the forms in the formset and display them as you
+would with a regular form::
+
+ >>> formset = ArticleFormSet()
+ >>> for form in formset.forms:
+ ... print form.as_table()
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+
+As you can see it only displayed one form. This is because by default the
+``formset_factory`` defines one extra form. This can be controlled with the
+``extra`` parameter::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
+
+Using initial data with a formset
+---------------------------------
+
+Initial data is what drives the main usability of a formset. As shown above
+you can define the number of extra forms. What this means is that you are
+telling the formset how many additional forms to show in addition to the
+number of forms it generates from the initial data. Lets take a look at an
+example::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
+ >>> formset = ArticleFormSet(initial=[
+ ... {'title': u'Django is now open source',
+ ... 'pub_date': datetime.date.today()},
+ ... ])
+
+ >>> for form in formset.forms:
+ ... print form.as_table()
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr>
+ <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
+ <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
+ <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+ <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+
+There are now a total of three forms showing above. One for the initial data
+that was passed in and two extra forms. Also note that we are passing in a
+list of dictionaries as the initial data.
+
+Limiting the maximum number of forms
+------------------------------------
+
+The ``max_num`` parameter to ``formset_factory`` gives you the ability to
+force the maximum number of forms the formset will display::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
+ >>> formset = ArticleFormset()
+ >>> for form in formset.forms:
+ ... print form.as_table()
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+
+The default value of ``max_num`` is ``0`` which is the same as saying put no
+limit on the number forms displayed.
+
+Formset validation
+------------------
+
+Validation with a formset is about identical to a regular ``Form``. There is
+an ``is_valid`` method on the formset to provide a convenient way to validate
+each form in the formset::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm)
+ >>> formset = ArticleFormSet({})
+ >>> formset.is_valid()
+ True
+
+We passed in no data to the formset which is resulting in a valid form. The
+formset is smart enough to ignore extra forms that were not changed. If we
+attempt to provide an article, but fail to do so::
+
+ >>> data = {
+ ... 'form-TOTAL_FORMS': u'1',
+ ... 'form-INITIAL_FORMS': u'1',
+ ... 'form-0-title': u'Test',
+ ... 'form-0-pub_date': u'',
+ ... }
+ >>> formset = ArticleFormSet(data)
+ >>> formset.is_valid()
+ False
+ >>> formset.errors
+ [{'pub_date': [u'This field is required.']}]
+
+As we can see the formset properly performed validation and gave us the
+expected errors.
+
+Understanding the ManagementForm
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You may have noticed the additional data that was required in the formset's
+data above. This data is coming from the ``ManagementForm``. This form is
+dealt with internally to the formset. If you don't use it, it will result in
+an exception::
+
+ >>> data = {
+ ... 'form-0-title': u'Test',
+ ... 'form-0-pub_date': u'',
+ ... }
+ >>> formset = ArticleFormSet(data)
+ Traceback (most recent call last):
+ ...
+ django.newforms.util.ValidationError: [u'ManagementForm data is missing or has been tampered with']
+
+It is used to keep track of how many form instances are being displayed. If
+you are adding new forms via javascript, you should increment the count fields
+in this form as well.
+
+Custom formset validation
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A formset has a ``clean`` method similar to the one on a ``Form`` class. This
+is where you define your own validation that deals at the formset level::
+
+ >>> from django.newforms.formsets import BaseFormSet
+
+ >>> class BaseArticleFormSet(BaseFormSet):
+ ... def clean(self):
+ ... raise forms.ValidationError, u'An error occured.'
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
+ >>> formset = ArticleFormSet({})
+ >>> formset.is_valid()
+ False
+ >>> formset.non_form_errors()
+ [u'An error occured.']
+
+The formset ``clean`` method is called after all the ``Form.clean`` methods
+have been called. The errors will be found using the ``non_form_errors()``
+method on the formset.
+
+Dealing with ordering and deletion of forms
+-------------------------------------------
+
+Common use cases with a formset is dealing with ordering and deletion of the
+form instances. This has been dealt with for you. The ``formset_factory``
+provides two optional parameters ``can_order`` and ``can_delete`` that will do
+the extra work of adding the extra fields and providing simpler ways of
+getting to that data.
+
+``can_order``
+~~~~~~~~~~~~~
+
+Default: ``False``
+
+Lets create a formset with the ability to order::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
+ >>> formset = ArticleFormSet(initial=[
+ ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+ ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+ ... ])
+ >>> for form in formset.forms:
+ ... print form.as_table()
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
+ <tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
+ <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
+ <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
+ <tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
+ <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+ <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+ <tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
+
+This adds an additional field to each form. This new field is named ``ORDER``
+and is an ``forms.IntegerField``. For the forms that came from the initial
+data it automatically assigned them a numeric value. Lets look at what will
+happen when the user changes these values::
+
+ >>> data = {
+ ... 'form-TOTAL_FORMS': u'3',
+ ... 'form-INITIAL_FORMS': u'2',
+ ... 'form-0-title': u'Article #1',
+ ... 'form-0-pub_date': u'2008-05-10',
+ ... 'form-0-ORDER': u'2',
+ ... 'form-1-title': u'Article #2',
+ ... 'form-1-pub_date': u'2008-05-11',
+ ... 'form-1-ORDER': u'1',
+ ... 'form-2-title': u'Article #3',
+ ... 'form-2-pub_date': u'2008-05-01',
+ ... 'form-2-ORDER': u'0',
+ ... }
+
+ >>> formset = ArticleFormSet(data, initial=[
+ ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+ ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+ ... ])
+ >>> formset.is_valid()
+ True
+ >>> for form in formset.ordered_forms:
+ ... print form.cleaned_data
+ {'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': u'Article #3'}
+ {'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': u'Article #2'}
+ {'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': u'Article #1'}
+
+``can_delete``
+~~~~~~~~~~~~~~
+
+Default: ``False``
+
+Lets create a formset with the ability to delete::
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
+ >>> formset = ArticleFormSet(initial=[
+ ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+ ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+ ... ])
+ >>> for form in formset.forms:
+ .... print form.as_table()
+ <input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" />
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
+ <tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></td></tr>
+ <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
+ <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
+ <tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></td></tr>
+ <tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
+ <tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
+ <tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></td></tr>
+
+Similar to ``can_order`` this adds a new field to each form named ``DELETE``
+and is a ``forms.BooleanField``. When data comes through marking any of the
+delete fields you can access them with ``deleted_forms``::
+
+ >>> data = {
+ ... 'form-TOTAL_FORMS': u'3',
+ ... 'form-INITIAL_FORMS': u'2',
+ ... 'form-0-title': u'Article #1',
+ ... 'form-0-pub_date': u'2008-05-10',
+ ... 'form-0-DELETE': u'on',
+ ... 'form-1-title': u'Article #2',
+ ... 'form-1-pub_date': u'2008-05-11',
+ ... 'form-1-DELETE': u'',
+ ... 'form-2-title': u'',
+ ... 'form-2-pub_date': u'',
+ ... 'form-2-DELETE': u'',
+ ... }
+
+ >>> formset = ArticleFormSet(data, initial=[
+ ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)},
+ ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)},
+ ... ])
+ >>> [form.cleaned_data for form in formset.deleted_forms]
+ [{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': u'Article #1'}]
+
+Adding additional fields to a formset
+-------------------------------------
+
+If you need to add additional fields to the formset this can be easily
+accomplished. The formset base class provides an ``add_fields`` method. You
+can simply override this method to add your own fields or even redefine the
+default fields/attributes of the order and deletion fields::
+
+ >>> class BaseArticleFormSet(BaseFormSet):
+ ... def add_fields(self, form, index):
+ ... super(BaseArticleFormSet, self).add_fields(form, index)
+ ... form.fields["my_field"] = forms.CharField()
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
+ >>> formset = ArticleFormSet()
+ >>> for form in formset.forms:
+ ... print form.as_table()
+ <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr>
+ <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr>
+ <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr>
+
+Using a formsets in views and templates
+---------------------------------------
+
+Using a formset inside a view is as easy as using a regular ``Form`` class.
+The only thing you will want to be aware of is making sure to use the
+management form inside the template. Lets look at a sample view::
+
+ def manage_articles(request):
+ ArticleFormSet = formset_factory(ArticleForm)
+ if request.method == 'POST':
+ formset = ArticleFormSet(request.POST, request.FILES)
+ if formset.is_valid():
+ # do something with the formset.cleaned_data
+ else:
+ formset = ArticleFormSet()
+ return render_to_response('manage_articles.html', {'formset': formset})
+
+The ``manage_articles.html`` template might look like this::
+
+ <form method="POST" action="">
+ {{ formset.management_form }}
+ <table>
+ {% for form in formset.forms %}
+ {{ form }}
+ {% endfor %}
+ </table>
+ </form>
+
+However the above can be slightly shortcutted and let the formset itself deal
+with the management form::
+
+ <form method="POST" action="">
+ <table>
+ {{ formset }}
+ </table>
+ </form>
+
+The above ends up calling the ``as_table`` method on the formset class.
+
More coming soon
================