Add Forms Dynamically In Django Using Formset And JavaScript
Last Updated :
29 Aug, 2024
Formsets in Django allow us to manage a collection of forms as a single unit. They are particularly useful when we need to handle multiple forms on a single page, like adding or editing multiple objects at once. However, a common requirement in many applications is the ability to add forms to a formset without a page reloading dynamically.
In this article, we’ll explore how to implement formsets in a Django project with a to-do list application, covering two use cases:
- Formset for a Normal Form
- Formset for a Model Form
Prerequisites: To follow along with this tutorial, we should have a basic understanding of Django forms and formsets, as well as some familiarity with JavaScript and jQuery.
We'll create a Django project named "GFGTodo" that allows us to manage a to-do list using formsets. This project will include dynamic form functionality for both normal and model forms.
Set Up the Django Project
Create a New Django Project
django-admin startproject GFGTodo
cd GFGTodo
Create a New Django App
python manage.py startapp todo
Edit GFGTodo/settings.py and add 'todo' to the INSTALLED_APPS list:
INSTALLED_APPS = [
# other apps
'todo',
]
Now, let's create a form for the Task
and set up a formset. Create a file named forms.py in the todo directory.
Here, we use the formset_factory
function is used to create a formset from a standard Django form.
Python
# todo/forms.py
from django import forms
from django.forms import formset_factory
class TaskForm(forms.Form):
name = forms.CharField(max_length=100, label='Task Name')
description = forms.CharField(widget=forms.Textarea, label='Description')
TaskFormSet = formset_factory(TaskForm, extra=1)
The number of forms rendered initially is controlled by the extra argument. The above formset will have a single form initially.
Create Views
In the view, we'll render the formset and handle the form submission. Edit views.py in the todo app:
Python
# todo/views.py
from django.shortcuts import render
from .forms import TaskFormSet
def task_list(request):
if request.method == 'POST':
formset = TaskFormSet(request.POST)
if formset.is_valid():
# Handle formset data
pass
else:
formset = TaskFormSet()
return render(request, 'todo/task_list.html', {'formset': formset})
After validating the form data we can play with the data submitted.
Create Templates
In the template, we'll render the formset and add the necessary HTML and JavaScript to dynamically add new forms. Create templates/todo/task_list.html:
HTML
<!DOCTYPE html>
<html>
<head>
<title>Task List</title>
<!-- Load jQuery from a CDN for handling dynamic behaviors -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1>Task List</h1>
<!-- Form to submit the formset data -->
<form method="post">
{% csrf_token %}
<!-- Management form, necessary for formset to track the number of forms -->
{{ formset.management_form }}
<!-- Container to hold all the form instances -->
<div id="formset">
{% for form in formset %}
<div class="form-row">
{{ form.as_p }}
<button type="button" class="remove-form">Remove</button>
</div>
{% endfor %}
</div>
<!-- Button to dynamically add a new form to the formset -->
<button type="button" id="add-form">Add Form</button>
<button type="submit">Save</button>
</form>
<script>
$(document).ready(function() {
// Initialize form index to the last form index in the formset
let formIdx = {{ formset.total_form_count|add:"-1" }};
// Click event handler to add a new form
$('#add-form').click(function() {
$('#formset').append($('#formset .form-row:last').clone().find('input').each(function() {
// Update the name attribute of the cloned form fields with the new form index
let name = $(this).attr('name').replace(/-\d+-/, '-' + formIdx + '-');
// Clear the value of the input field
$(this).attr('name', name).val('');
}).end().find('input[name$=-id]').val('').end().find('.remove-form').show().end());
formIdx++; // Increment the form index
// Update the management form's TOTAL_FORMS field
$('#id_form-TOTAL_FORMS').val(formIdx);
});
// Click event handler to remove a form
$('#formset').on('click', '.remove-form', function() {
// Remove the selected form row
$(this).closest('.form-row').remove();
formIdx--; // Decrement the form index
// Update the management form's TOTAL_FORMS field
$('#id_form-TOTAL_FORMS').val(formIdx);
});
// Initially hide the remove button for the existing forms
$('.remove-form').hide();
});
</script>
</body>
</html>
The {{ formset.management_form }}
is essential for Django to manage the number of forms in the formset correctly. It tracks how many forms are being sent back to the server.
Explanation of the Script:
- Dynamic Form Addition:
- When the "Add Form" button is clicked, the last form in the formset is cloned.
- The `name` attributes of the input fields in the cloned form are updated to reflect the new form index.
- The input fields are cleared to ensure the new form is blank.
- The total number of forms (`TOTAL_FORMS`) is updated to include the newly added form.
- Form Removal:
- Each form has a "Remove" button that, when clicked, removes the form from the DOM.
- The form index is decremented, and the `TOTAL_FORMS` field is updated accordingly.
- Initial Form Setup:
- The "Remove" button is hidden for the initial forms, assuming we don't want to allow the user to remove them by default. This can be customized based on our needs.
Add a URL pattern in todo/urls.py:
Python
# todo/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.task_list, name='task_list'),
]
Include this URL configuration in GFGTodo/urls.py:
Python
# GFGTodo/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('tasks/', include('todo.urls')),
]
Run the Server
python manage.py runserver
Output
Define the Model
We'll create a simple model representing an Task in an Todo List: Edit models.py in the todo app:
Python
# todo/models.py
from django.db import models
class Task(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
Here, we will use modelformset_factory for model-based forms, automatically handling form creation, validation, and saving of instances to the database. Update forms.py:
Python
# todo/forms.py
from django.forms import modelformset_factory
from .models import Task
TaskFormSet = modelformset_factory(Task, fields=('name', 'description'), extra=1)
Migrate the Database
Run the migrations to create the Task model table:
python manage.py makemigrations
python manage.py migrate
Here, we are updating the same view to handle the model formset. Update views.py:
Python
# todo/views.py
from django.shortcuts import render
from .forms import TaskFormSet
def task_list(request):
if request.method == 'POST':
formset = TaskFormSet(request.POST, queryset=Task.objects.all())
if formset.is_valid():
formset.save()
else:
formset = TaskFormSet(queryset=Task.objects.all())
return render(request, 'todo/task_list.html', {'formset': formset})
As the form is a model form, after performing the validation we call the save method to create Tasks.
Run the Server
python manage.py runserver
Output
Update forms.py to include both normal forms and model forms:
Python
# todo/forms.py
from django import forms
from django.forms import formset_factory, modelformset_factory
from .models import Task
class TaskForm(forms.Form):
name = forms.CharField(max_length=100, label='Task Name')
description = forms.CharField(widget=forms.Textarea, label='Description')
class TaskModelForm(forms.ModelForm):
class Meta:
model = Task
fields = ['name', 'description']
TaskFormSet = formset_factory(TaskForm, extra=1)
TaskModelFormSet = modelformset_factory(Task, form=TaskModelForm, extra=1)
Update the Views
Modify views.py to handle both formsets:
Python
# todo/views.py
from django.shortcuts import render
from .forms import TaskFormSet, TaskModelFormSet
def task_list(request):
if request.method == 'POST':
formset = TaskFormSet(request.POST)
model_formset = TaskModelFormSet(request.POST)
if formset.is_valid() and model_formset.is_valid():
# Process normal formset data
# Process model formset data
formset.save()
model_formset.save()
else:
formset = TaskFormSet()
model_formset = TaskModelFormSet(queryset=Task.objects.all())
return render(request, 'todo/task_list.html', {'formset': formset, 'model_formset': model_formset})
Update Templates
Update task_list.html to include both formsets:
HTML
<!DOCTYPE html>
<html>
<head>
<title>Task List</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1>Task List</h1>
<h2>Normal Formset</h2>
<form method="post" id="normal-formset">
{% csrf_token %}
{{ formset.management_form }}
<div id="formset">
{% for form in formset %}
<div class="form-row">
{{ form.as_p }}
<button type="button" class="remove-form">Remove</button>
</div>
{% endfor %}
</div>
<button type="button" id="add-form">Add Form</button>
<button type="submit">Save Normal Forms</button>
</form>
<h2>Model Formset</h2>
<form method="post" id="model-formset">
{% csrf_token %}
{{ model_formset.management_form }}
<div id="model_formset">
{% for form in model_formset %}
<div class="form-row">
{{ form.as_p }}
<button type="button" class="remove-form">Remove</button>
</div>
{% endfor %}
</div>
<button type="button" id="add-model-form">Add Model Form</button>
<button type="submit">Save Model Forms</button>
</form>
<script>
$(document).ready(function() {
// Handling Normal Formset
let formIdx = {{ formset.total_form_count|add:"-1" }};
$('#add-form').click(function() {
$('#formset').append($('#formset .form-row:last').clone().find('input').each(function() {
let name = $(this).attr('name').replace(/-\d+-/, '-' + formIdx + '-');
$(this).attr('name', name).val('');
}).end().find('input[name$=-id]').val('').end().find('.remove-form').show().end());
formIdx++;
$('#id_form-TOTAL_FORMS').val(formIdx);
});
$('#formset').on('click', '.remove-form', function() {
$(this).closest('.form-row').remove();
formIdx--;
$('#id_form-TOTAL_FORMS').val(formIdx);
});
$('.remove-form').hide(); // Hide remove button for initial forms
// Handling Model Formset
let modelFormIdx = {{ model_formset.total_form_count|add:"-1" }};
$('#add-model-form').click(function() {
$('#model_formset').append($('#model_formset .form-row:last').clone().find('input').each(function() {
let name = $(this).attr('name').replace(/-\d+-/, '-' + modelFormIdx + '-');
$(this).attr('name', name).val('');
}).end().find('input[name$=-id]').val('').end().find('.remove-form').show().end());
modelFormIdx++;
$('#id_form-TOTAL_FORMS').val(modelFormIdx);
});
$('#model_formset').on('click', '.remove-form', function() {
$(this).closest('.form-row').remove();
modelFormIdx--;
$('#id_form-TOTAL_FORMS').val(modelFormIdx);
});
$('.remove-form').hide(); // Hide remove button for initial forms
});
</script>
</body>
</html>
Run the Server
python manage.py runserver
Output
Conclusion
By following this article, we have learned how to dynamically add forms to a Django formset in three different scenarios:
- Normal Forms: Allows users to manage multiple instances of a non-model form.
- Model Forms: Enables users to manage multiple instances of a model form tied to a database.
- Combination of Both: Handles both normal forms and model forms in a single view.
This approach not only enhances user experience but also streamlines form management in complex applications. Feel free to customize and expand upon this basic setup to fit your specific needs.
Similar Reads
How to create a form using Django Forms ?
Django forms are an advanced set of HTML forms that can be created using python and support all features of HTML forms in a pythonic way. This post revolves around how to create a basic form using various Form Fields and attributes. Creating a form in Django is completely similar to creating a model
3 min read
How to dynamically create new elements in JavaScript ?
New elements can be dynamically created in JavaScript with the help of createElement() method. The attributes of the created element can be set using the setAttribute() method. The examples given below would demonstrate this approach. Example 1: In this example, a newly created element is added as a
4 min read
Dynamic HTML Using Handlebars JavaScript
The Dynamic HTML Using Handlebars JavaScript consists of creating the user interactive web pages by combining the HTML with the Handlebars templating engine. The Handlebars mainly ease the process of dynamically rendering the HTML content by allowing the insertion of variables, expressions, and logi
3 min read
How to Dynamically Add Select Fields Using jQuery?
Select Fields can be added dynamically using jQuery. This increases the flexibility and interactivity of web forms, allowing users to add or remove options as needed. In this article, we will learn two different approaches to Dynamically Add Select Fields Using jQuery. These are the following approa
3 min read
How to pass data to javascript in Django Framework ?
Django is a python framework for web development which works on jinja2 templating engine. When data is rendered along with the template after passing through views.py, that data becomes static on the html file along which it was rendered. As django is a backend framework, hence to use the power of p
3 min read
Initial form data - Django Forms
When using Django forms, we may want to automatically fill in some fields with default values. This is called initial data and it's different from placeholders because it actually fills the fields. When the form is submitted, this data is treated just like anything the user types in.Django offers mu
3 min read
How to add and remove input fields dynamically using jQuery with Bootstrap ?
In this article, we will learn how to add and remove input fields dynamically using jQuery with Bootstrap. The input fields will be added and removed from the HTML document dynamically using jQuery.In this process, we will use the following multiple functions.Click() Method: Used to attach the click
3 min read
How to get form data using JavaScript/jQuery?
The serializeArray() method creates an array of objects (name and value) by serializing form values. This method can be used to get the form data. Syntax: $(selector).serializeArray() Parameter: It does not accept any parameter. Return Value: It returns all the value that is inside the inputs fields
1 min read
{{ form.as_ul }} - Render Django Forms as list
Django forms are an advanced set of HTML forms that can be created using python and support all features of HTML forms in a pythonic way. Rendering Django Forms in the template may seem messy at times but with proper knowledge of Django Forms and attributes of fields, one can easily create excellent
2 min read
How to add input fields dynamically on button click in AngularJS ?
The task is to add an input field on the page when the user clicks on the button using AngularJs. Steps: The required component for the operation is created (add-inputComponent). In that component, html file (add-input.component.html) required html is written. In that HTML, the main div for input fi
2 min read