Formsets en Django

Avanzado
Django
Django
Actualizado: 18/04/2026

¿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.

Diagrama conceptual de Formsets en Django

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 - Autor del tutorial

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.