Django: múltiples modelos en una plantilla usando formularios [cerrado]

114

Estoy creando una aplicación de seguimiento de tickets de soporte y tengo algunos modelos que me gustaría crear desde una página. Los boletos pertenecen a un Cliente a través de ForeignKey. Las notas también pertenecen a Tickets a través de ForeignKey. Me gustaría tener la opción de seleccionar un Cliente (que es un proyecto completamente separado) O crear un nuevo Cliente, luego crear un Ticket y finalmente crear una Nota asignada al nuevo ticket.

Como soy bastante nuevo en Django, tiendo a trabajar de forma iterativa, probando nuevas funciones cada vez. He jugado con ModelForms pero quiero ocultar algunos de los campos y hacer una validación compleja. Parece que el nivel de control que estoy buscando requiere conjuntos de formularios o hacer todo a mano, con una tediosa página de plantilla codificada a mano, que estoy tratando de evitar.

¿Hay alguna característica encantadora que me falta? ¿Alguien tiene una buena referencia o ejemplo para usar conjuntos de formularios? Pasé todo un fin de semana en los documentos de la API para ellos y todavía no tengo ni idea. ¿Es un problema de diseño si descompongo y codifico todo a mano?

neoice
fuente
primero debe validar su formulario de cliente y, si era válido, crear una copia de request.POST (new_data = request.POST.copy ()). y luego obtener la identificación de cliente (del formulario de cliente validado) y con la actualización de new_data, haga ID de cliente un valor para el campo de clave extranjera (tal vez cliente en su modelo). Y finalmente considere new_data para validar su segundo formulario (Tickets)
Negar37

Respuestas:

87

Esto realmente no es demasiado difícil de implementar con ModelForms . Entonces, digamos que tiene los Formularios A, B y C. Imprime cada uno de los formularios y la página y ahora necesita manejar el POST.

if request.POST():
    a_valid = formA.is_valid()
    b_valid = formB.is_valid()
    c_valid = formC.is_valid()
    # we do this since 'and' short circuits and we want to check to whole page for form errors
    if a_valid and b_valid and c_valid:
        a = formA.save()
        b = formB.save(commit=False)
        c = formC.save(commit=False)
        b.foreignkeytoA = a
        b.save()
        c.foreignkeytoB = b
        c.save()

Aquí están los documentos para la validación personalizada.

Jason Christa
fuente
2
Por cierto, no creo que los conjuntos de formularios sean una buena solución al problema que describiste. Siempre los usé para representar múltiples instancias de un modelo. Por ejemplo, tiene un formulario de solicitante y desea 3 referencias para crear un conjunto de formularios que tenga 3 instancias del modelo de referencia.
Jason Christa
1
tenga en cuenta que, con la forma en que lo hace, la llamada .is_valid () no está cortocircuitada. Si desea cortocircuitarlo, deberá retrasar la llamada a la función .is_valid () hasta que aparezca 'y'.
Lie Ryan
66

Hace un día estuve en la misma situación, y aquí están mis dos centavos:

1) Encontré posiblemente la demostración más corta y concisa de la entrada de modelos múltiples en una sola forma aquí: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ .

En pocas palabras: crea un formulario para cada modelo, envía ambos a la plantilla en una sola <form>, usando prefixkeyarg y haz que la vista maneje la validación. Si hay dependencia, asegúrese de guardar el modelo "padre" antes que el dependiente y utilice la ID del padre para la clave externa antes de guardar el modelo "hijo". El enlace tiene la demostración.

2) Tal vez los conjuntos de formularios puedan ser golpeados para hacer esto, pero por lo que profundicé, los conjuntos de formularios son principalmente para ingresar múltiplos del mismo modelo, que pueden estar opcionalmente vinculados a otro modelo / modelos mediante claves externas. Sin embargo, parece que no hay una opción predeterminada para ingresar más de los datos de un modelo y eso no es para lo que parece estar destinado el conjunto de formularios.

Gnudiff
fuente
26

Recientemente tuve un problema y descubrí cómo hacer esto. Suponiendo que tiene tres clases, Primaria, B, C y que B, C tienen una clave externa a primaria

    class PrimaryForm(ModelForm):
        class Meta:
            model = Primary

    class BForm(ModelForm):
        class Meta:
            model = B
            exclude = ('primary',)

    class CForm(ModelForm):
         class Meta:
            model = C
            exclude = ('primary',)

    def generateView(request):
        if request.method == 'POST': # If the form has been submitted...
            primary_form = PrimaryForm(request.POST, prefix = "primary")
            b_form = BForm(request.POST, prefix = "b")
            c_form = CForm(request.POST, prefix = "c")
            if primary_form.is_valid() and b_form.is_valid() and c_form.is_valid(): # All validation rules pass
                    print "all validation passed"
                    primary = primary_form.save()
                    b_form.cleaned_data["primary"] = primary
                    b = b_form.save()
                    c_form.cleaned_data["primary"] = primary
                    c = c_form.save()
                    return HttpResponseRedirect("/viewer/%s/" % (primary.name))
            else:
                    print "failed"

        else:
            primary_form = PrimaryForm(prefix = "primary")
            b_form = BForm(prefix = "b")
            c_form = Form(prefix = "c")
     return render_to_response('multi_model.html', {
     'primary_form': primary_form,
     'b_form': b_form,
     'c_form': c_form,
      })

Este método debería permitirle realizar cualquier validación que necesite, así como generar los tres objetos en la misma página. También he usado javascript y campos ocultos para permitir la generación de múltiples objetos B, C en la misma página.


fuente
3
En este ejemplo, ¿cómo está configurando las claves externas para los modelos B y C para que apunten al modelo principal?
Usuario
Solo tengo dos modelos que quiero mostrar en el mismo formulario. Pero no obtengo la declaración exclude = ('primary',). ¿Qué es primario? Si tiene 2 modelos CustomerConfig y Contract. El contrato tiene la clave externa para CustomerConfig. Como customer_config = models.ForeignKey ('CustomerPartnerConfiguration') ¿Qué es 'primario'?
pitchblack408
10

El MultiModelForm de django-betterformses un contenedor conveniente para hacer lo que se describe en la respuesta de Gnudiff . Envuelve los correos electrónicos regulares ModelFormen una sola clase que se utiliza de forma transparente (al menos para uso básico) como una forma única. He copiado un ejemplo de sus documentos a continuación.

# forms.py
from django import forms
from django.contrib.auth import get_user_model
from betterforms.multiform import MultiModelForm
from .models import UserProfile

User = get_user_model()

class UserEditForm(forms.ModelForm):
    class Meta:
        fields = ('email',)

class UserProfileForm(forms.ModelForm):
    class Meta:
        fields = ('favorite_color',)

class UserEditMultiForm(MultiModelForm):
    form_classes = {
        'user': UserEditForm,
        'profile': UserProfileForm,
    }

# views.py
from django.views.generic import UpdateView
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
from django.contrib.auth import get_user_model
from .forms import UserEditMultiForm

User = get_user_model()

class UserSignupView(UpdateView):
    model = User
    form_class = UserEditMultiForm
    success_url = reverse_lazy('home')

    def get_form_kwargs(self):
        kwargs = super(UserSignupView, self).get_form_kwargs()
        kwargs.update(instance={
            'user': self.object,
            'profile': self.object.profile,
        })
        return kwargs
jozxyqk
fuente
Acabo de ver django-betterformsy su clase MultiModelForm antes de encontrar su respuesta. Su solución se ve muy bien, pero parece que no se ha actualizado en un tiempo. ¿Sigues usando este @jozxyqk? ¿Algún problema?
mejora el
@enchance han pasado algunos años. En ese entonces lo encontré conveniente y una de las mejores opciones que existen. Si no se pone demasiado sofisticado, le ahorrará algo de tiempo. Me imagino que cuando quieras empezar a personalizar y hacer formularios no triviales, sería más fácil crear los tuyos. Mezclar fácilmente formas y contextos en las vistas es la primera característica que realmente creo que me perdí en django.
jozxyqk
Gracias por la respuesta, hombre. Estoy considerando simplemente bifurcarlo y tal vez actualizar algunas cosas en el camino. Por lo que he visto hasta ahora, está funcionando bien. Tienes razón, es un gran ahorro de tiempo.
mejora el
5

Actualmente tengo una solución alternativa funcional (pasa mis pruebas unitarias). En mi opinión, es una buena solución cuando solo desea agregar un número limitado de campos de otros modelos.

Me estoy perdiendo de algo ?

class UserProfileForm(ModelForm):
    def __init__(self, instance=None, *args, **kwargs):
        # Add these fields from the user object
        _fields = ('first_name', 'last_name', 'email',)
        # Retrieve initial (current) data from the user object
        _initial = model_to_dict(instance.user, _fields) if instance is not None else {}
        # Pass the initial data to the base
        super(UserProfileForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
        # Retrieve the fields from the user model and update the fields with it
        self.fields.update(fields_for_model(User, _fields))

    class Meta:
        model = UserProfile
        exclude = ('user',)

    def save(self, *args, **kwargs):
        u = self.instance.user
        u.first_name = self.cleaned_data['first_name']
        u.last_name = self.cleaned_data['last_name']
        u.email = self.cleaned_data['email']
        u.save()
        profile = super(UserProfileForm, self).save(*args,**kwargs)
        return profile
Paul Bormans
fuente
3

"Quiero ocultar algunos de los campos y hacer una validación compleja".

Empiezo con la interfaz de administración incorporada.

  1. Construya el ModelForm para mostrar los campos deseados.

  2. Amplíe el formulario con las reglas de validación dentro del formulario. Por lo general, este es un cleanmétodo.

    Asegúrese de que esta parte funcione razonablemente bien.

Una vez hecho esto, puede alejarse de la interfaz de administración incorporada.

Entonces puede jugar con múltiples formularios parcialmente relacionados en una sola página web. Este es un montón de plantillas para presentar todos los formularios en una sola página.

Luego, debe escribir la función de vista para leer y validar las diversas cosas del formulario y hacer los diversos objetos guardados ().

"¿Es un problema de diseño si descompongo y codifico todo a mano?" No, es mucho tiempo sin mucho beneficio.

S.Lott
fuente
No sé cómo, por lo tanto, no lo hagas
orokusaki
1
@orokusaki: ¿Qué más te gustaría? Eso parece describir una solución. ¿Qué más debería decirse? La pregunta es vaga, por lo que es difícil proporcionar un código real. En lugar de quejarse, proporcione una sugerencia de mejora. ¿Que sugieres?
S.Lott
1

De acuerdo con la documentación de Django, los conjuntos de formularios en línea tienen este propósito: "Los conjuntos de formularios en línea son una pequeña capa de abstracción en la parte superior de los conjuntos de formularios del modelo. Esto simplifica el caso de trabajar con objetos relacionados a través de una clave externa".

Ver https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets

usuario1376892
fuente