单个Django ModelForm中有多个模型?


96

ModelFormDjango 是否可以在一个模型中包含多个模型?我正在尝试创建个人资料编辑表单。因此,我需要包括User模型 UserProfile模型中的某些字段。目前我正在使用2种形式

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")

有没有一种方法可以将这些合并为一个表单,或者我是否只需要创建一个表单并处理数据库加载并保存自己?


Answers:


92

您可以只在一个<form>html元素内的模板中显示两种形式。然后,只需在视图中分别处理表单即可。您仍然可以使用数据库,form.save()而不必自己进行数据库加载和保存。

在这种情况下,您不需要它,但是如果您要使用具有相同字段名的表单,请查看prefixdjango表单的kwarg。(我在这里回答了一个问题)。


这是一个很好的建议,但是在某些情况下这是不适用的。表单集的定制模型表单。
Wtower

8
使基于类的视图能够显示多种形式和模板,然后将它们组合为同一<form>元素的简单方法是什么?
jozxyqk 2015年

1
但是如何?通常,FormView只有一个form_class分配给它。
erikbwork

@erikbwork在这种情况下,您不应使用FormView。只需Subclass即可TemplateView实现与FormView相同的逻辑,但是具有多种形式。
moppag

10

您可以尝试使用以下代码:

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

用法示例:

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())

由于某些显式检查admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
似乎

我该如何搭配使用UpdateView
Pavel Shlepnev

3

我和erikbwork都有一个问题,即一个模型只能包含在一个通用的基于类的视图中。我找到了类似苗的类似方法,但是更加模块化。

我写了一个Mixin,因此您可以使用所有通用的基于类的视图。定义模型,字段,现在还定义child_model和child_field-然后可以将两个模型的字段包装在标签中,如Zach描述。

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())

用法示例:

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

或使用ModelFormClass:

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

做完了 希望能对某人有所帮助。


在这save_child_form.course_key = self.object.course_key什么?
亚当·斯塔吉尔

我认为Course_key是相关的模型,在我的情况下,它是作为backref的UserProfile.user中的“ user”,如果该字段名称是可重复使用的mixin,则该字段名称应该是可自定义的。但是,我仍然遇到另一个问题,即子表单实际上没有填充初始数据,User的所有字段都已预先填充,但UserProfile却没有。我可能必须先修复它。
robvdl

为什么不填充子表单的问题是因为它在get_child_form方法中调用,return self.child_form_class(**self.get_form_kwargs())但是在中获得了错误的模型实例kwargs['instance'],例如instance是主要模型,而不是子模型。要解决此问题,您需要先将kwargs保存到变量中,kwargs = self.get_form_kwargs()然后kwargs['initial']在调用之前使用正确的模型实例进行更新return self.child_form_class(**kwargs)。就我而言,这kwargs['instance'] = kwargs['instance'].profile是否有意义。
robvdl

不幸的是,保存时它仍然会在两个地方崩溃,一个地方在form_valid中尚不存在self.object的地方,因此它抛出AttributeError,而另一个地方实例不存在。我不确定此解决方案在发布之前是否经过了全面测试,因此最好使用CombinedFormBase与其他答案一起使用。
robvdl

1
什么model_forms
奶奶Aching

2

您可能应该看一下Inline表单集。当模型通过外键关联时,将使用内联表单集。


1
内联表单集用于需要处理一对多关系的情况。例如您在其中添加员工的公司。我正在尝试将2个表合并为一种形式。这是一对一的关系。
杰森·韦伯

可以使用内联表单集,但是可能不太理想。您还可以创建一个为您处理关系的模型,然后使用单个表单。仅使用stackoverflow.com/questions/2770810/…中建议的具有2种形式的一页即可。
John Percival Hackworth 2010年

2

您可以在此处检查我的答案是否存在类似问题。

它讨论了如何将注册和用户配置文件合并为一种形式,但是可以将其推广到任何ModelForm组合。


0

我在项目中使用了django BetterformsMultiForm和MultiModelForm。但是,可以改进代码。例如,它依赖于django.six,而3. +不支持,但所有这些都可以轻松修复。

这个问题在StackOverflow中已经出现 几次了,所以我认为是时候找到一种标准的方法来解决这个问题了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.