Django:使用形式在一个模板中的多个模型

问题:Django:使用形式在一个模板中的多个模型

我正在构建一个支持票证跟踪应用程序,并希望从一个页面创建一些模型。票证通过ForeignKey属于客户。注释也通过ForeignKey属于票证。我想选择一个客户(这是一个单独的项目)或创建一个新客户,然后创建一个工单,最后创建一个分配给新工单的便笺。

由于我是Django的新手,因此我倾向于反复工作,每次尝试新功能。我玩过ModelForms,但是我想隐藏一些字段并进行一些复杂的验证。似乎我正在寻找的控制级别需要表单集或手动完成所有操作,并完成一个繁琐的手工编码模板页面,而我试图避免这种情况。

我缺少一些可爱的功能吗?有人对使用表单集有很好的参考或示例吗?我花了整个周末为他们准备API文档,但我仍然一无所知。如果我分解并手工编码所有内容,这是设计问题吗?

I’m building a support ticket tracking app and have a few models I’d like to create from one page. Tickets belong to a Customer via a ForeignKey. Notes belong to Tickets via a ForeignKey as well. I’d like to have the option of selecting a Customer (that’s a whole separate project) OR creating a new Customer, then creating a Ticket and finally creating a Note assigned to the new ticket.

Since I’m fairly new to Django, I tend to work iteratively, trying out new features each time. I’ve played with ModelForms but I want to hide some of the fields and do some complex validation. It seems like the level of control I’m looking for either requires formsets or doing everything by hand, complete with a tedious, hand-coded template page, which I’m trying to avoid.

Is there some lovely feature I’m missing? Does someone have a good reference or example for using formsets? I spent a whole weekend on the API docs for them and I’m still clueless. Is it a design issue if I break down and hand-code everything?


回答 0

使用ModelForms实在不是太难。假设您有A,B和C表单。您打印出每个表单和页面,现在需要处理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()

是用于自定义验证的文档。

This really isn’t too hard to implement with ModelForms. So lets say you have Forms A, B, and C. You print out each of the forms and the page and now you need to handle the 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()

Here are the docs for custom validation.


回答 1

我一天前也处于相同的情况,这是我的2美分:

1)我可以在这里找到单一形式的多个模型输入的最短,最简洁的演示:http : //collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/

简而言之:为每个模型创建一个表单<form>,使用prefixkeyarg 将它们都提交到模板中,并进行视图句柄验证。如果存在依赖关系,只需确保在依赖关系之前保存“父”模型,并在提交“子”模型的保存之前使用父代的ID作为外键。链接中有演示。

2)也许表单集可打成了这样做,但据我的钻研,表单集主要用于输入相同的模型,它的倍数可能 /模型由外键可以任意捆绑到另一种模式。但是,似乎没有默认选项可输入多个模型的数据,而这并不是表单集的含义。

I just was in about the same situation a day ago, and here are my 2 cents:

1) I found arguably the shortest and most concise demonstration of multiple model entry in single form here: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ .

In a nutshell: Make a form for each model, submit them both to template in a single <form>, using prefix keyarg and have the view handle validation. If there is dependency, just make sure you save the “parent” model before dependant, and use parent’s ID for foreign key before commiting save of “child” model. The link has the demo.

2) Maybe formsets can be beaten into doing this, but as far as I delved in, formsets are primarily for entering multiples of the same model, which may be optionally tied to another model/models by foreign keys. However, there seem to be no default option for entering more than one model’s data and that’s not what formset seems to be meant for.


回答 2

我最近遇到了一些问题,只是想出了解决方法。假设您有三个类,Primary,B,C,并且B,C具有Primary的外键

    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,
      })

此方法应允许您执行所需的任何验证,以及在同一页面上生成所有三个对象。我还使用了JavaScript和隐藏字段来允许在同一页面上生成多个B,C对象。

I very recently had the some problem and just figured out how to do this. Assuming you have three classes, Primary, B, C and that B,C have a foreign key to primary

    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,
      })

This method should allow you to do whatever validation you require, as well as generating all three objects on the same page. I have also used javascript and hidden fields to allow the generation of multiple B,C objects on the same page.


回答 3

来自的MultiModelFormdjango-betterforms是一个方便的包装程序,可以执行Gnudiff的答案中所述。它将regulars包装ModelForm在单个类中,该类透明(至少对于基本用法而言)用作单个形式。我从下面的文档中复制了一个示例。

# 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

The MultiModelForm from django-betterforms is a convenient wrapper to do what is described in Gnudiff’s answer. It wraps regular ModelForms in a single class which is transparently (at least for basic usage) used as a single form. I’ve copied an example from their docs below.

# 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

回答 4

我目前有一个解决方法功能(它通过了我的单元测试)。当您只想从其他模型中添加有限数量的字段时,这是一个很好的解决方案。

我在这里想念什么吗?

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

I currently have a workaround functional (it passes my unit tests). It is a good solution to my opinion when you only want to add a limited number of fields from other models.

Am I missing something here ?

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

回答 5

“我想隐藏一些字段并进行一些复杂的验证。”

我从内置的管理界面开始。

  1. 构建ModelForm以显示所需的字段。

  2. 用表单中的验证规则扩展表单。通常这是一种clean方法。

    确保这部分工作正常。

完成此操作后,您可以离开内置的管理界面。

然后,您可以在单个网页上四处查找与部分相关的表单。这是一堆模板材料,可在一个页面上显示所有表单。

然后,您必须编写视图函数来读取和验证各种表单内容,并执行各种对象saves()。

“如果我分解并手动编码所有内容,这是设计问题吗?” 不,只是很多时间而没有太多好处。

“I want to hide some of the fields and do some complex validation.”

I start with the built-in admin interface.

  1. Build the ModelForm to show the desired fields.

  2. Extend the Form with the validation rules within the form. Usually this is a clean method.

    Be sure this part works reasonably well.

Once this is done, you can move away from the built-in admin interface.

Then you can fool around with multiple, partially related forms on a single web page. This is a bunch of template stuff to present all the forms on a single page.

Then you have to write the view function to read and validated the various form things and do the various object saves().

“Is it a design issue if I break down and hand-code everything?” No, it’s just a lot of time for not much benefit.


回答 6

根据Django文档,内联表单集用于此目的:“内联表单集是模型表单集之上的一个小抽象层。它们简化了通过外键处理相关对象的情况”。

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

According to Django documentation, inline formsets are for this purpose: “Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key”.

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