Django Dynamic Formset, v1.2

So, it’s two months later than I’d estimated, but sometime this morning, I committed version 1.2. In spite of the delay (which is a story for another time), I’m pretty excited about this release — I got to add a couple of nifty features, and squash some bugs in the process (thanks for the bug reports and patches, guys).

Here’s the list of changes:

  • Inline formsets created with “can_delete” set to True are now supported properly; clicking the “remove” link hides the form and sets the DELETE field, so Django handles the deleting when the page is POSTed.
  • Added form templates: you can now specify a form that will be cloned to generate the forms in the formset. As a side-effect, you can now delete all forms in a formset, and the “add” link still works as expected.
  • Clicking the “add” link now clones the last form, instead of the first; this works much better in the admin, especially if you have one or more extra forms (thanks justhamade).
  • Added an optional setting “extraClasses”; set this is an array of CSS classes, and they’ll be applied to the formset’s rows in turn. So, to get the row-striping effect in the Django admin, you’d do something like this:
    $('...').formset({extraClasses: ['row1', 'row2']});

    Adding and removing forms keeps the classes in sync, so your stripes don’t get all mix’d up — hopefully, this is a feature, but time will tell.

  • Updated examples and documentation.

As always, you can download the latest release from the Google Code page, or use the links below:


36 thoughts on “Django Dynamic Formset, v1.2

  1. Mike

    Thanks for the great work putting this together.

    I have found a problem with the management of the delete functionality. The delete state does not persist through validation of the forms.

    Say for example you have two forms in the formset. The first one you remove with the intention of deleting it. The second one, you fill out, but it has invalid data. If you then submit this, the page will reload, and the first form will still display.

    I have made changes to the jQuery code to resolve this. I am very new to jQuery though, so it might not be the best solution, but it does work.

    The diff is below:

    >                 // added to hide 
    >                 if (':checked')) {
    >                     // this means that the form is already marked for deletion, 
    >                     // so should hide it to remain consistent (occurs after failed validation)
    >                     // at the django end
    >                     hiddenDel = row.find('input:hidden[id $= "-DELETE"]');
    >                     hiddenDel.val('on');
    >                     row.hide();
    >                 }
  2. Mike

    FYI, I’ve been working on getting the ordering working with this is as well. It’s taken more code than I expected, and as a jQuery novice might not be the best way of doing it, but will be happy to post what I have when I’m done if you want to take a look?

  3. Mike

    Hey there … I have got ordering together, although it will probably need some work if you wanted to roll it into the project. Usual caveats of it’s working for me but might not for everyone etc ;-)

    It also needs some code to deal with the order value in the form processing, but this is exactly the same as it would be for general formset processing …

    One other comment I would make is that it can be worth hiding all the forms in a div until after they have been processed by the formset function, so that the checkboxes and order form elements aren’t shown whilst they are being processed.

    Hope this is helpful. Here’s the complete diff between 1.2 and my current version:

    <             // copied the approach directly from the insertDeleteLink method
    <             // to provide a patch for managing moving the formset elements
    <             insertMoveLinks = function(row) {
    &lt;                 var moveText = ' <a href="void(0)" rel="nofollow">' + options.upText + '</a> <a href="void(0)" rel="nofollow">' + options.downText + '</a>';
    &lt;                 if ('TR')) {
    &lt;                     row.children(':last').append(moveText);
    &lt;                 } else if ('UL') ||'OL')) {
    &lt;                     row.append('' + moveText + '');
    &lt;                 } else {
    &lt;                     row.append(moveText);
    &lt;                 }
    &lt;                 row.find('a.' + options.upCssClass).click(function() {
    &lt;                     var row = $(this).parents('.' + options.formCssClass),
    &lt;                         order = row.find('input:hidden[id $= &quot;-ORDER&quot;]'),
    &lt;                         orderVal = order.val();
    &lt;                     if (orderVal == 1) {
    &lt;                         // if we're already at the top, then we can ignore the request
    &lt;                         return;
    &lt;                     }
    &lt;                     else {
    &lt;                         // get the previous form element
    &lt;                         var prev = row.prev('.' + options.formCssClass);
    &lt;                         // move it below the row
    &lt;                         // TODO: this has only been tested with one particular implementation, there may be problems
    &lt;                         // with different form layout approaches.
    &lt;                         row.after(prev);
    &lt;                         // change the order values
    &lt;                         order.val(parseInt(orderVal)-1);
    &lt;                         prev.find('input:hidden[ id $= &quot;-ORDER&quot;]').val(orderVal);
    &lt;                     }
    &lt;                 })
    &lt;                 row.find('a.' + options.downCssClass).click(function() {
    &lt;                     var row = $(this).parents('.' + options.formCssClass),
    &lt;                         order = row.find('input:hidden[id $= &quot;-ORDER&quot;]'),
    &lt;                         orderVal = order.val(),
    &lt;                         formCount = parseInt($('#id_' + options.prefix + '-TOTAL_FORMS').val());
    &lt;                     if (orderVal == formCount) {
    &lt;                         // we're already at the bottom, so we can ignore request
    &lt;                         return;
    &lt;                     }
    &lt;                     else {
    &lt;                         // get the next form element
    &lt;                         var next ='.' + options.formCssClass);
    &lt;                         // move this form element below
    &lt;                         next.after(row);
    &lt;                         // change the order values
    &lt;                         order.val(parseInt(orderVal)+1);
    &lt;                         next.find('input:hidden[ id $= &quot;-ORDER&quot;]').val(orderVal);
    &lt;                         //updateElementIndex($(row), options.prefix, String(orderVal+1));
    &lt;                         //updateElementIndex($(next), options.prefix, String(orderVal));
    &lt;                     }
    &lt;                 })
    &lt;             }
    &lt;                 // added to hide 
    &lt;                 if (':checked')) {
    &lt;                     // this means that the form is already marked for deletion, 
    &lt;                     // so should hide it to remain consistent (occurs after failed validation)
    &lt;                     // at the django end
    &lt;                     hiddenDel = row.find('input:hidden[id $= &quot;-DELETE&quot;]');
    &lt;                     hiddenDel.val('on'); 
    &lt;                     row.hide();
    &lt;                 }
    &lt;             }
    &lt;             // this patch is to be able to work with the order field that is defined by 
    &lt;             // the &quot;can_order = True&quot; flag when creating the formset
    &lt;             // Django adds a text field (field name ORDER) with a numeric value
    &lt;             // specifying the order of the form elements.
    &lt;             var order = row.find('input:text[id $= &quot;-ORDER&quot;]');
    &lt;             if (order.length) {
    &lt;                 // check for an order value
    &lt;                 // if not defined, check for previous row, get order value from that, increment and assign.
    &lt;                 var thisOrderVal = order.val();
    &lt;                 if (!thisOrderVal.length) {
    &lt;                     thisOrderVal = row.prev('.' + options.formCssClass).find('input:hidden[id $= &quot;-ORDER&quot;]').val();
    &lt;                     if (thisOrderVal &amp;&amp; thisOrderVal.length) {
    &lt;                         thisOrderVal = parseInt(thisOrderVal)+1;
    &lt;                     }
    &lt;                 }
    &lt;                 if (!thisOrderVal) {
    &lt;                     // if we still don't have a value, we assume that this is the first entry, and it's empty, so 
    &lt;                     // we can assign it to be 1
    &lt;                     thisOrderVal = 1;
    &lt;                 }
    &lt;                 order.before(' ');
    &lt;                 order.remove();
    &lt;                 if (order.length) {
    &lt;                     insertMoveLinks(row);
    &lt;                 }
    &lt;             // if a form is submitted but there are errors, the form positions in the set
    &lt;             // are reset by django. This code will sort the forms according to their ORDER values
    &lt;             var count = parseInt($('#id_' + options.prefix + '-TOTAL_FORMS').val());
    &lt;             for (var i=1;i&lt;=count;i++){
    &lt;                 var row = $$.find('input:hidden[id $= &quot;-ORDER&quot;][value=&quot;'+String(i)+'&quot;]').parents('.' + options.formCssClass);
    &lt;                 if (row.get(0) != $('.' + options.formCssClass + ':last').get(0)) {
    &lt;                     $('.' + options.formCssClass + ':last').after(row);
    &lt;                 }
    &lt;             }
    &lt;                 // order patch, need to set the order value for the new entry
    &lt;                 row.find('input:hidden[id $= &quot;-ORDER&quot;]').val(formCount+1);
    &lt;         upText: 'move up',
    &lt;         upCssClass: 'up-link',
    &lt;         downText: 'move down',
    &lt;         downCssClass: 'down-link',
  4. ksamuel

    Thanks for sharing.

    The demo won’t work if you have non-ascii characters in the file path of the diretory where the django project lie because of a nasty Python 2.5 bug in os.path.join.

    If somebody encounter this problem, just move it to another directory and it works like a charm.

    Please not as well that due to jQuery 1.3 limitations, if a user hit the back button, all the fields he added vanhish. It should have been solved in jQuery 1.4.


  5. Alec Koumjian

    Hey Mike and elo80ka,

    Just curious about the integration with the empty_form functionality in 1.2. Having trouble figuring out how to use empty_form to generate a template.

  6. I used trunk, w/ empty_form functionality for my project, which uses inline formsets as rows. Worked awesomely! I had to make a few tweaks locally to the jquery file to make it work with my HTML, but other than that, I really appreciate the good work here.

  7. One thing i noticed about the functionality w/ tabular inlines is that the “add” button disappears when i delete all of my forms from my formsets and then throw a ValidationError in my formset’s clean() method (requires at least 2 forms) and reload the form. This is because there are no more ‘s and $$ is therefore empty so the add button doesn’t get added. I got around this by adding a to my with style=’display:none;’.

  8. trubliphone

    This is a great piece of code. I am trying to use it. However, I’m unclear how I can select the widgets of a _particular_ form within a formset (in javascript).

    In the form that my formset is bound to I have a javascript function associated with an event on a particular widget. That function ought to change the value of another widget on the form (they are two linked select widgets – the options for each come from a call to a remote server).

    I can add and delete forms no problem, thanks to your code.

    And I can change the content of one select widget based on the user selected value on the other select widget.

    However, when I try to do this on a form that has been added dynamically to the formset, it consistently changes the corresponding select widget on the first form in the formset only. How can I identify particular forms that have been dynamically generated?

    Many thanks for your help and time.

  9. @trubliphone: you should be able to re-bind your event handler in the `added` callback (if you downloaded the demo project, take a look at the AutoComplete examples). Basically, you’d have something like this:

      added: function(row) {
        // Event handlers are cloned with the form, which is why changing
        // the value in the cloned selects ends up changing the select value
        // in the first form.
        // You need to rebind the event handlers to the appropriate form controls. For example:
        var el = row.find('#firstSelect').unbind(); // Removes all existing handlers
        el.change(function(){...}); // Rebind the handler, pointing at the right field
  10. derek

    Stanislaus; I am unable to run the demo under Django 1.4 – two errors:

    1. settings:
    /usr/local/lib/python2.6/dist-packages/django/conf/ DeprecationWarning: The ADMIN_MEDIA_PREFIX setting has been removed; use STATIC_URL instead.
    “use STATIC_URL instead.”, DeprecationWarning)

    2. database:
    raise ImproperlyConfigured(“settings.DATABASES is improperly configured. ”
    django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.

  11. Serge Ilyn

    My project involves several inline formsets. I’m using Django 1.3, jQuery 1.7.1,
    Unfortunately, plugin (I tried version 1.2) does not work for me. If briefly, the problem is that the add, remove and delete functionality does not behave correctly.

    It is possible that I did not configure things correctly, but here are more details in what I observe:

    Let’s say I have 3 inline records rendered with one formset, and 2 inline records rendered with another formset – and I am trying to update the main record (and possibly all 5 inline records and possibly add more inlines). When I open the form with inlines, what I have is inline forms rendered (all five in two groups of 3 and 2). I also have “add” link for each group and I also have two “remove” links – one for each group.

    When I click on “add” link it adds as many additional inlines as there are records in the formset. Accordingly, in my example, in the first formset, it would add 3 additional records; “add” link for the second formset would add every time 2 additional records. (BTW, all added records are cloned – I am not thrilled with this approach, but it’s OK). For each added records, the “add” link would also add one “remove” link. It means that when I open the whole thing for the first time I would have two “remove” links (one for each formset). If after that I click on “add” two time for the formset with two records, I would have the total of SIX inlines for the the group with two inlines and three “remove” links – the last two “remove” links would be for the four newly added inlines.

    Add to this, the “delete” checkbox never appears. As far as I understand (but I may be wrong) this is the result of changed behavior in 1.2 version of the plugin, where “remove” links are used according to the documentation to delete records from the database.

    That’s not how the whole thing is supposed to work. Let’s go step by step.

    1. There should be a functionality to REMOVE newly added inlines (not yet saved to the database) from UI. The DELETE functionality (no matter how it’s done – via checkbox or a link) should not be available for records not yet saved to the database.

    2. For the existing records (a.k.a. persisted in the database), there should be a DELETE functionality but not REMOVE. I guess theoretically it’s possible to try to combine both in one, but if you have a complex form with several formsets where each formset may have several records, it becomes very confusing while editing records – there is no way to say what has been edited and what has been just added.

    3. There should be “ADD” functionality which should add as many new forms to the formset as specified in the “extra” option (for extra=0, one form is also added) when a formset is created by its factory. When new forms are added to the formset, they should be blank (not cloned by default) although having an option of cloning the last form in the formset (new or just added) can be a nice feature which a developer can use if needed.

    Again, if all my experience is the result of my poor configuration of the plug-in – my apology to you. In such case any guidelines how to do this corectly would be highly appreciated.

    Best regards,


  12. Hi @Serge,

    Could you post the HTML and relevant Javascript call for any one of the formsets in question? Just one should be sufficient (so there isn’t too much code).

  13. Serge Ilyn

    Thank you for responding. Just a couple of more details – I made sure that any other JS does not conflict (for example, sections with formsets are collapsible – if I remove all that extra stuff, it still behaves the same way). Also final HTML (see at the bottom seems to be OK)

    Here is template Django include with one of the formsets
    Relationships To Entries

            {% for form in to_formSet.forms %}
                    {% for fld in form.hidden_fields %}{{ fld }}{% endfor %}
                    {% if %}{{ form.DELETE }}{% endif %}
                        {% if form.type.errors %}{% for error in form.type.errors %}{% if not forloop.first %}{% endif %}{{ error|escape }}{% endfor %}{% endif %}
                        {% if %}{% for error in %}{% if not forloop.first %}{% endif %}{{ error|escape }}{% endfor %}{% endif %}
                        {% if form.description.errors %}{% for error in form.description.errors %}{% if not forloop.first %}{% endif %}{{ error|escape }}{% endfor %}{% endif %}
            {% endfor %}

    This is JS initialization

          $(document).ready(function() {
           $(function() {
                   prefix: '{{ to_formSet.prefix }}',
                   formCssClass: 'to-formset',
                   addText: 'Add target ("to") relationship',
                   deleteText: 'Remove target ("to") relationship',

    Final HTML (note that checkbox is in HTML, but I guess it’s hidden by the plugin code) for the case with one form in one formset – the second formset is empty:

    Relationships To Entries



    Select a Relationship type: it can be either a Shekn's default relationship type or relationship type created for the project.

    Relationship to entry (target)*

    Kendo UI 2
    Root entry for project 'Test 3'
    Test 3 Entry Relationship

    Select an Entry to which the current entry (source) will have a relationship to (target).


    Description of the relationship; max 200 chars.

    Relationships From Entries

  14. Serge Ilyn

    Oops – actual HTML is not rendered or stripped by WP. Please let me know if you need it – I will find a way to escape it or better if I can send by email if you need it.



  15. Serge Ilyn

    If email is OK, you can sent it to serge.ilyn at Notifications I get from WP are sent from “donotreply” so I huess i cannot contact you via email at this moment.

  16. Pingback: Django Form ; dynamic form, form clone « djangogal

  17. John

    Thanks for this great piece of code. It works just great for me. However I am up to the point of trying something a bit more complex, and went back to look at your examples. The example I was after is the autocomplete, but this functionality does not seem to be working. I was just wondering if I’m missing something, or if you had more documentation on working with the added function. If you’d point me in the right direction I’d most appreciate it.

    What I am trying to do is, have a tree menu on the side, when a person selects something from the tree I want to add a new row with some pre-populated data based on the selection. The added function I believe is the way to do this, but not sure where to begin.

    Thanks again for the great code, and for any advice you can give.

  18. @John: that does sound interesting. What it sounds like you’d need to do is:

    a) When an item is selected from your menu, call a function (JqueryUI-style) to add a new form;
    b) Use the `added` callback to pre-populate the form fields with the required data

    Right now, you can’t really do (a), though I suppose you could trigger the click event on the “add” button. I’d really be interested in adding this though (been thinking about it for a while now). Can you send me an email, if you’ve got time (my address is in the code)? Let’ssee if we can work something out.

  19. John


    I totally agree with your outlook on what is required, hence I wanted to see your examples for autocomplete which I believe utilize the add function. However as a first attempt I couldn’t get it working. I sent you an email as requested, but as yet have not gotten reply, so I’m hoping I didn’t send it to the wrong email address. If anyoneh as any notes or feedback on implementing the add function, I would much appreciate the help. Thanks in advance. (sorry for any typo’s but wordpress does not seem to function well with iexplorer)

  20. Thanks for this code. It does exactly what I need… except, I’d like to always have two form fields show by default (i.e. not attach a ‘remove’ to the first two fields).

    Any ideas on how I might acheive this?

    Thanks again :)

  21. Hi @Nicole,

    I’m afraid you’ll have to dig into the source for that…sorry :-(
    Shouldn’t be too hard to implement though — sounds like you want your formset to behave like Django admin inlines (I believe by default, no remove buttons are rendered for inlines that’ve been saved to the database). If you aren’t in an awful hurry, I could dig into the source, and see if I can come up with something?

    BTW, looks gorgeous :-)

  22. Hi Justin & Elo80ka,

    Thanks for your replies :)

    I’ve managed to work around my problem with some simple javascript.

    I’m just hiding links with the class ‘delete-row’ on document ready.

    This means that my default fields cannot be deleted, while fields added dynamically can be.

    @elo80ka thanks for your kind words on KabuCreative!

  23. Hi @Eimantas,

    The IDs you’re referring to are actually instance IDs, set by Django when an instance of your form is saved. From your screenshot (and explanation), it appears the last two forms were added dynamically? If so, they can’t have any IDs (since they haven’t been saved yet).

  24. Eimantas

    Hello Stanislaus,

    Yes, you are right, these are dynamically added fields. Thanks :)
    I am actually creating an app for summarizing lectures for students. The main App page should look like that:

    1st topic’s name
    1st lecture’s summary

    2nd topic’s name
    2nd lecture’s summary


    I am displaying my page using two different Model formsets and zipping them ( While I can successfully save existing topic names / summaries to database using Ajax / Jquery (, I am not sure how should I add new instances for model formsets and save them to database without reloading a page. I think that your app may be useful there, but I will need to modify it. Do you think is it even theoretically achievable ?

  25. @Eimantas: Sure is. Here are a couple of approaches you could try:

    Use jQuery Form to submit the formsets via AJAX; if the submission is successful, replace the existing form HTML with the new HTML returned from your view. There are some neat patterns for writing views that respond dfferently to normal and AJAX requests — for instance, if you switch from using render to using TemplateResponse, you could use a decorator to switch templates if it finds an “X-Requested-With” header with a value of “XMLHTTPRequest”.

    Ditch formsets entirely, and use a single Django form (or, in your case, two — for Topics and Summaries). Clicking on the “add” button would use AJAX to fetch the form; submitting would use AJAX to send it to the view, which would render a list snippet that you can add on the client. The upside of this, is you don’t need to re-submit the entire formset each time.

    Note that I’m assuming here (based on the code you shared) that you’re using a single HTML Form for multiple Django formsets.

  26. Eimantas

    Stanislaus > Umm I’m not sure that I understood what did you meant by your last sentence. Since Topic and Summary are different models, I have two different Model.formets that I am zipping (topic-lecture-topic-lecture etc.) and that’s pretty much it.
    Thank you very much for your tips. Since I am a beginner, it may take some time for me to try these different approaches. F.ex. today I’ve been learning how to serialize my form’s data to a simple Jquery string (it was easy) and to Json (This one wasn’t).
    If I will get stuck with the problem that I mentioned I will post a question there, hope that you will help me (if you will have time ofc.) Cheers :)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s