Trabajando con formularios anidados con Django
Una de las mayores ventajas de Django es su capacidad para crear aplicaciones CRUDs sin esfuerzo. Solo debe definir sus modelos, registrarlos en el admin y eso es todo. Si usted no va a seguir el enfoque del admin, puede usar las class based views de Django para definir cada una de las vistas que necesitaría para administrar los objetos en su base de datos. Para crear y actualizar instancias de modelo, ni siquiera necesita definir un formulario porque CreateView y UpdateView lo harán por usted. En solo un par de horas, puede tener una aplicación CRUD muy estable sin casi ningún esfuerzo.
Sin embargo, esta genialidad parece desaparecer cuando comenzamos a tener relaciones entre nuestros modelos; como foreign keys o relaciones uno a uno. Siguiendo el enfoque anterior, es difícil editar el objeto padre mientras se edita un hijo, o editar varios hijos a la vez.
Django admin resuelve esta situación usando admin inlines. Pero, ¿qué podemos hacer cuando nuestra aplicación use sus propias vistas para administrar nuestro CRUD? Bueno, usamos Formsets. En esta publicación, revisaremos cómo usarlos, junto con nuestras vistas basadas en la clase, para lograr un CRUD increíble con varias relaciones y con una pequeña cantidad de código. Entonces, ¡comencemos!
Configurando nuestros modelos
Para esta publicación, usaré dos modelos pequeños. Padre e hijo. Como su nombre lo indica, un padre puede tener varios hijos, pero un hijo solo puede tener un padre.
from django.db import models class Parent(models.Model): name = models.CharField(max_length=250) class Meta: verbose_name = "Parent" verbose_name_plural = "Parents" def __str__(self): return self.name class Child(models.Model): name = models.CharField(max_length=250) parent = models.ForeignKey(Parent, on_delete=models.CASCADE) class Meta: verbose_name = "Child" verbose_name_plural = "Children" def __str__(self): return self.name
Ahora, agreguemos la vista de creación para el padre, donde también queremos crear los hijos relacionados.
from django.views.generic.list import ListView from django.views.generic.edit import CreateView, UpdateView from .models import Parent class ParentListView(ListView): model = Parent class ParentCreateView(CreateView): model = Parent fields = ["name"]
Si lo dejamos así, no podremos agregar hijos para un padre. Pero si agregamos child_set a la matriz de campos, se generará un «FieldError: Unknown field(s) (child_set) especificado para Parent». ¿Entonces, qué debemos hacer?
Configurando los formularios y las vistas
Para crear los formularios en línea, utilizaremos los inline form sets de Django. Este tipo de formsets le permite crear varios objetos secundarios a partir de un objeto primario, todos en el mismo formulario. Estos son la base de lo que el administrador de Django usa cuando registra inlines instances.
Para nuestro ejemplo, vamos a ir más allá y ser aún más abstractos. Usaremos el método inlineformset_factory que, en solo una línea, crea el formset que necesitamos.
from django.forms.models import inlineformset_factory ChildFormset = inlineformset_factory( Parent, Child, fields=('name',) )
Ahora, actualizaremos nuestro createView para agregar formsets en línea.
class ParentCreateView(CreateView): model = Parent fields = ["name"] def get_context_data(self, **kwargs): # we need to overwrite get_context_data # to make sure that our formset is rendered data = super().get_context_data(**kwargs) if self.request.POST: data["children"] = ChildFormset(self.request.POST) else: data["children"] = ChildFormset() return data def form_valid(self, form): context = self.get_context_data() children = context["children"] self.object = form.save() if children.is_valid(): children.instance = self.object children.save() return super().form_valid(form) def get_success_url(self): return reverse("parents:list")
Como puede ver, agregamos el formset a nuestro contexto para poder representarlo. Además, en el método form_valid, después de guardar nuestro objeto padre, lo asignamos como la instancia de los formsets, verificamos su validez y lo guardamos; creando los objetos hijos también.
Para representar el formulario, podemos crear un archivo parents/parent_form.html:
<h1>Parents</h1> <form method="post">{% csrf_token %} {{ form.as_p }} <h2>children</h2> {{ children.as_p }} <input type="submit" value="Save"> </form>
Esta plantilla se verá así:
Como puede ver, el formulario no solo le permite agregar el padre, sino también crear tres hijos.
Para la vista de actualización, podemos seguir el mismo enfoque. La única diferencia es que iniciamos el objeto ChildFormSet con el argumento instance. De esa manera, nuestro formset se inicializará con los hijos del padre que estamos editando.
class ParentUpdateView(UpdateView): model = Parent fields = ["name"] def get_context_data(self, **kwargs): # we need to overwrite get_context_data # to make sure that our formset is rendered. # the difference with CreateView is that # on this view we pass instance argument # to the formset because we already have # the instance created data = super().get_context_data(**kwargs) if self.request.POST: data["children"] = ChildFormset(self.request.POST, instance=self.object) else: data["children"] = ChildFormset(instance=self.object) return data def form_valid(self, form): context = self.get_context_data() children = context["children"] self.object = form.save() if children.is_valid(): children.instance = self.object children.save() return super().form_valid(form) def get_success_url(self): return reverse("parents:list")
¡Y eso es todo! Al usar solo algunos métodos y objetos de Django, pudimos crear un formulario anidado para crear o editar un objeto principal y sus elementos secundarios al mismo tiempo sin casi ningún esfuerzo.
He estado trabajando con Django durante casi cuatro años y todavía logra sorprenderme con su simplicidad y todas las herramientas útiles que proporciona. Para este caso, permite realizar una tarea muy compleja, que en otros casos requeriría una solicitud de AJAX o algo similar, solo usando python y html puro.
Y este es solo un caso simple. Si necesita otro nivel de relación, por ejemplo, si su modelo Hijo tiene un Nieto, puede crear un formset que le permita editar un Padre, un Hijo y un Nieto sin utilizar un enfoque diferente. Puede ver un excelente ejemplo aquí.
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia
Hola He leído tu Articulo y es de lo mejor que he podido encontrar.
En el proyecto de fin de curso que estoy trabajando, esto no me funciona del todo.
En el CreateView logro renderizar los campos del modelo con foreignKey(Child), este a su vez tiene un campo asociado de timo ImageField. Pero no logo grabar las imágenes seleccionadas en la tabla.
En el UpdateView, no logro hacer que me muestre las imágnes(childs) asociadas a (Parent).
Igualmente quiero agradecer por este Post y me encantaría puedas darme alguna sugerencia