¿Qué son los formsets?
Los formsets son una colección de formularios del mismo tipo que se procesan juntos. Son ideales para editar múltiples objetos de un modelo en una sola página: las líneas de un pedido, los participantes de un evento, las imágenes de un producto, etc.

formset_factory para Form simples
formset_factory crea un formset a partir de un formulario Form estándar:
# forms.py
from django import forms
class ParticipanteForm(forms.Form):
nombre = forms.CharField(max_length=100)
email = forms.EmailField()
telefono = forms.CharField(max_length=15, required=False)
# views.py
from django.forms import formset_factory
from .forms import ParticipanteForm
ParticipanteFormSet = formset_factory(
ParticipanteForm,
extra=3, # Número de formularios vacíos adicionales
max_num=10, # Máximo de formularios permitidos
can_delete=True # Permite marcar formularios para eliminar
)
def registrar_participantes(request, evento_id):
if request.method == 'POST':
formset = ParticipanteFormSet(request.POST)
if formset.is_valid():
for form in formset:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
# Crear el participante
Participante.objects.create(
evento_id=evento_id,
**{k: v for k, v in form.cleaned_data.items() if k != 'DELETE'}
)
return redirect('evento-detail', pk=evento_id)
else:
formset = ParticipanteFormSet()
return render(request, 'eventos/participantes.html', {'formset': formset})
modelformset_factory para modelos
modelformset_factory crea un formset vinculado directamente a un modelo:
from django.forms import modelformset_factory
from .models import Producto
ProductoFormSet = modelformset_factory(
Producto,
fields=['nombre', 'precio', 'activo'],
extra=0, # No añadir formularios vacíos extra
can_delete=True
)
def editar_productos(request):
queryset = Producto.objects.filter(categoria__slug='electronica')
if request.method == 'POST':
formset = ProductoFormSet(request.POST, queryset=queryset)
if formset.is_valid():
formset.save() # Crea, actualiza y elimina automáticamente
return redirect('producto-list')
else:
formset = ProductoFormSet(queryset=queryset)
return render(request, 'catalogo/editar_productos.html', {'formset': formset})
inlineformset_factory para relaciones ForeignKey
inlineformset_factory es la opción más común: gestiona objetos relacionados con un modelo padre a través de ForeignKey:
from django.forms import inlineformset_factory
from .models import Pedido, LineaPedido
LineaPedidoFormSet = inlineformset_factory(
Pedido, # Modelo padre
LineaPedido, # Modelo hijo (tiene ForeignKey a Pedido)
fields=['producto', 'cantidad', 'precio_unitario'],
extra=3,
can_delete=True,
min_num=1, # Mínimo 1 línea obligatoria
validate_min=True
)
def crear_pedido(request):
if request.method == 'POST':
form = PedidoForm(request.POST)
formset = LineaPedidoFormSet(request.POST)
if form.is_valid() and formset.is_valid():
pedido = form.save()
lineas = formset.save(commit=False)
for linea in lineas:
linea.pedido = pedido
linea.save()
formset.save_m2m()
return redirect('pedido-detail', pk=pedido.pk)
else:
form = PedidoForm()
formset = LineaPedidoFormSet()
return render(request, 'pedidos/crear.html', {
'form': form,
'formset': formset,
})
Con el objeto padre ya existente, el formset se inicializa con la instancia:
def editar_pedido(request, pk):
pedido = get_object_or_404(Pedido, pk=pk)
if request.method == 'POST':
form = PedidoForm(request.POST, instance=pedido)
formset = LineaPedidoFormSet(request.POST, instance=pedido)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
return redirect('pedido-detail', pk=pedido.pk)
else:
form = PedidoForm(instance=pedido)
formset = LineaPedidoFormSet(instance=pedido)
return render(request, 'pedidos/editar.html', {'form': form, 'formset': formset})
Renderizar formsets en plantillas
Los formsets requieren el management_form para gestionar el número de formularios:
<form method="post">
{% csrf_token %}
<!-- Formulario principal (si existe) -->
{{ form.as_p }}
<!-- Management form: OBLIGATORIO para que el formset funcione -->
{{ formset.management_form }}
<!-- Renderizar cada formulario del formset -->
{% for form in formset %}
<div class="formset-row {% if form.instance.pk %}existente{% else %}nuevo{% endif %}">
{{ form.id }} {# Campo oculto para el ID del objeto #}
{% for field in form.visible_fields %}
<div class="campo">
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
{% if formset.can_delete %}
<label>
{{ form.DELETE }}
Eliminar esta línea
</label>
{% endif %}
</div>
{% endfor %}
<button type="submit">Guardar</button>
</form>
Validación a nivel de formset
Es posible sobreescribir el formset para añadir validación que afecte al conjunto:
from django.forms import BaseInlineFormSet
from django.core.exceptions import ValidationError
class LineaPedidoFormSetBase(BaseInlineFormSet):
def clean(self):
"""Valida que el total del pedido no supere el límite."""
if any(self.errors):
return
total = sum(
form.cleaned_data.get('precio_unitario', 0) * form.cleaned_data.get('cantidad', 0)
for form in self.forms
if not form.cleaned_data.get('DELETE', False)
)
if total > 10000:
raise ValidationError('El importe total del pedido no puede superar los 10.000 €.')
LineaPedidoFormSet = inlineformset_factory(
Pedido, LineaPedido,
formset=LineaPedidoFormSetBase,
fields=['producto', 'cantidad', 'precio_unitario'],
extra=3, can_delete=True
)
Los formsets eliminan la necesidad de implementar manualmente la lógica de gestión de colecciones de formularios, ofreciendo un mecanismo robusto y consistente con el resto del sistema de formularios de Django.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear un formset básico con formset_factory para gestionar múltiples instancias de un Form. Usar modelformset_factory para formsets vinculados a modelos Django. Implementar inlineformset_factory para gestionar modelos relacionados por ForeignKey. Procesar formsets en vistas con is_valid() y save(). Renderizar formsets en plantillas con management form y campos individuales.