我有一个模板页期望两个表单。如果我只使用一种形式,就像下面这个典型的例子一样:

if request.method == 'POST':
    form = AuthorForm(request.POST,)
    if form.is_valid():
        form.save()
        # do something.
else:
    form = AuthorForm()

然而,如果我想处理多个表单,我如何让视图知道我只提交了其中一个表单,而不是另一个(即它仍然是请求。POST但我只想处理提交发生的形式)?


这是基于答案的解决方案,其中expectedphrase和bannedphrase是不同表单的提交按钮的名称,expectedphraseform和bannedphraseform是表单。

if request.method == 'POST':
    if 'bannedphrase' in request.POST:
        bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
        if bannedphraseform.is_valid():
            bannedphraseform.save()
        expectedphraseform = ExpectedPhraseForm(prefix='expected')
    elif 'expectedphrase' in request.POST:
        expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
        if expectedphraseform.is_valid():
            expectedphraseform.save() 
        bannedphraseform = BannedPhraseForm(prefix='banned')
else:
    bannedphraseform = BannedPhraseForm(prefix='banned')
    expectedphraseform = ExpectedPhraseForm(prefix='expected')

当前回答

根据@ybendana的回答:

同样,我们使用is_bound来检查表单是否能够进行验证。请参阅文档的这一部分:

绑定和未绑定的表单 Form实例可以绑定到一组数据,也可以不绑定。 如果它绑定到一组数据,它就能够验证该数据并将表单呈现为HTML,并在HTML中显示数据。 如果未绑定,则不能进行验证(因为没有数据要验证!),但仍然可以将空白表单呈现为HTML。

我们为表单对象和它们的详细信息使用元组列表,这允许更多的可扩展性和更少的重复。

但是,我们没有重写get(),而是重写get_context_data(),从而将一个新的空白表单实例(带前缀)插入到响应中作为任何请求的默认操作。在POST请求的上下文中,我们重写POST()方法以:

使用前缀检查每个表单是否已经提交 验证已提交的表单 使用cleaned_data处理有效的表单 通过覆盖上下文数据向响应返回任何无效表单

# views.py

class MultipleForms(TemplateResponseMixin, ContextMixin, View):

    form_list = [ # (context_key, formcls, prefix)
        ("form_a", FormA, "prefix_a"),
        ("form_b", FormB, "prefix_b"),
        ("form_c", FormC, "prefix_c"),
        ...
        ("form_x", FormX, "prefix_x"),
    ]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Add blank forms to context with prefixes
        for context_key, formcls, prefix in self.form_list:
            context[context_key] = formcls(prefix=prefix)
        return context

    def post(self, request, *args, **kwargs):
        # Get object and context
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        # Process forms
        for context_key, formcls, prefix in self.form_list:
            if prefix in request.POST:
                # Get the form object with prefix and pass it the POST data to \
                # validate and clean etc.
                form = formcls(request.POST, prefix=prefix)
                if form.is_bound:
                    # If the form is bound (i.e. it is capable of validation)  \
                    # check the validation
                    if form.is_valid():
                        # call the form's save() method or do whatever you     \
                        # want with form.cleaned_data
                        form.save()
                    else:
                        # overwrite context data for this form so that it is   \
                        # returned to the page with validation errors
                        context[context_key] = form
        # Pass context back to render_to_response() including any invalid forms
        return self.render_to_response(context)
        

这种方法允许在同一页面上重复表单条目,我发现@ybendana的答案并不适用。

我相信,将这个方法折叠到一个Mixin类中,将form_list对象作为一个属性,并像上面那样挂钩get_context_data()和post(),不会有更多的工作。

编辑:这个已经存在了。请参阅此存储库。

注: 这个方法需要TemplateResponseMixin来实现render_to_response(),需要ContextMixin来实现get_context_data()。要么使用这些mixin,要么使用源自它们的CBV。

其他回答

你有几个选择:

在这两个表单的操作中放入不同的url。然后你会有两个不同的视图函数来处理这两个不同的表单。 从POST数据中读取提交按钮值。你可以知道哪个提交按钮被点击了:我如何构建多个提交按钮django form?

一个供将来参考的方法是这样的。禁止的短语形式是第一个,期望的短语形式是第二个。如果第一个被击中,第二个就会被跳过(在这种情况下这是一个合理的假设):

if request.method == 'POST':
    bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
    if bannedphraseform.is_valid():
        bannedphraseform.save()
else:
    bannedphraseform = BannedPhraseForm(prefix='banned')

if request.method == 'POST' and not bannedphraseform.is_valid():
    expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
    bannedphraseform = BannedPhraseForm(prefix='banned')
    if expectedphraseform.is_valid():
        expectedphraseform.save()

else:
    expectedphraseform = ExpectedPhraseForm(prefix='expected')

我需要在同一页面上独立验证多个表单。我忽略的关键概念是:1)使用表单前缀作为提交按钮名称;2)无界表单不会触发验证。如果它对其他人有帮助,这里是我使用TemplateView的两个表单AForm和BForm的简化示例,基于@adam-nelson和@daniel-sokolowski的回答和@zeraien (https://stackoverflow.com/a/17303480/2680349):)的评论

# views.py
def _get_form(request, formcls, prefix):
    data = request.POST if prefix in request.POST else None
    return formcls(data, prefix=prefix)

class MyView(TemplateView):
    template_name = 'mytemplate.html'

    def get(self, request, *args, **kwargs):
        return self.render_to_response({'aform': AForm(prefix='aform_pre'), 'bform': BForm(prefix='bform_pre')})

    def post(self, request, *args, **kwargs):
        aform = _get_form(request, AForm, 'aform_pre')
        bform = _get_form(request, BForm, 'bform_pre')
        if aform.is_bound and aform.is_valid():
            # Process aform and render response
        elif bform.is_bound and bform.is_valid():
            # Process bform and render response
        return self.render_to_response({'aform': aform, 'bform': bform})

# mytemplate.html
<form action="" method="post">
    {% csrf_token %}
    {{ aform.as_p }}
    <input type="submit" name="{{aform.prefix}}" value="Submit" />
    {{ bform.as_p }}
    <input type="submit" name="{{bform.prefix}}" value="Submit" />
</form>

想要分享我的解决方案,Django表单没有被使用。 我在一个页面上有多个表单元素,我想使用一个视图来管理来自所有表单的所有POST请求。

我所做的是引入了一个不可见的输入标记,这样我就可以向视图传递一个参数来检查提交了哪个表单。

<form method="post" id="formOne">
    {% csrf_token %}
   <input type="hidden" name="form_type" value="formOne">

    .....
</form>

.....

<form method="post" id="formTwo">
    {% csrf_token %}
    <input type="hidden" name="form_type" value="formTwo">
   ....
</form>

views.py

def handlemultipleforms(request, template="handle/multiple_forms.html"):
    """
    Handle Multiple <form></form> elements
    """
    if request.method == 'POST':
        if request.POST.get("form_type") == 'formOne':
            #Handle Elements from first Form
        elif request.POST.get("form_type") == 'formTwo':
            #Handle Elements from second Form

Django的基于类的视图提供了一个通用的FormView,但是出于所有的意图和目的,它被设计成只处理一个表单。

使用Django的通用视图处理具有相同目标action url的多个表单的一种方法是扩展'TemplateView',如下所示;我经常使用这种方法,以至于我把它做成了Eclipse IDE模板。

class NegotiationGroupMultifacetedView(TemplateView):
    ### TemplateResponseMixin
    template_name = 'offers/offer_detail.html'

    ### ContextMixin 
    def get_context_data(self, **kwargs):
        """ Adds extra content to our template """
        context = super(NegotiationGroupDetailView, self).get_context_data(**kwargs)

        ...

        context['negotiation_bid_form'] = NegotiationBidForm(
            prefix='NegotiationBidForm', 
            ...
            # Multiple 'submit' button paths should be handled in form's .save()/clean()
            data = self.request.POST if bool(set(['NegotiationBidForm-submit-counter-bid',
                                              'NegotiationBidForm-submit-approve-bid',
                                              'NegotiationBidForm-submit-decline-further-bids']).intersection(
                                                    self.request.POST)) else None,
            )
        context['offer_attachment_form'] = NegotiationAttachmentForm(
            prefix='NegotiationAttachment', 
            ...
            data = self.request.POST if 'NegotiationAttachment-submit' in self.request.POST else None,
            files = self.request.FILES if 'NegotiationAttachment-submit' in self.request.POST else None
            )
        context['offer_contact_form'] = NegotiationContactForm()
        return context

    ### NegotiationGroupDetailView 
    def post(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        if context['negotiation_bid_form'].is_valid():
            instance = context['negotiation_bid_form'].save()
            messages.success(request, 'Your offer bid #{0} has been submitted.'.format(instance.pk))
        elif context['offer_attachment_form'].is_valid():
            instance = context['offer_attachment_form'].save()
            messages.success(request, 'Your offer attachment #{0} has been submitted.'.format(instance.pk))
                # advise of any errors

        else 
            messages.error('Error(s) encountered during form processing, please review below and re-submit')

        return self.render_to_response(context)

html模板的效果如下:

...

<form id='offer_negotiation_form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {{ negotiation_bid_form.as_p }}
    ...
    <input type="submit" name="{{ negotiation_bid_form.prefix }}-submit-counter-bid" 
    title="Submit a counter bid"
    value="Counter Bid" />
</form>

...

<form id='offer-attachment-form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {{ offer_attachment_form.as_p }}

    <input name="{{ offer_attachment_form.prefix }}-submit" type="submit" value="Submit" />
</form>

...