Ruddra.com

Working with Formsets

Working with Formsets

This post is deprecated and may not be useful.

As django documentation says:

A formset is a layer of abstraction to work with multiple forms on the same page. It can be best compared to a data grid.

So here I am going to show a very simple django formset implementation example.

Here we are going to use the following model, form, template, view:

Model

class Product(models.Model):
    name = models.CharField(max_length=50)
    quantity = models.IntegerField()
    price = models.IntegerField()

class Distributor(models.Model):
    name = models.CharField(max_length=100)
    products= models.ManyToManyField(Product)

These fairly simple models, where product is related to distributor model by a many-to-many relation.

Form

First we declare productform, then using formset factory helps to create multiple instances of product. Then we add this to distributor form like below:

from django import forms
from django.forms.formsets import formset_factory
class ProductForm(forms.Form):
    name = forms.CharField()
    quantity = forms.IntegerField()
    price = forms.IntegerField()

ProductFormset= formset_factory(ProductForm)

class DistributorForm(forms.Form):
    name= forms.CharField()
    products= ProductFormset()

Now we use this form in template.

Template

<form action="" method="post" class="">
    {% csrf_token %}
    <h2> Distributors :</h2>
    {% for field in form %}
        {{ field.errors }}
        {{ field.label_tag }} : {{ field }}
        {{ form.products.management_form }}

        <h3> Product Instance(s)</h3>
        <table id="table-product">
        {% form.products.all %}
            <thead>
                <tr>
                <th>name</th>
                <th>quantity</th>
                <th>price</th>
                </tr>
            </thead>
            <tbody class="product-instances">
                <tr>
                <td>{{ form.product }}</td>
                <td>{{ form.product }}</td>
                <td>{{ form.product }}</td>
                <td> <input id="input_add" type="button" name="add" value=" Add More " class="tr_clone_add btn data_input"> </td>
                </tr>
            </tbody>
        {% endfor %}
        </table>
    <input type="submit" name="submit" class="button" value="Save"/>
    </form>

<script>
    var i = 1;
    $("#input_add").click(function() {
        $("tbody tr:first").clone().find(".data_input").each(function() {
            if ($(this).attr('class')== 'tr_clone_add btn data_input'){
                $(this).attr({
                    'id': function(_, id) { return "remove_button" },
                    'name': function(_, name) { return "name_remove" +i },
                    'value': 'Remove'
                }).on("click", function(){
                    var a = $(this).parent();
                    var b= a.parent();
                    i=i-1
                    $('#id_form-TOTAL_FORMS').val(i);
                    b.remove();

                    $('.product-instances tr').each(function(index, value){
                        $(this).find('.data_input').each(function(){
                            $(this).attr({
                                'id': function (_, id) {
                                    var idData= id;
                                    var splitV= String(idData).split('-');
                                    var fData= splitV[0];
                                    var tData= splitV[2];
                                    return fData+ "-" +index + "-" + tData
                                },
                                'name': function (_, name) {
                                    var nameData= name;
                                    var splitV= String(nameData).split('-');
                                    var fData= splitV[0];
                                    var tData= splitV[2];
                                    return fData+ "-" +index + "-" + tData
                                }
                            });
                        })
                    })
                })
            }
            else{
                $(this).attr({
                    'id': function (_, id) {
                        var idData= id;
                        var splitV= String(idData).split('-');
                        var fData= splitV[0];
                        var tData= splitV[2];
                        return fData+ "-" +i + "-" + tData
                    },
                    'name': function (_, name) {
                        var nameData= name;
                        var splitV= String(nameData).split('-');
                        var fData= splitV[0];
                        var tData= splitV[2];
                        return fData+ "-" +i + "-" + tData
                    }
                });

            }
        }).end().appendTo("tbody");
        $('#id_form-TOTAL_FORMS').val(1+i);
        i++;

    });
</script>

The html part is fairly simple, like using form in template. Then the JS is being used so that multiple instances of product form can be generated like:

<!-- First row of the table -->

<tr>
  <td><input type="text" name="form-0-name" id="id_form-0-name" /></td>
  <td>
    <input type="number" name="form-0-quantity" id="id_form-0-quantity" />
  </td>
  <td><input type="number" name="form-0-price" id="id_form-0-price" /></td>
  <td>
    <input
      id="input_add"
      type="button"
      name="add"
      value=" Add More "
      class="tr_clone_add btn data_input"
    />
  </td>
</tr>

<!-- Second row of the table -->

<tr>
  <td><input type="text" name="form-1-name" id="id_form-1-name" /></td>
  <td>
    <input type="number" name="form-1-quantity" id="id_form-1-quantity" />
  </td>
  <td><input type="number" name="form-1-price" id="id_form-1-price" /></td>
  <td>
    <input
      id="remove_button"
      type="button"
      name="remove_button1"
      value=" Remove "
      class="tr_clone_add btn data_input"
    />
  </td>
</tr>

<!-- more inline formset are going to rendered here -->

View

Here values from the form are being saved to database.

def post(request):
        form = DistributorForm(request.POST)
        form.product_instances = ProductFormset(request.POST)
        if form.is_valid():
            distributor= Distributor() #model class
            distributor.name= form.cleaned_data('name')
            distributor.save()
            if form.product_instances.cleaned_data is not None:
                for items in form.product_instances.cleaned_data:
                    product = Product() #Product model class
                    product.name= item['name']
                    product.quantity= item['quantity']
                    product.price= item['price']
                    product.save()
                    distributor.products.add(product)
            return redirect('/success')
        return redirect('/failure')

Notes to keep in mind:

First, need to be careful about things like:

<input
  type="hidden"
  name="form-TOTAL_FORMS"
  value="1"
  id="id_form-TOTAL_FORMS"
/>
<input
  type="hidden"
  name="form-INITIAL_FORMS"
  value="0"
  id="id_form-INITIAL_FORMS"
/>
<input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />

Here form-TOTAL_FORMS ’s value should be equal to number of rows in table. The code above must exist in order to formset to work.

Second, in views.py, formset form class needs to be called, else cleaned data within the formset can’t be found.

form.product_instances = ProductFormset(request.POST)

Thats all.

Last updated: Mar 18, 2024


← Previous
Sample Ajax GET/POST Request in Django

Let us make a test scenario here: A dropdown field which on change we are going to send a Get/Post …

Next →
Some Useful Tools/Function for Django

This post is deprecated as its based on Django 1.6 mostly. I am going to share some useful Django …

Share Your Thoughts
M↓ Markdown