¿Cómo puedo guardar la entrada de 3 FormModels desde una vista?

Hola Stackoverflow, esta es mi primera publicación aquí. Acabo de comenzar a aprender a codificar en Python / Django, por lo que mi código puede ser incorrecto, lo siento.

Después de tres días de buscar y probar mucho código, espero que alguien con conocimientos :)

Lo que estoy tratando de hacer:

Un gran formulario: el usuario completa los campos personales, los campos de los expedientes y la información sobre una segunda persona. (parte no se guarda correctamente en este momento)

Views.py una vista para 2 ModelForms y reutilizando una nuevamente para c_person. Models.py 2 modelos (un dosier y un modelo de datos de una persona). Forms.py 2 ModelForms (uno de dossier y otro de persona, reutilizando el formulario de persona dentro de la vista).

Los datos dossier.html para dar una idea del formulario (se eliminó una gran cantidad de código).

                                    <div class="column is-one-fifth">
                                    <label class="label is-small">Firstname</label>
                                    {{ person_form.first_name }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Preposition</label>
                                    {{ person_form.preposition }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Lastname</label>
                                    {{ person_form.last_name }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Address</label>
                                    {{ person_form.address }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Firstname</label>
                                    {{ cperson_form.first_name }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Lastname</label>
                                    {{ cperson_form.last_name }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Number</label>
                                    {{ cperson_form.number }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Status</label>
                                    {{ dossier_form.status }}
                                </div>

                                <div class="column is-one-fifth">
                                    <label class="label is-small">Operator</label>
                                    <div class=select>{{ dossier_form.operator }}</div>
                                </div>

models.py

class Person(models.Model):

number = models.IntegerField(unique=True, null=True, blank=True)
first_name = models.CharField(max_length=35, null=True, blank=True)
last_name = models.CharField(max_length=35, null=True, blank=True)
initials = models.CharField(max_length=10, null=True, blank=True)
preposition = models.CharField(max_length=10, null=True, blank=True)
address = models.CharField(max_length=80, null=True, blank=True)
address_number = models.CharField(max_length=5, null=True, blank=True)
email = models.EmailField(null=True, blank=True)
phone_number = models.IntegerField(null=True, blank=True)

@property
def full_name(self):
    "Returns the person's full name"
    full_name = (self.last_name + ', ' + self.first_name)

    # Need to check this, django return 'None' if no preposition is used
    if self.preposition:
        full_name = full_name + ' ' + self.preposition

    return full_name

def __str__(self):
    return self.first_name + ' ' + self.last_name

class Dossier(models.Model):
# Core
status = models.ForeignKey('DossierStatus', null=True, on_delete=models.SET_NULL)
operator = models.ForeignKey('User', null=True, on_delete=models.SET_NULL)
d_person = models.ForeignKey('Person', related_name='d_person', null=True, on_delete=models.SET_NULL, blank=True)
c_person = models.ForeignKey(Person, related_name='c_person', null=True, on_delete=models.SET_NULL, blank=True)


def __int__(self):
    return self.id

def get_absolute_url(self):
    return reverse('dossier_edit', kwargs={'pk': self.pk})

Forms.py

class PersForm(forms.ModelForm):
class Meta:
    model = Person
    fields = ['number', 'first_name', 'preposition', 'last_name', 'initials', 'address', 'address_number', 'email', 'phone_number']

class DossierForm(forms.ModelForm):
class Meta:
    model = Dossier
    fields = '__all__'

Views.py

def dossier(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
    # create a form instance and populate it with data from the request:
    dossier_form = DossierForm(request.POST, request.FILES)
    dossier_form_valid = dossier_form.is_valid()
    person_form = PersForm(request.POST)
    person_form_valid = person_form.is_valid()
    cperson_form = PersForm(request.POST)
    cperson_form_valid = cperson_form.is_valid()
    # check whether it's valid:
    if dossier_form_valid and person_form_valid and cperson_form_valid:
        print("succes")
        # process the data 
        person_save = person_form.save()
        dossier_save = dossier_form.save(commit=False)
        cperson_save = cperson_form.save(commit=False)

        dossier_save.person_save = person_save
        cperson_save.dossier_save = dossier_save
        cperson_save.save()

        # redirect to a new URL:
        return HttpResponseRedirect('/data/index/')
    else:
            print("failure")
            print(dossier_form.errors)
            print(person_form.errors)

# if a GET (or any other method) we'll create a blank form
else:
    dossier_form = DossierForm()
    person_form = PersForm()
    cperson_form = PersForm()

return render(request, 'dossier.html',
              {'title': 'Nieuwe aanvraag', 'dossier_form': dossier_form,
               'person_form': person_form, 'cperson_form': cperson_form})

El problema aquí es que solo se guardan los datos del formulario de una persona en la base de datos. Probé muchas variaciones diferentes en este código:

        person_save = person_form.save()
        dossier_save = dossier_form.save(commit=False)
        cperson_save = cperson_form.save(commit=False)

        dossier_save.person_save = person_save
        cperson_save.dossier_save = dossier_save
        cperson_save.save()
1
Robin T 22 feb. 2018 a las 11:34

2 respuestas

La mejor respuesta

Al pasar el argumento commit al método save() en un formulario de modelo, guardará su modelo en la base de datos. Veo que el término commit aquí es un poco engañoso, porque suena como confirmar la transacción de la base de datos , pero simplemente define si se llamará o no al método save() del modelo. .

Lo que quiere hacer es envolver todo este código en una sola transacción de base de datos, usando django.models.db.transaction.

Primero impórtelo en su vista:

from django.db.models import transaction

Entonces puedes usarlo así:

if dossier_form_valid and person_form_valid and cperson_form_valid:
    print("succes")
    # process the data 

    with transaction.atomic():
        d_person = person_form.save()
        c_person = cperson_form.save()

        dossier = dossier_form.save(commit=False)
        dossier.d_person = d_person
        dossier.c_person = c_person
        dossier.save()

El método model form save() devuelve una instancia del modelo Django que representa. Así que lo renombró solo a dossier en lugar de dossier_save, etc.

En dossier_form puede usar commit=False porque tiene más datos para asociar con dossier, de esa manera evita llamar al método save() varias veces. Pero para que el objeto persista en la base de datos, después de agregar los datos adicionales, debe llamar al método de guardar como dossier.save().

Todo dentro del bloque transaction.atomic() se ejecutará en una sola transacción de la base de datos, si ocurre un error, Django se revertirá automáticamente.

1
Vitor Freitas 22 feb. 2018 a las 08:54

La respuesta de Vitor es correcta, sin embargo, la importación ha cambiado de:

from django.db.models import transaction

Ha cambiado a:

from django.db import transaction
2
Sheldon Lipshitz 3 oct. 2018 a las 17:03