Introduction
Django formsets are a powerful tool for handling multiple instances of a form on a single page. They're particularly useful when dealing with one-to-many relationships between models, such as an Invoice and its related Lines. In this tutorial, we'll walk through creating an Invoice app with Django formsets, covering models, forms, views, and templates.
Setting Up the Project and App
Let's assume you have a Django project set up. Create a new app named 'invoice':
$ python manage.py startapp invoice
Defining Models
We'll create two models: Invoice and Line. The Invoice model will have a one-to-many relationship with Line.
from django.db import models class Invoice(models.Model): invoice_number = models.CharField(max_length=50) invoice_date = models.DateField() class Line(models.Model): invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE) description = models.CharField(max_length=255) quantity = models.PositiveIntegerField()
Creating Forms
We'll create two forms: InvoiceForm for the invoice details and LineFormSet for the invoice lines.
from django import forms from .models import Invoice, Line class InvoiceForm(forms.ModelForm): class Meta: model = Invoice fields = ['invoice_number', 'invoice_date'] LineFormSet = forms.inlineformset_factory(Invoice, Line, fields=('description', 'quantity', 'unit_price'), extra=1, can_delete=True)
Writing Views
We'll create a view to handle the formset and save the data.
from django.shortcuts import render from django.http import HttpResponseRedirect from .forms import InvoiceForm, LineFormSet from .models import Invoice def invoice_create(request): if request.method == 'POST': invoice_form = InvoiceForm(request.POST) line_formset = LineFormSet(request.POST) if invoice_form.is_valid() and line_formset.is_valid(): invoice = invoice_form.save() for line_form in line_formset: if line_form.cleaned_data: line = line_form.save(commit=False) line.invoice = invoice line.save() return HttpResponseRedirect('/invoice/success/') # Replace with appropriate URL else: invoice_form = InvoiceForm() line_formset = LineFormSet() return render(request, 'invoice/invoice_form.html', {'invoice_form': invoice_form, 'line_formset': line_formset})
Creating Templates
Create an HTML template to render the formset:
{% extends “base.html” %} {% block content %} <form method="post"> {% csrf_token %} {{ invoice_form.as_p }} {{ line_formset.management_form }} {% for form in line_formset %} {{ form.as_p }} {% endfor %} <button type="submit">Create Invoice</button> </form> {% endblock %}
Explanation
We've created two models, Invoice and Line, with a one-to-many relationship.
The InvoiceForm handles the invoice details.
The LineFormSet handles multiple instances of the Line model.
The view handles form submission, saving the invoice and its lines.
The template renders the invoice form and the line formset.
Additional Considerations
Form validation: Implement custom validation for fields as needed.
Error handling: Display error messages appropriately.
Form styling: Use CSS to improve the form's appearance.
Form optimization: Consider using JavaScript to enhance user experience.
Security: Protect against vulnerabilities like CSRF and XSS.
This is a simple example that you can have a solid foundation for using Django formsets to create dynamic and user-friendly forms for your applications.