Vários modelos em um único django ModelForm?

96

É possível ter vários modelos incluídos em um único ModelFormno django? Estou tentando criar um formulário de edição de perfil. Portanto, preciso incluir alguns campos do modelo de usuário e do modelo de UserProfile. Atualmente estou usando 2 formulários como este

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

Existe uma maneira de consolidar isso em um formulário ou eu só preciso criar um formulário e lidar com o carregamento do banco de dados e salvando-me?

Jason Webb
fonte
Possível duplicata do Django: vários modelos em um modelo usando formulários
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

92

Você pode apenas mostrar os dois formulários no modelo dentro de um <form>elemento html. Em seguida, basta processar os formulários separadamente na visualização. Você ainda poderá usar form.save()e não terá que processar o carregamento e salvamento do banco de dados.

Nesse caso, você não deve precisar, mas se for usar formulários com os mesmos nomes de campo, procure no prefixkwarg por formulários django. (Eu respondi uma pergunta sobre isso aqui ).

Zach
fonte
Este é um bom conselho, mas há casos em que isso não é aplicável, por exemplo. formulário de modelo personalizado para um formset.
Torre de Vigia de
8
Qual seria uma maneira direta de tornar uma visão baseada em classe capaz de mostrar mais de um formulário e um modelo que os combina no mesmo <form>elemento?
jozxyqk
1
Mas como? Normalmente, um FormViewtem apenas um form_classatribuído a ele.
erikbwork
@erikbwork Você não deve usar um FormView para este caso. Apenas subclasse TemplateViewe implemente a mesma lógica do FormView, mas com vários formulários.
moppag
10

Você pode tentar usar estes pedaços de código:

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

Exemplo de uso:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())
Miao ZhiCheng
fonte
Parece que isso não pode ser usado no administrador devido a algumas verificações explícitas:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo
Como posso usar com UpdateView?
Pavel Shlepnev
3

erikbwork e eu tínhamos o problema de que só se pode incluir um modelo em uma Visão baseada em classe genérica. Eu encontrei uma maneira semelhante de abordar como Miao, mas mais modular.

Eu escrevi um Mixin para que você possa usar todas as visualizações genéricas baseadas em classes. Defina modelo, campos e agora também child_model e child_field - e então você pode envolver os campos de ambos os modelos em uma tag como Zach descreve.

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

Exemplo de uso:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

Ou com ModelFormClass:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

Feito. Espero que ajude alguém.

LGG
fonte
Nisto save_child_form.course_key = self.object, o que é .course_key?
Adam Starrh
Acho que course_key é o modelo relacionado, no meu caso que é "user" como em UserProfile.user que é um backref, talvez esse nome de campo deva ser customizável se fosse para ser um mixin reutilizável. Mas ainda estou tendo outro problema em que o formulário filho não é realmente preenchido com os dados iniciais, todos os campos de User são pré-preenchidos, mas não para UserProfile. Talvez eu tenha que consertar isso primeiro.
robvdl
O problema por que o formulário filho não é preenchido é porque no método get_child_form, ele chama, return self.child_form_class(**self.get_form_kwargs())mas obtém a instância de modelo errada kwargs['instance'], por exemplo, instância é o modelo principal e não o modelo filho. Para corrigir, você precisa primeiro salvar kwargs em uma variável e kwargs = self.get_form_kwargs()depois atualizar kwargs['initial']com a instância de modelo correta antes de chamar return self.child_form_class(**kwargs). No meu caso, era kwargs['instance'] = kwargs['instance'].profilese isso fizesse sentido.
robvdl
Infelizmente, ao salvar, ele ainda travará em dois lugares, um onde self.object ainda não está lá em form_valid, então ele lança um AttributeError e outra instância de lugar não está lá. Não tenho certeza se esta solução foi totalmente testada antes de ser postada, então pode ser melhor ir com a outra resposta usando CombinedFormBase.
robvdl
1
O que é model_forms?
Granny Aching de
2

Você provavelmente deve dar uma olhada em conjuntos de formulários embutidos . Formulários embutidos são usados ​​quando seus modelos são relacionados por uma chave estrangeira.

John Percival Hackworth
fonte
1
Os conjuntos de formulários embutidos são usados ​​quando você precisa trabalhar com um relacionamento um para muitos. Como uma empresa onde você adiciona funcionários. Estou tentando combinar 2 tabelas em um único formulário. É um relacionamento um para um.
Jason Webb
O uso de um formset Inline funcionaria, mas provavelmente seria menos do que ideal. Você também pode criar um modelo que lida com a relação para você e, em seguida, usar um único formulário. Apenas ter uma única página com 2 formulários, conforme sugerido em stackoverflow.com/questions/2770810/… , funcionaria.
John Percival Hackworth
2

Você pode verificar minha resposta aqui para um problema semelhante.

Ele fala sobre como combinar registro e perfil de usuário em um único formulário, mas pode ser generalizado para qualquer combinação de ModelForm.

Mitar
fonte
0

Eu usei Django betterforms 's multiforme e MultiModelForm no meu projeto. O código pode ser melhorado, no entanto. Por exemplo, ele depende de django.six, que não é compatível com o 3. +, mas tudo isso pode ser facilmente corrigido

Esta questão apareceu várias vezes no StackOverflow, então acho que é hora de encontrar uma maneira padronizada de lidar com isso.

J Eti
fonte