Правильный способ обработки нескольких форм на одной странице в Django
У меня есть страница шаблона, ожидающая две формы. Если я просто использую одну форму, все в порядке, как в этом типичном примере:
if request.method == 'POST':
form = AuthorForm(request.POST,)
if form.is_valid():
form.save()
# do something.
else:
form = AuthorForm()
Однако если я хочу работать с несколькими формами, как я могу сообщить представлению, что я отправляю только одну из форм, а не другую (т.е. это все еще request.POST, но я хочу обработать только форму, для которой отправляю получилось)?
Это решение основано на ответе, где ожидаемая фраза и запрещенная фраза - это названия кнопок отправки для различных форм, а ожидаемая фраза и форма запрещенной фразы - это формы.
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')
13 ответов
У вас есть несколько вариантов:
Поместите разные URL-адреса в действие для двух форм. Тогда у вас будет две разные функции просмотра для работы с двумя разными формами.
Прочитайте значения кнопки отправки из данных POST. Вы можете сказать, какая кнопка отправки была нажата: Как я могу создать несколько кнопок отправки формы django?
Метод для дальнейшего использования - что-то вроде этого. bannedphraseform - это первая форма, а ожидаемая фраза - вторая. Если первый попадет, второй будет пропущен (что в данном случае является разумным предположением):
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) неограниченная форма не запускает проверку. Если это кому-то поможет, вот мой упрощенный пример двух форм AForm и BForm, использующих TemplateView на основе ответов @adam-nelson и @daniel-sokolowski и комментария @zeraien ( /questions/7224206/ispolzovanie-neskolkih-form-na-stranitse-v-django/7224212#7224212):
# 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, но для всех целей и задач он предназначен для обработки только одной формы.
Один из способов обработки нескольких форм с одним и тем же целевым URL-адресом действия с использованием общих представлений 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>
...
Посмотреть:
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 %}
Это немного поздно, но это лучшее решение, которое я нашел. Вы создаете поисковый словарь для имени формы и ее класса, вам также нужно добавить атрибут для идентификации формы, а в ваших представлениях вы должны добавить его как скрытое поле с 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)
Я надеюсь, что это поможет в будущем.
На основе @ybendana:
Опять же, мы используем
is_bound
чтобы проверить, подходит ли форма для проверки. См. :
Этот раздел документацииСвязанные и несвязанные формы
Экземпляр формы либо привязан к набору данных, либо не привязан.
- Если он привязан к набору данных, он способен проверить эти данные и отобразить форму как HTML с данными, отображаемыми в HTML.
- Если он не привязан, он не может выполнить проверку (потому что нет данных для проверки!), Но он все равно может отображать пустую форму как HTML.
Мы используем список кортежей для объектов формы и их деталей, что обеспечивает большую расширяемость и меньше повторений.
Однако вместо отмены
get()
, мы переопределяем, чтобы сделать вставку нового пустого экземпляра формы (с префиксом) в ответ действием по умолчанию для любого запроса. В контексте запроса 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 \
# 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
объект как атрибут и привязка и
post()
как указано выше.
Изменить: это уже существует. См. Этот репозиторий.
NB: этот метод требуется
TemplateResponseMixin
для
render_to_response()
а также
ContextMixin
для
get_context_data()
работать. Либо используйте эти Mixin, либо CBV, который происходит от них.
Если вы используете подход с представлениями, основанными на классе, и разными атрибутами действий, я имею в виду
Поместите разные 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>
Я обнаружил довольно интересный способ отправить ДВЕ формы с одной страницы, используя одно и то же представление. Я пробовал много вариантов, но просто хотел что-то, что может просто работать. Итак, вот что я обнаружил. Но это работает только тогда, когда на странице всего ДВЕ формы .
Я использую просто
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()
Вот простой способ справиться с вышеизложенным.
В 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')),
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})
У меня это сработало точно так, как я хотел. У этого подхода есть единственная проблема: он проверяет обе ошибки формы. Но работает совершенно нормально.