Módulo 4 · Lección 24 de 45
CreateView, UpdateView y DeleteView
Las tres vistas que faltan para el CRUD completo, el formulario que se genera solo, y adónde se va después de guardar.
Con la lección anterior tienes las dos páginas de lectura. Faltan las tres de escritura, y son justo las que la lección 16 dejó a medias, con aquel ejemplo marcado como incorrecto a propósito: sin validación, sin protección y con un MultiValueDictKeyError esperando.
Estas tres clases resuelven las tres cosas. Y hay que decir una cosa de entrada: usan formularios por debajo, y los formularios son el bloque siguiente entero. Aquí se ven funcionando y se explica lo que hace falta para entenderlas; el detalle de cómo se validan los datos viene después.
CreateView#
from django.views.generic.edit import CreateView
class CrearContacto(CreateView):
model = Contacto
fields = ['nombre', 'correo', 'telefono', 'grupo']<form method="post">{% csrf_token %}
{{ form.as_p }}
<button>Guardar</button>
</form>Eso es todo, y lo que sale es esto:
<input type="hidden" name="csrfmiddlewaretoken" value="vBN8G9mDNG...">
<label for="id_nombre">
<input type="text" name="nombre" maxlength="100" required id="id_nombre">
<label for="id_correo">
<input type="email" name="correo" maxlength="254" required id="id_correo">
<label for="id_telefono">
<input type="text" name="telefono" maxlength="20" id="id_telefono">
<label for="id_grupo">
<select name="grupo" id="id_grupo">Merece la pena mirar ese HTML con calma, porque cada detalle sale de algo que escribiste en el bloque B.
El type="email" sale del EmailField. El maxlength="100" sale del max_length del modelo. El required del nombre está y el del teléfono no, porque el teléfono tenía blank=True, que es exactamente lo que la lección 9 dijo que significaba blank: cosa de formularios. Y el grupo es un <select> porque es un ForeignKey, con las opciones sacadas de la tabla.
O sea: el formulario es una traducción del modelo. Eso es lo que hace un ModelForm, y es lo que la vista ha construido sola a partir de model y fields.
El {% csrf_token %} es la protección que faltaba en la lección 16. Sin él, el POST da 403. Lo que hace y por qué se explica en el bloque D, pero la regla se puede aprender hoy: todo formulario con method="post" lleva {% csrf_token %} dentro.
UpdateView#
class EditarContacto(UpdateView):
model = Contacto
fields = ['nombre', 'correo', 'telefono', 'grupo']path('cbv/<int:pk>/editar/', views_clase.EditarContacto.as_view(), name='cbv-editar'),value="Ana Ruiz"
value="ana@ejemplo.es"
value=""
value="1"Idéntica a la de crear salvo por una cosa: la ruta captura un pk, la vista busca el objeto como hace DetailView, y el formulario sale relleno.
Y como se avisó en la lección anterior, las dos usan la misma plantilla, modelo_form.html. Cuando hace falta distinguirlas, el truco es mirar si hay objeto:
<h1>{% if object %}Editar {{ object }}{% else %}Nuevo contacto{% endif %}</h1>En CreateView, object vale None.
Adónde va después de guardar#
POST nuevo -> 302 /contactos/6/Un 302 a la ficha del contacto recién creado, y en ninguna de las dos vistas hay escrita una dirección. Sale de esto:
def get_absolute_url(self):
return reverse('contactos:detalle', kwargs={'pk': self.pk})Esa es la recompensa de las tres líneas de la lección 21. Sin ellas:
ImproperlyConfigured: No URL to redirect to. Either provide a url or
define a get_absolute_url method on the Model.El mensaje dice las dos opciones. La otra es el atributo:
success_url = reverse_lazy('contactos:cbv-lista')Y ahí hay un detalle que confunde la primera vez: reverse_lazy, no reverse. Como es un atributo de clase, se evalúa al importar el módulo, y en ese momento las rutas todavía no están cargadas. La versión perezosa no resuelve la dirección hasta que se usa. La regla práctica: reverse dentro de un método, reverse_lazy en un atributo de clase.
Lo que está pasando por debajo es el patrón de la lección 2. Si los datos valen, se guarda y se redirige; si no valen, se vuelve a pintar el formulario con los errores y con lo que ya estaba escrito:
POST inválido -> 200200 y no 302, porque no se ha ido a ninguna parte.
Los dos métodos que se sobrescriben#
Casi todo lo que se necesita cambiar está en uno de estos dos.
form_valid, que corre cuando los datos son correctos y antes de redirigir. Es el sitio de lo que el formulario no puede saber:
def form_valid(self, formulario):
formulario.instance.propietario = self.request.user
return super().form_valid(formulario)Ese formulario.instance es el objeto todavía sin guardar. Se le añade lo que falte y se deja que el padre haga el resto. Es el patrón para rellenar campos automáticos, y vuelve en el bloque de usuarios.
get_success_url, cuando el destino depende de algo:
def get_success_url(self):
return self.object.get_absolute_url()Después de guardar, self.object es el objeto guardado, ya con su pk.
DeleteView#
class BorrarContacto(DeleteView):
model = Contacto
success_url = reverse_lazy('contactos:cbv-lista')<form method="post">{% csrf_token %}
¿Borrar {{ object }}? <button>Sí</button>
</form>GET borrar -> 200
POST borrar -> 302 /contactos/cbv/lista/
sigue? -> FalseDos peticiones para borrar, y no es burocracia: el GET enseña la confirmación y solo el POST borra. La lección 2 ya contó por qué, con la historia del buscador que vació la base de datos siguiendo enlaces. Un borrado detrás de un GET lo ejecuta cualquier cosa que visite la dirección.
De ahí que la plantilla se llame modelo_confirm_delete.html: la vista da por supuesto que hay una confirmación.