Vistas genéricas CRUD

Intermedio
Django
Django
Actualizado: 18/04/2026

CreateView: formularios de creación

CreateView gestiona el ciclo completo de creación de un objeto: mostrar el formulario en GET y procesarlo en POST con validación:

# views.py
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Producto
from .forms import ProductoForm

class ProductoCreateView(LoginRequiredMixin, CreateView):
    model = Producto
    form_class = ProductoForm
    template_name = 'catalogo/producto_form.html'
    success_url = reverse_lazy('producto-list')

    def form_valid(self, form):
        """Se llama cuando el formulario es válido antes de guardar."""
        form.instance.creado_por = self.request.user
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['titulo'] = 'Nuevo producto'
        context['boton'] = 'Crear producto'
        return context

Diagrama conceptual de Vistas genéricas CRUD

Si no se específica form_class, CreateView genera el formulario automáticamente a partir del modelo con el atributo fields:

class ProductoCreateView(CreateView):
    model = Producto
    fields = ['nombre', 'precio', 'categoria', 'descripcion']
    success_url = reverse_lazy('producto-list')

Plantilla compartida para crear y editar

<!-- catalogo/producto_form.html -->
{% extends "base.html" %}

{% block contenido %}
<h1>{{ titulo|default:"Producto" }}</h1>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">{{ boton|default:"Guardar" }}</button>
    <a href="{% url 'producto-list' %}">Cancelar</a>
</form>
{% endblock %}

UpdateView: formularios de edición

UpdateView recupera el objeto por pk o slug, pre-rellena el formulario con sus valores actuales y lo procesa en POST:

class ProductoUpdateView(LoginRequiredMixin, UpdateView):
    model = Producto
    form_class = ProductoForm
    template_name = 'catalogo/producto_form.html'

    def get_success_url(self):
        """Redirige a la página de detalle del objeto editado."""
        return reverse_lazy('producto-detail', kwargs={'pk': self.object.pk})

    def get_form(self, form_class=None):
        """Personaliza el formulario según el usuario."""
        form = super().get_form(form_class)
        if not self.request.user.is_staff:
            form.fields['precio'].disabled = True
        return form

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['titulo'] = f'Editar: {self.object.nombre}'
        context['boton'] = 'Guardar cambios'
        return context

DeleteView: confirmación de borrado

DeleteView solicita confirmación antes de eliminar el objeto. En GET muestra la plantilla de confirmación; en POST realiza el borrado:

class ProductoDeleteView(LoginRequiredMixin, DeleteView):
    model = Producto
    template_name = 'catalogo/producto_confirm_delete.html'
    success_url = reverse_lazy('producto-list')

    def get_queryset(self):
        """Solo permite eliminar productos del usuario autenticado."""
        return super().get_queryset().filter(creado_por=self.request.user)
<!-- catalogo/producto_confirm_delete.html -->
{% extends "base.html" %}
{% block contenido %}
<h2>¿Eliminar "{{ object.nombre }}"?</h2>
<p>Esta acción no se puede deshacer.</p>
<form method="post">
    {% csrf_token %}
    <button type="submit" class="btn-danger">Eliminar</button>
    <a href="{% url 'producto-detail' object.pk %}">Cancelar</a>
</form>
{% endblock %}

URLs para el CRUD completo

# urls.py
from django.urls import path
from .views import (
    ProductoListView, ProductoDetailView,
    ProductoCreateView, ProductoUpdateView, ProductoDeleteView
)

urlpatterns = [
    path('', ProductoListView.as_view(), name='producto-list'),
    path('<int:pk>/', ProductoDetailView.as_view(), name='producto-detail'),
    path('nuevo/', ProductoCreateView.as_view(), name='producto-create'),
    path('<int:pk>/editar/', ProductoUpdateView.as_view(), name='producto-update'),
    path('<int:pk>/eliminar/', ProductoDeleteView.as_view(), name='producto-delete'),
]

success_url y get_success_url

success_url acepta una URL estática definida con reverse_lazy(). Para URLs dinámicas que dependen del objeto recién creado o actualizado, hay que sobreescribir get_success_url():

def get_success_url(self):
    return reverse_lazy('producto-detail', kwargs={'pk': self.object.pk})

reverse_lazy() se usa en lugar de reverse() porque los atributos de clase se evalúan antes de que el sistema de URLs esté completamente configurado.

Sobreescribir form_valid

El método form_valid se ejecuta cuando el formulario supera la validación. Sobreescribirlo permite añadir lógica adicional antes o después de guardar:

def form_valid(self, form):
    # Asignar datos antes de guardar
    form.instance.creado_por = self.request.user
    form.instance.ultima_modificacion = timezone.now()

    # Llamar al padre para guardar y redirigir
    response = super().form_valid(form)

    # Lógica posterior al guardado
    messages.success(self.request, f'Producto "{self.object.nombre}" guardado correctamente.')
    return response

Las vistas genéricas CRUD de Django eliminan la mayor parte del código repetitivo y permiten construir un panel de gestión completo con pocas líneas de código, manteniendo la flexibilidad para personalizar cada aspecto del proceso.

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 vistas de creación de objetos con CreateView y formularios ModelForm integrados. Implementar vistas de edición con UpdateView controlando los campos editables. Configurar DeleteView con confirmación antes de eliminar un registro. Definir success_url para redirigir al usuario tras una operación exitosa. Sobreescribir form_valid y get_form para personalizar el comportamiento del formulario.