Правильный способ обработки нескольких форм на одной странице в Django

204

У меня есть страница шаблона, ожидающая две формы. Если я просто использую одну форму, все в порядке, как в этом типичном примере:

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')
Адам Нельсон
источник
2
Нет ли логической ошибки в вашем решении? Если вы разместите фразу «bannedphrase», ожидаемая фраза не будет заполнена.
Ztyx
2
Это будет обрабатывать только одну форму , в то время, речь идет об обработке многочисленных форм , в то же время
светит

Ответы:

142

У вас есть несколько вариантов:

  1. Поместите разные URL-адреса в действие для двух форм. Тогда у вас будет две разные функции просмотра для работы с двумя разными формами.

  2. Прочитайте значения кнопки отправки из данных POST. Вы можете сказать, какая кнопка отправки была нажата: Как я могу построить несколько кнопок отправки формы django?

Нед Бэтчелдер
источник
5
3) Определите, какая форма отправляется из имен полей в данных POST. Включите некоторые скрытые входные данные, если ваши предложения не имеют уникальных полей со всеми возможными значениями, которые не являются пустыми.
Денис Откидач
13
4) Добавьте скрытое поле, идентифицирующее форму, и проверьте значение этого поля в вашем представлении.
Совют
Я бы держался подальше от загрязнения данных POST, если это возможно. Я рекомендую добавить параметр GET в URL-адрес действия формы.
pygeek
6
# 1 действительно ваш лучший выбор здесь. Вы не хотите загрязнять свой POST скрытыми полями и не хотите привязывать свой вид к своему шаблону и / или форме.
Метеорайнер
5
@meteorainer, если вы используете номер один, есть ли способ передать ошибки обратно в формы в родительском представлении, которое их создает, без использования инфраструктуры сообщений или строк запроса? Этот ответ кажется самым близким, но здесь он по-прежнему представляет собой одно представление, обрабатывающее обе формы: stackoverflow.com/a/21271659/2532070
YPCrumble
45

Метод для дальнейшего использования - что-то вроде этого. 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')
Адам Нельсон
источник
7
использование префикса = действительно «правильный путь»
Rich
Префикс-Кварг сделал свою работу, приятно!
Стефан Хойер
1
Отличная идея с этими префиксами, мы использовали их сейчас, и они работают как шарм. Но нам все равно пришлось вставить скрытое поле, чтобы определить, какая форма была отправлена, потому что обе формы находятся в лайтбоксе (каждая в отдельном). Поскольку нам нужно повторно открыть правильный лайтбокс, нам нужно точно знать, какая форма была отправлена, а затем, если первая форма имеет какие-либо ошибки проверки, вторая автоматически побеждает, а первая форма сбрасывается, хотя нам все еще нужно отображать ошибки из первая форма. Просто подумал, что вы должны знать
Enduriel
Не было бы неуклюже распространить эту модель на случай трех форм? Например, с проверкой is_valid () из первой формы, затем из первых двух и т. Д. Может быть, просто иметь handled = Falseобновление, которое обновляется Trueпри обнаружении совместимой формы?
Бинки
14

Представления на основе классов в 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>

...
Даниэль Соколовский
источник
1
Я борюсь с этой же проблемой и пытался найти способ обработать каждое сообщение в отдельном представлении формы, а затем перенаправить в общий вид шаблона. Суть в том, чтобы сделать представление шаблона ответственным за получение содержимого, а представления формы за сохранение. проверка является проблемой, хотя. сохранение форм в сессии пришло мне в голову ... Все еще ищу чистое решение.
Даниэле Бернардини
14

Мне нужно было несколько форм, которые независимо проверялись на одной странице. Ключевые понятия, которые я пропустил: 1) использование префикса формы для имени кнопки отправки и 2) неограниченная форма не запускает проверку. Если это кому-то поможет, вот мой упрощенный пример двух форм AForm и BForm, использующих TemplateView на основе ответов @ 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>
ybendana
источник
Я думаю, что это на самом деле чистое решение. Спасибо.
chhantyal
Мне очень нравится это решение. Один вопрос: есть ли причина, по которой _get_form () не является методом класса MyView?
авиаудар
1
@ AndréTerra это определенно может быть, хотя вы, вероятно, захотите иметь его в универсальном классе, который наследует от TemplateView, чтобы вы могли использовать его в других представлениях.
Ибендана
1
Это отличное решение. Мне нужно было изменить одну строку __get_form, чтобы она работала: в data = request.POST if prefix in next(iter(request.POST.keys())) else None противном случае inне сработало.
Larapsodia
Использование одного тега <form>, подобного этому, означает, что обязательные поля требуются глобально, когда они должны быть в форме, в зависимости от того, какая кнопка отправки была нажата. Разделение на два тега <form> (с одним и тем же действием) работает.
Вспышка
3

Хотел поделиться своим решением, где формы 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
chatuur
источник
Я думаю, что это хороший и более простой выход
Шедрак
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>
NameError
источник
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
Не могли бы вы объяснить свой ответ? Это помогло бы другим с подобной проблемой и могло бы помочь отладить ваш код или
вопросный
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')),
Абилаш Рагху
источник