Django: Crea un formulario con varios modelos usando django-betterforms

A menudo cuando desarrollamos aplicaciones web nos encontramos con varias situaciones; una de ellas es guardar datos en un formulario que están en diferentes modelos.

Django: Crea un formulario con varios modelos usando django-betterforms

¡Claro! Es un poco complicado tener un formulario con diferentes modelos y más cuando lo hacemos con la clase FormView. ¡No se preocupen! Les mostraré como hacerlo más fácil usando un paquete de python para django llamado django-betterforms.

Tabla de contenido

  1. Configurando el proyecto
  2. Creamos la aplicacion catálogos
  3. Creando los formularios
  4. Implementando los formularios con CreateView

Configurando el proyecto

Primero instalamos el paquete django-betterforms en nuestro entorno virtual y creamos el proyecto. Lo llamaré demobetterform.

    $ (entornovirtual) pip install django-betterforms
    $ (entornovirtual) django-admin.py startproject demobetterform

Si quieres saber cómo configurar el proyecto y poder seguir ésta guía te recomiendo éste post. Recuerda hacer las migraciones al crear el proyecto y al modificar los modelos.


Creamos la aplicacion catálogos

Una vez creado el proyecto creamos una app. La llamaremos catálogos. Usamos el django-admin de ésta forma:

    $ (entornovirtual) django-admin.py startapp catalogos

Entramos a la carpeta catalogos y abrimos nuestro archivo models.py y agregamos dos modelos de modo que uno será maestro y otro detalle:

class Persona(models.Model):
    nombre = models.CharField(max_length=50)
    apellido = models.CharField(max_length=50)


class Empleado(models.Model):
    CATEGORIAS = (
        (0, 'Secretario'),
        (1, 'Tesorero'),
        (2, 'Jefe')
    )
    categoria = models.CharField(choices=CATEGORIAS, max_length=2)
    salario = models.DecimalField(max_digits=8, decimal_places=2)
    persona = models.ForeignKey(Persona, on_delete=models.CASCADE)

Hacemos la migración para tener los nuevos cambios reflejados en la base de datos.

(entornovirtual)> python manage.py makemigrations catalogos
Migrations for 'catalogos':
  apps\catalogos\migrations\0001_initial.py
    - Create model Empleado
    - Create model Persona
    - Add field persona to empleado

(entornovirtual) python manage.py migrate catalogos 0001
Operations to perform:
  Target specific migration: 0001_initial, from catalogos
Running migrations:
  Applying catalogos.0001_initial... OK

Creando los formularios

Dentro del directorio catálogos crea un archivo llamado forms.py y escribe lo siguiente:

from django.forms import ModelForm

from betterforms.multiform import MultiModelForm

from .models import Persona, Empleado


class PersonaModelForm(ModelForm):
    class Meta:
        model = Persona
        fields = '__all__'


class EmpleadoModelForm(ModelForm):
    class Meta:
        model = Empleado
        fields = ['categoria', 'salario'] # No agregar el campo 'persona'


class EmpleadoPersonaModelForm(MultiModelForm):
    form_classes = {
        'persona': PersonaModelForm,
        'empleado': EmpleadoModelForm,
    }

Lo que hicimos ahí fue crear dos formularios bases, el de persona y el del empleado. La última clase es donde unimos los dos formularios para hacerlo en uno solo y lo usemos en una vista CreateView.

También aquí hay otro truco. Es necesario no incluir el campo persona del modelo Empleado en el formulario de EmpleadoModelForm porque la persona la crearemos en el mismo formulario al momento de guardarlo.


Implementando los formularios con CreateView

Ahora si viene la implementación de nuestro formulario EmpleadoPersonaModelForm en nuestro archivo views.py.

from django.urls import reverse
from django.http import HttpResponseRedirect
from django.views.generic import CreateView, TemplateView

from .forms import EmpleadoPersonaModelForm


class EmpleadoCreateView(CreateView):
    form_class = EmpleadoPersonaModelForm
    template_name = 'formulario.html'

    def form_valid(self, form):
        persona = form['persona'].save()
        empleado = form['empleado'].save(commit=False)
        empleado.persona = persona
        empleado.save()
        return HttpResponseRedirect(reverse('success'))


class SuccessView(TemplateView):
    template_name = 'success.html'

Vamos muy bien. Con ésta línea persona = form['persona'].save() nos cercioramos de que primero guarde la persona, empleado = form['empleado'].save(commit=False) crea la instancia del empleado aún sin guardarla en la base de datos y la que sigue que es empleado.persona = persona asigna la persona a la instancia de empleado en su campo persona y posteriormente la guarda con save.

Ahora nos falta crear los archivos html. Agrega dos archivos que se llame formulario.html y el otro success.html y le pegas lo siguiente.

formulario.html

<h1>Crear</h1>

<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Guardar">
</form>

success.html

<h1>El empleado ha sido creado!</h1>

Y con eso ya debe de funcionar éste proyecto. Ahora les regalaré unas capturas de que todo esta a la perfección.

Formulario para crear un empleado

Mensaje que todo ha sido creado correctamente

En el siguiente post haremos la actualización con varios modelos con Django y publicaremos el código fuente.

Saludos y bendiciones!