在Django中一页上处理多种形式的正确方法


200

我有一个模板页面,需要两种形式。如果我仅使用一种形式,则可以像下面的典型示例一样正常:

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

但是,如果我想使用多个表单,我如何让视图知道我仅提交一种表单,而不提交另一种表单(即,它仍然是request.POST,但我只想处理提交表单的表单)发生了吗?


这是该解决方案基于这样的答案expectedphrasebannedphrase是的提交按钮的不同形式和名称expectedphraseformbannedphraseform是形式。

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

2
您的解决方案是否存在逻辑错误?如果您发布“ bannedphrase”,则预期短语格式将不会被填充。
Ztyx 2011年

2
这一次只能处理一种形式,问题是要同时处理多种形式
2016年

Answers:


140

您有几种选择:

  1. 在两种形式的操作中放入不同的URL。然后,您将拥有两个不同的视图函数来处理两种不同的形式。

  2. 从POST数据中读取提交按钮的值。您可以确定单击了哪个提交按钮:如何构建多个django表单提交按钮?


5
3)确定从POST数据中的字段名称提交哪种形式。如果您的发件人没有唯一字段且所有可能的值都不为空,则包括一些隐藏的输入。
Denis Otkidach 09年

13
4)添加一个标识表单的隐藏字段,并在视图中检查此字段的值。
苏联2011年

如果可能的话,我将避免污染POST数据。我建议改为向表单操作网址添加GET参数。
pygeek

6
#1确实是您最好的选择。您不想用隐藏的字段来污染您的POST,也不想将视图绑定到模板和/或表单。
meteorainer 2013年

5
@meteorainer(如果使用数字1)是否可以将错误传递回实例化这些错误的父视图中的表单,而无需使用消息框架或查询字符串?这个答案似乎是最接近的,但是在这里,它仍然只是处理两种形式的一种视图:stackoverflow.com/a/21271659/2532070
YPCrumble 2014年

45

将来的参考方法是这样的。bannedphraseform是第一种形式,expectedphraseform是第二种形式。如果第一个被命中,则第二个被跳过(在这种情况下,这是一个合理的假设):

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

7
使用prefix =确实是“正确的方法”
2012年

prefix-kwarg做得很好,很好!
Stephan Hoyer

1
带有这些前缀的好主意,我们现在使用那些前缀,它们就像一个魅力一样工作。但是我们仍然必须插入一个隐藏字段来检测提交的表单,因为两种表单都在灯箱中(每个表单都在单独的表单中)。因为我们需要重新打开正确的灯箱,所以我们需要确切地知道提交了哪个表单,然后,如果第一个表单有任何验证错误,则第二个表单将自动获胜,并且第一个表单会被重置,尽管我们仍然需要从表单中显示错误。第一种形式。只是认为您应该知道
Enduriel

将这种模式扩展到三种形式的情况是否笨拙?例如,先检查第一个表单的is_valid(),然后检查前两个表单,以此类推……也许只有一个当找到兼容表单时才handled = False更新为True
宾基

14

Django的基于类的视图提供了通用的FormView,但出于所有目的和目的,它仅设计用于处理一种形式。

使用Django的通用视图处理具有相同目标操作网址的多种形式的一种方法是扩展“ 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>

...

1
我在同一个问题上苦苦挣扎,并试图找到一种在单独的表单视图中处理每个帖子,然后重定向到通用模板视图的方法。关键是使模板视图负责获取内容,使表单视图负责保存。验证是一个问题。将表单保存到会话中使我心烦意乱...仍在寻找一个干净的解决方案。
Daniele Bernardini

14

我需要在同一页面上经过独立验证的多种形式。我缺少的关键概念是: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>

我认为这实际上是一个干净的解决方案。谢谢。
chhantyal '16

我真的很喜欢这个解决方案。一个问题:_get_form()不是MyView类的方法吗?
空袭

1
@AndréTerra当然可以,尽管您可能希望将其放在从TemplateView继承的通用类中,以便可以在其他视图中重用它。
ybendana

1
这是一个很好的解决方案。我需要更改__get_form的一行以使其起作用: data = request.POST if prefix in next(iter(request.POST.keys())) else None 否则in不起作用。
larapsodia

像这样使用单个<form>标记意味着,当按表单单击取决于表单的提交按钮时,全局必填字段是必填字段。拆分为两个<form>标记(具有相同的操作)即可。
Flash

3

想与我分享未使用Django Forms的解决方案。我在一个页面上有多个表单元素,并且我想使用一个视图来管理所有表单的所有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

我认为这是一个很好
且更

2

这有点晚了,但这是我找到的最佳解决方案。您需要为表单名称及其类创建一个查找字典,还必须添加一个属性来标识该表单,并且在视图中必须使用将该属性添加为隐藏字段form.formlabel

# form holder
form_holder = {
    'majeur': {
        'class': FormClass1,
    },
    'majsoft': {
        'class': FormClass2,
    },
    'tiers1': {
        'class': FormClass3,
    },
    'tiers2': {
        'class': FormClass4,
    },
    'tiers3': {
        'class': FormClass5,
    },
    'tiers4': {
        'class': FormClass6,
    },
}

for key in form_holder.keys():
    # If the key is the same as the formlabel, we should use the posted data
    if request.POST.get('formlabel', None) == key:
        # Get the form and initate it with the sent data
        form = form_holder.get(key).get('class')(
            data=request.POST
        )

        # Validate the form
        if form.is_valid():
            # Correct data entries
            messages.info(request, _(u"Configuration validée."))

            if form.save():
                # Save succeeded
                messages.success(
                    request,
                    _(u"Données enregistrées avec succès.")
                )
            else:
                # Save failed
                messages.warning(
                    request,
                    _(u"Un problème est survenu pendant l'enregistrement "
                      u"des données, merci de réessayer plus tard.")
                )
        else:
            # Form is not valid, show feedback to the user
            messages.error(
                request,
                _(u"Merci de corriger les erreurs suivantes.")
            )
    else:
        # Just initiate the form without data
        form = form_holder.get(key).get('class')(key)()

    # Add the attribute for the name
    setattr(form, 'formlabel', key)

    # Append it to the tempalte variable that will hold all the forms
    forms.append(form)

我希望这会在将来对您有所帮助。


2

如果您使用基于类的视图和不同的“动作”属性的方法,我的意思是

在两种形式的操作中放入不同的URL。然后,您将拥有两个不同的视图函数来处理两种不同的形式。

您可以使用重载get_context_data方法轻松处理来自不同形式的错误,例如:

views.py:

class LoginView(FormView):
    form_class = AuthFormEdited
    success_url = '/'
    template_name = 'main/index.html'

    def dispatch(self, request, *args, **kwargs):
        return super(LoginView, self).dispatch(request, *args, **kwargs)

    ....

    def get_context_data(self, **kwargs):
        context = super(LoginView, self).get_context_data(**kwargs)
        context['login_view_in_action'] = True
        return context

class SignInView(FormView):
    form_class = SignInForm
    success_url = '/'
    template_name = 'main/index.html'

    def dispatch(self, request, *args, **kwargs):
        return super(SignInView, self).dispatch(request, *args, **kwargs)

    .....

    def get_context_data(self, **kwargs):
        context = super(SignInView, self).get_context_data(**kwargs)
        context['login_view_in_action'] = False
        return context

模板:

<div class="login-form">
<form action="/login/" method="post" role="form">
    {% csrf_token %}
    {% if login_view_in_action %}
        {% for e in form.non_field_errors %}
            <div class="alert alert-danger alert-dismissable">
                {{ e }}
                <a class="panel-close close" data-dismiss="alert">×</a>
            </div>
        {% endfor %}
    {% endif %}
    .....
    </form>
</div>

<div class="signin-form">
<form action="/registration/" method="post" role="form">
    {% csrf_token %}
    {% if not login_view_in_action %}
        {% for e in form.non_field_errors %}
            <div class="alert alert-danger alert-dismissable">
                {{ e }}
                <a class="panel-close close" data-dismiss="alert">×</a>
            </div>
        {% endfor %}
    {% endif %}
   ....
  </form>
</div>

2

视图:

class AddProductView(generic.TemplateView):
template_name = 'manager/add_product.html'

    def get(self, request, *args, **kwargs):
    form = ProductForm(self.request.GET or None, prefix="sch")
    sub_form = ImageForm(self.request.GET or None, prefix="loc")
    context = super(AddProductView, self).get_context_data(**kwargs)
    context['form'] = form
    context['sub_form'] = sub_form
    return self.render_to_response(context)

def post(self, request, *args, **kwargs):
    form = ProductForm(request.POST,  prefix="sch")
    sub_form = ImageForm(request.POST, prefix="loc")
    ...

模板:

{% block container %}
<div class="container">
    <br/>
    <form action="{% url 'manager:add_product' %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        {{ sub_form.as_p }}
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
</div>
{% endblock %}

4
您能解释一下您的答案吗?这将帮助其他人遇到类似的问题,并可能有助于调试您的或提问者的代码……
creyD

0

这是处理上述问题的简单方法。

在HTML模板中,我们将发布

<form action="/useradd/addnewroute/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->
<form>
<form action="/useradd/addarea/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->

<form>

视野中

   def addnewroute(request):
      if request.method == "POST":
         # do something



  def addarea(request):
      if request.method == "POST":
         # do something

在URL中提供所需的信息,例如

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
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.