If you have an existing project, it's quite easy to add client-side support for adding and removing forms.
I'll assume you've already created your formset. You can create
formsets using any of the provided methods: both regular formsets
(created with the formset_factory) and inline formsets
(created with the inlineformset_factory) are supported.
First, copy
jquery.formset.jsto yourMEDIA_ROOT; don't forget to include the jQuery library too!Include a reference to the script in your template; again, remember to reference the jQuery library, before including the script.
Render the formset as you would normally -- I usually use a table but you can use DIVs, Ps or whatever you desire. Let's use the example markup below:
<form id="myForm" method="post" action=""> <table border="0" cellpadding="0" cellspacing="0"> <tbody> {% for form in formset.forms %} <tr> <td>{{ form.field1 }}</td> <td>{{ form.field2 }}</td> <td>{{ form.field3 }}</td> </tr> {% endfor %} </tbody> </table> {{ formset.management_form }} </form>Add the following script to your template (before the closing
BODYtag, or in yourHEAD, below the reference tojquery.formset.js):<script type="text/javascript"> $(function() { $('#myForm tbody tr').formset(); }) </script>Notice that our jQuery selector targets the container for each form. We could have assigned a class to each
TRand used that instead:$('.form-container').formset();Either way is fine, really :)
If you used a non-inline formset, you're done. Fini. Save your template and navigate to the appropriate view in your application, and you should see an "add another" link. Clicking on it should add another instance of your form to the page. You can remove instances by clicking the "remove" link for an instance.
.. versionchanged:: 1.2 In previous versions, if there was only one form in the formset, the remove link would be hidden. With the addition of form templates, this behaviour has been changed -- it is now possible to remove all forms in a formset, and have the `add` link still behave as expected.
..versionchanged:: 1.2
In version 1.2, the behaviour of inline formsets was changed: clicking the "remove" link for an instance in an inline formset will now cause Django to delete that instance from the database when the form is POSTed.
To enable this behaviour, you'll need to do the following:
Create an inline formset, making sure to pass
can_delete=Truein the call toinlineformset_factory. For more information, see the Django documentation on formsets.Render the formset as you would normally. Be sure to include the
DELETEfield generated by Django, for each form with an existing instance. Here's an example:<form id="myForm" method="post" action=""> <table border="0" cellpadding="0" cellspacing="0"> <tbody> {% for form in formset.forms %} <tr> <td> {% if form.instance.pk %}{{ form.DELETE }}{% endif %} {{ form.field1 }} </td> <td>{{ form.field2 }}</td> <td>{{ form.field3 }}</td> </tr> {% endfor %} </tbody> </table> {{ formset.management_form }} </form>Notice the
{% if form.instance.pk %}...{% endif %}around{{ form.DELETE }}? This is generally a good idea, since we only want Django to delete instances that already exist in the database.Call
formsetin your template, making sure to set theprefixoption:<script type="text/javascript"> $(function() { $('#myForm tbody tr').formset({ prefix: '{{ formset.prefix }}' }); }) </script>Save your template and hit refresh in your browser. Try adding and removing a few rows, then submitting the page.
.. versionadded:: 1.1
What if you need to display more than one formset on a page? If you try the above code with more than one formset, you'll notice it doesn't work quite the way you'd expect. There are two things you need to do, in order to use more than one formset on a single page:
- Give each formset a unique prefix.
- Tell the plugin which forms belong to which formset -- you do this
using the
formCssClassoption.
For example, to use the plugin with FormSet1, FormSet2 and FormSet3
on the same page, here's what you'd do:
In your view, when you instantiate each formset, pass a unique value for the
prefixkeyword argument:def my_view(request): if request.method == 'POST': formset1, formset2, formset3 = \ FormSet1(request.POST, prefix='fs1'), \ FormSet2(request.POST, prefix='fs2'), \ FormSet3(request.POST, prefix='fs3') if formset1.is_valid() and formset2.is_valid() \ and formset3.is_valid(): # Do something awesome with the forms. else: formset1, formset2, formset3 = \ FormSet1(prefix='fs1'), \ FormSet2(prefix='fs2'), \ FormSet3(prefix='fs3') ... Giving each formset a unique prefix ensures that they don't step on each other. For more information on ``prefix``, see the `Django documentation <http://docs.djangoproject.com/en/dev/topics/forms/formsets/#using-more-than-one-formset-in-a-view>`.Render the formsets in your template:
<form id="myFormsets" method="post" action=""> <table id="myFormset1Table" border="0" cellpadding="0"> <caption>Formset One</caption> <tbody> {% for form in formset1.forms %} <tr> <td>{{ form.field1 }}</td> <td>{{ form.field2 }}</td> </tr> {% endfor %} </tbody> </table> {{ formset1.management_form }} <table id="myFormset2Table" border="0" cellpadding="0"> <caption>Formset Two</caption> <tbody> {% for form in formset2.forms %} <tr> <td>{{ form.field1 }}</td> <td>{{ form.field2 }}</td> </tr> {% endfor %} </tbody> </table> {{ formset2.management_form }} ... </form>Add the code to initialize the plugin for the formsets, passing a unique CSS class name to
formCssClassfor each formset:<script type="text/javascript"> $(function() { $('#myFormset1Table tbody tr').formset({ prefix: '{{ formset1.prefix }}', formCssClass: 'dynamic-formset1' }); $('#myFormset2Table tbody tr').formset({ prefix: '{{ formset2.prefix }}', formCssClass: 'dynamic-formset2' }); ... }) </script>Save your template, hit refresh in your browser, et voila!
You can customize this plugin's behavior by passing an options hash. A complete list of available options is shown below:
``prefix``
Use this to specify the prefix for your formset if it's anything
other than the default ("form"). This option must be supplied for
inline formsets.
``addText``
Use this to set the text for the generated add link. The default
text is "add another".
``deleteText``
Use this to set the text for the generated delete links. The
default text is "remove".
``addCssClass``
Use this to change the default CSS class applied to the generated
add link (possibly, to avoid CSS conflicts within your templates).
The default class is "add-row".
``deleteCssClass``
Use this to change the default CSS class applied to the generated
delete links. The default class is "delete-row".
``added``
If you set this to a function, that function will be called each
time a new form is added. The function should take a single argument,
``row``; it will be passed a jQuery object, wrapping the form that
was just added.
``removed``
Set this to a function, and that function will be called each time
a form is deleted. The function should take a single argument,
``row``; it will be passed a jQuery object, wrapping the form that
was just removed.
.. versionadded:: 1.1
``formCssClass``
Use this to set the CSS class applied to all forms within the same
formset. Internally, all forms with the same class are assumed to
belong to the same formset. If you have multiple formsets on a single
HTML page, you MUST provide unique class names for each formset. If
you don't provide a value, this defaults to "dynamic-form".
For more information, see the section on :ref:`Using multiple Formsets
on the same page <using-multiple-formsets>`, and check out the example
in the demo project.
.. versionadded:: 1.2
``formTemplate``
Use this to override the form that gets cloned, each time a new form
instance is added. If specified, this should be a jQuery selector.
``extraClasses``
Set this to an array of CSS class names (defaults to an empty array),
and the classes will be applied to each form in the formset in turn.
This can easily be used to acheive row-striping effects, which can
make large formsets easier to deal with visually.
.. versionadded:: 1.3
``keepFieldValues``
Set this to a jQuery selector, which should resolve to a list of elements
whose values should be preserved when the form is cloned.
Internally, this value is passed directly to the ``$.not(...)`` method.
This means you can also pass in DOM elements, or a function (in newer
versions of jQuery) as your selector.
.. versionadded:: 1.4
``addContainerClass``
Use this to control the positioning of the generated add link by
defining the class selector that should act as the container to
which the add link is appended to.
``deleteContainerClass``
Use this to control the positioning of the generated delete links
by defining the class selector that should act as the container to
which a particular row's delete link is appended to. The container
will be searched for within the particular row itself.
.. versionadded:: 1.5
``hideLastAddForm``
Set this to ``true`` to hide the last empty add form (displayed when
in Django ``extra`` parameter is greater than 0). The form becomes
visible when clicking the "add new" link.
Note
The addCssClass, addContainerClass`, ``deleteCssClass and
deleteContainerClass options must be unique. Internally, the plugin
uses the class names to target and place the add and delete links. Any other
elements with the same class applied to them will also have the add and
delete behavior, which is almost certainly not what you want.
Each form's container will have the class specified by the formCssClass
option (defaults to "dynamic-form") applied to it. You can use this to define
style rules targeting each of these forms.