Working with Formsets
Sep 17, 2015 · 4 Min Read · 1 Like · 3 CommentsThis 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: Jul 13, 2024
I won't spam you. Unsubscribe at any time.