Permalink
Switch branches/tags
Find file Copy path
30dd94e Jul 15, 2014
320 lines (247 sloc) 11.4 KB

Django Dynamic Formsets documentation

The 5-minute introduction

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.

  1. First, copy jquery.formset.js to your MEDIA_ROOT; don't forget to include the jQuery library too!

  2. Include a reference to the script in your template; again, remember to reference the jQuery library, before including the script.

  3. 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>
    
  4. Add the following script to your template (before the closing BODY tag, or in your HEAD, below the reference to jquery.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 TR and 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.


Working with Inline Formsets

..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:

  1. Create an inline formset, making sure to pass can_delete=True in the call to inlineformset_factory. For more information, see the Django documentation on formsets.

  2. Render the formset as you would normally. Be sure to include the DELETE field 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.

  3. Call formset in your template, making sure to set the prefix option:

    <script type="text/javascript">
        $(function() {
            $('#myForm tbody tr').formset({
                prefix: '{{ formset.prefix }}'
            });
        })
    </script>
    
  4. Save your template and hit refresh in your browser. Try adding and removing a few rows, then submitting the page.

.. versionadded:: 1.1

Using multiple Formsets on the same page

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:

  1. Give each formset a unique prefix.
  2. Tell the plugin which forms belong to which formset -- you do this using the formCssClass option.

For example, to use the plugin with FormSet1, FormSet2 and FormSet3 on the same page, here's what you'd do:

  1. In your view, when you instantiate each formset, pass a unique value for the prefix keyword 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>`.
    
  2. 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>
    
  3. Add the code to initialize the plugin for the formsets, passing a unique CSS class name to formCssClass for 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!

Formset options

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.

Note

The addCssClass and deleteCssClass options must be unique. Internally, the plugin uses the class names to target 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.

Provided CSS classes

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.