Formset
I Formset di Django permettono di gestire piu istanze dello stesso form in una singola pagina. Sono particolarmente utili quando devi creare o modificare piu oggetti contemporaneamente, come righe di un ordine o una lista di contatti.
formset_factory
La funzione formset_factory crea un formset a partire da un form standard:
from django import forms
from django.forms import formset_factory
class IngredienteForm(forms.Form):
nome = forms.CharField(max_length=100)
quantita = forms.CharField(max_length=50)
IngredienteFormSet = formset_factory(
IngredienteForm,
extra=3, # numero di form vuoti da mostrare
max_num=10, # numero massimo di form
min_num=1, # numero minimo di form
)
Usare un Formset nella View
La gestione di un formset nella view segue lo stesso schema dei form singoli:
from django.shortcuts import render, redirect
def aggiungi_ingredienti(request):
if request.method == 'POST':
formset = IngredienteFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data:
nome = form.cleaned_data['nome']
quantita = form.cleaned_data['quantita']
# Salva ogni ingrediente
return redirect('lista_ingredienti')
else:
formset = IngredienteFormSet()
return render(request, 'ingredienti.html', {'formset': formset})
Nel template, il formset si renderizza cosi:
# <form method="post">
# {% csrf_token %}
# {{ formset.management_form }}
# {% for form in formset %}
# <div class="ingrediente">
# {{ form.as_p }}
# </div>
# {% endfor %}
# <button type="submit">Salva</button>
# </form>
Il management_form e obbligatorio: contiene i campi nascosti necessari per gestire il formset.
modelformset_factory
La funzione modelformset_factory crea un formset collegato a un modello del database:
from django.forms import modelformset_factory
from .models import Prodotto
ProdottoFormSet = modelformset_factory(
Prodotto,
fields=['nome', 'prezzo', 'disponibile'],
extra=2,
can_delete=True, # aggiunge un checkbox per eliminare
)
Nella view, il formset carica automaticamente i dati dal database:
def gestisci_prodotti(request):
if request.method == 'POST':
formset = ProdottoFormSet(request.POST)
if formset.is_valid():
formset.save()
return redirect('lista_prodotti')
else:
formset = ProdottoFormSet(queryset=Prodotto.objects.filter(disponibile=True))
return render(request, 'prodotti.html', {'formset': formset})
Il parametro queryset permette di filtrare quali oggetti mostrare nel formset.
inlineformset_factory
La funzione inlineformset_factory e perfetta per modelli con relazione ForeignKey. Permette di gestire gli oggetti correlati direttamente dalla pagina del modello padre:
from django.forms import inlineformset_factory
from .models import Ordine, RigaOrdine
RigaOrdineFormSet = inlineformset_factory(
Ordine, # modello padre
RigaOrdine, # modello figlio
fields=['prodotto', 'quantita', 'prezzo_unitario'],
extra=3,
can_delete=True,
)
from django.shortcuts import get_object_or_404
def modifica_ordine(request, pk):
ordine = get_object_or_404(Ordine, pk=pk)
if request.method == 'POST':
formset = RigaOrdineFormSet(request.POST, instance=ordine)
if formset.is_valid():
formset.save()
return redirect('dettaglio_ordine', pk=ordine.pk)
else:
formset = RigaOrdineFormSet(instance=ordine)
return render(request, 'modifica_ordine.html', {
'ordine': ordine,
'formset': formset,
})
Il parametro instance collega il formset allâoggetto padre.
Validazione del Formset
Puoi personalizzare la validazione di un formset creando una classe personalizzata:
from django.forms import BaseFormSet, formset_factory, ValidationError
class BaseIngredienteFormSet(BaseFormSet):
def clean(self):
super().clean()
nomi = []
for form in self.forms:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
nome = form.cleaned_data.get('nome')
if nome in nomi:
raise ValidationError("Non puoi inserire ingredienti duplicati.")
nomi.append(nome)
IngredienteFormSet = formset_factory(
IngredienteForm,
formset=BaseIngredienteFormSet,
extra=3,
)
Dati Iniziali
Puoi passare dati iniziali a un formset non collegato a un modello:
def aggiungi_ingredienti(request):
dati_iniziali = [
{'nome': 'Farina', 'quantita': '500g'},
{'nome': 'Zucchero', 'quantita': '200g'},
]
formset = IngredienteFormSet(initial=dati_iniziali)
return render(request, 'ingredienti.html', {'formset': formset})
Conclusione
I Formset sono uno strumento indispensabile quando devi gestire piu form dello stesso tipo in una pagina. Con formset_factory per form generici, modelformset_factory per modelli e inlineformset_factory per relazioni padre-figlio, Django copre tutti gli scenari piu comuni nella gestione di dati multipli.