在Django中处理一个页面上的多个表单的正确方法
- 2024-12-23 08:43:00
- admin 原创
- 55
问题描述:
我有一个模板页面需要两个表单。如果我只使用一个表单,那么一切就都正常了,如以下典型示例所示:
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')
解决方案 1:
您有以下几种选择:
在操作中为两个表单设置不同的 URL。然后,您将拥有两个不同的视图函数来处理这两个不同的表单。
从 POST 数据中读取提交按钮的值。您可以判断单击了哪个提交按钮:如何构建多个提交按钮 django 表单?
解决方案 2:
供将来参考的方法是这样的。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')
解决方案 3:
我需要在同一页面上独立验证的多个表单。我遗漏的关键概念是 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>
解决方案 4:
想分享一下不使用 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>
视图.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
解决方案 5:
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>
...
解决方案 6:
这有点晚了,但这是我找到的最佳解决方案。您为表单名称及其类创建了一个查找字典,还必须添加一个属性来标识表单,并且在视图中您必须将其添加为隐藏字段,并使用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)
我希望这对将来有帮助。
解决方案 7:
看法:
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 %}
解决方案 8:
如果你使用基于类的视图和不同的“动作”属性的方法,我的意思是
在操作中为两个表单设置不同的 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>
解决方案 9:
根据@ybendana的回答:
再次,我们用它is_bound
来检查表单是否能够通过验证。请参阅文档的这一部分:
装订表格和非装订表格
表单实例要么绑定到一组数据,要么不绑定。
如果它绑定到一组数据,它就能够验证该数据并将数据显示在 HTML 中,并将表单呈现为 HTML。
如果未绑定,它就无法进行验证(因为没有数据需要验证!),但它仍然可以将空白表单呈现为 HTML。
我们使用元组列表来表示表单对象及其详细信息,以实现更高的可扩展性和更少的重复性。
get()
但是,我们不会重写 ,而是重写get_context_data()
,使将表单的新空白实例(带前缀)插入到响应中成为任何请求的默认操作。在 POST 请求的上下文中,我们重写该post()
方法以:
使用
prefix
检查每个表单是否已提交验证已提交的表单
使用
cleaned_data
context
通过覆盖数据将所有无效的表单返回到响应中
# 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 \n # 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) \n # check the validation
if form.is_valid():
# call the form's save() method or do whatever you \n # want with form.cleaned_data
form.save()
else:
# overwrite context data for this form so that it is \n # 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 。
解决方案 10:
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})
对我来说,这种方法完全符合我的要求。这种方法有一个问题,即它会验证两个表单的错误。但效果非常好。
解决方案 11:
我发现了一种非常有趣的方法,可以使用相同的视图从单个页面发送两个表单。我尝试了很多选项,但只想要一个可以正常工作的方法。所以这是我发现的方法。但它只在页面上只有两个表单时才有效。
我只是使用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()
解决方案 12:
这是处理上述问题的简单方法。
在 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')),
解决方案 13:
在Django中一个页面只能有一个表单。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件