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

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