ChatGPT解决这个技术问题 Extra ChatGPT

在 Django 中处理一页上的多个表单的正确方法

我有一个模板页面需要两种形式。如果我只使用一种形式,那么就像这个典型的例子一样:

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

但是,如果我想使用多个表单,我如何让视图知道我只提交一个表单而不是另一个(即它仍然是 request.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')
您的解决方案没有逻辑错误吗?如果您发布“bannedphrase”,则不会填充预期的短语。
这一次只能处理一个表格,问题是关于同时处理多个表格
所有这些答案都很有帮助,但他们没有解决无效表格的方法。当两个表格可能无效时,有人知道如何发回无效表格吗?
我真的很惊讶这些答案都没有引用表单集:“表单集是一个抽象层,可以在同一页面上处理多个表单。”请参阅此处 docs.djangoproject.com/en/3.2/topics/forms/modelforms/… 和此处 docs.djangoproject.com/en/3.2/topics/forms/formsets 的文档。我知道这是一个老问题,但表单集并不是新问题。

C
Community

你有几个选择:

将不同的 URL 放入两个表单的操作中。然后你将有两个不同的视图函数来处理这两种不同的形式。从 POST 数据中读取提交按钮的值。您可以知道单击了哪个提交按钮:How can I build multiple submit buttons django form?


3) 根据 POST 数据中的字段名确定提交哪个表单。如果您的 froms 没有唯一字段且所有可能的值都不为空,请包括一些隐藏的输入。
4) 添加一个标识表单的隐藏字段,并在您的视图中检查该字段的值。
如果可能的话,我会远离污染 POST 数据。我建议将 GET 参数添加到表单操作 url。
#1 真的是你最好的选择。您不想用隐藏字段污染您的 POST,也不想将您的视图绑定到您的模板和/或表单。
@meteorainer 如果您使用第一,有没有办法将错误传递回实例化这些错误的父视图中的表单,而不使用消息框架或查询字符串?这个答案似乎是最接近的,但这里仍然只是一个处理两种形式的视图:stackoverflow.com/a/21271659/2532070
A
Adam Nelson

供将来参考的方法是这样的。 banphraseform 是第一种形式,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')

使用 prefix= 确实是“正确的方式”
prefix-kwarg 完成了这项工作,很好!
带有这些前缀的好主意,我们现在使用它们,它们就像一个魅力。但是我们仍然必须插入一个隐藏字段来检测提交了哪个表单,因为两个表单都在一个灯箱中(每个都在一个单独的灯箱中)。因为我们需要重新打开正确的灯箱,所以我们需要确切知道提交了哪个表单,然后如果第一个表单有任何验证错误,第二个自动获胜并重置第一个表单,尽管我们仍然需要显示来自第一种形式。只是觉得你应该知道
将这种模式扩展到三种形式的情况会不会很笨拙?比如,从第一个表单检查 is_valid(),然后是前两个,等等……也许只有一个 handled = False 在找到兼容的表单时更新为 True
C
Community

我需要在同一页面上独立验证的多个表单。我缺少的关键概念是 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>

我认为这实际上是一个干净的解决方案。谢谢。
我真的很喜欢这个解决方案。一个问题: _get_form() 不是 MyView 类的方法有什么原因吗?
@AndréTerra 绝对可以,尽管您可能希望将它放在从 TemplateView 继承的通用类中,以便您可以在其他视图中重用它。
这是一个很好的解决方案。我需要更改 __get_form 的一行以使其正常工作:data = request.POST if prefix in next(iter(request.POST.keys())) else None 否则 in 不起作用。
像这样使用单个
标记意味着必填字段在应该执行时全局需要,具体取决于单击了哪个提交按钮。拆分成两个 标签(具有相同的操作)有效。
D
Daniel Sokolowski

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

使用 Django 的通用视图处理具有相同目标操作 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>

...

我正在努力解决同样的问题,并试图找到一种方法在单独的表单视图中处理每个帖子,然后重定向到通用模板视图。关键是让模板视图负责获取内容,让表单视图负责保存。验证是一个问题。我想到了将表格保存到会话中......仍在寻找一个干净的解决方案。
c
chatuur

想在不使用 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>

视图.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

虽然这可能不是最优雅和可扩展的解决方案,但它绝对非常简单且易于实现。到目前为止,与所有其他人相比,速度最快。我已经在自定义 CBV 中实现了这一点,并且像魅力一样工作。好主意!
e
e-nouri

这有点晚了,但这是我找到的最佳解决方案。您为表单名称及其类创建了一个查找字典,您还必须添加一个属性来识别表单,并且在您的视图中您必须使用 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)

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


H
Hoang Nhat Nguyen

看法:

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 %}

你能解释一下你的答案吗?它可以帮助其他人解决类似的问题,并可以帮助调试您或提问者的代码......
x
xle

基于 @ybendana 的 this answer

同样,我们使用 is_bound 来检查表单是否能够验证。请参阅this section of the documentation

绑定和未绑定表单 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's answer

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

编辑:这已经存在。请参阅this repository

注意:此方法需要 TemplateResponseMixin 才能使 render_to_response() 起作用,而 ContextMixin 才能使 get_context_data() 起作用。使用这些 Mixin 或从它们继承的 CBV


N
NameError

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

将不同的 URL 放入两个表单的操作中。然后你将有两个不同的视图函数来处理这两种不同的形式。

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

视图.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>

A
Abilash Raghu

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

在 Html 模板中,我们把 Post

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

a
atomvs
if request.method == 'POST':
    expectedphraseform = ExpectedphraseForm(request.POST)
    bannedphraseform = BannedphraseForm(request.POST)
    if expectedphraseform.is_valid():
        expectedphraseform.save()
        return HttpResponse("Success")
    if bannedphraseform.is_valid():
        bannedphraseform.save()
        return HttpResponse("Success")
else:
    bannedphraseform = BannedphraseForm()
    expectedphraseform = ExpectedphraseForm()
return render(request, 'some.html',{'bannedphraseform':bannedphraseform, 'expectedphraseform':expectedphraseform})

这对我来说准确无误。这种方法有一个问题,它验证了表单的两个错误。但工作完全正常。


A
Abdul Rehman

我发现了一种非常有趣的方法,可以使用同一个视图从一个页面发送两个表单。我尝试了很多选择,但只是想要一些可以正常工作的东西。所以这是我发现的东西。但它仅在页面上只有两个表单时才有效。

我只使用 try and except 方法来首先使用 try 第一种形式,如果这不起作用,请尝试第二种形式。知道它工作得非常好,这很有趣。不要在可扩展的应用程序上使用它,因为它可能会产生麻烦或可能危及应用程序的安全性,否则使用基于类的视图来提交多个表单或为每个表单创建单独的视图。

def create_profile(request):
    if request.method=='POST':
        try:       
            biograph = Biography(name=name, email=email, full_name=full_name, slug_name=slug_name, short_bio=short_bio)
            biograph.save()

        except:
            social = SocialMedia(twitter=twitter, instagram=instagram, facebook=facebook, linkedin=linkedin, github=github)
            social.save()

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅