Правильный способ обработки нескольких форм на одной странице в 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 ответов

Решение

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

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

  2. Прочитайте значения кнопки отправки из данных 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 мы переопределяем метод, чтобы:

  1. Использовать prefix чтобы проверить, была ли отправлена ​​каждая форма
  2. Подтвердите отправленные формы
  3. Обработайте действительные формы, используя cleaned_data
  4. Верните любые недопустимые формы в ответ, перезаписав 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})

У меня это сработало точно так, как я хотел. У этого подхода есть единственная проблема: он проверяет обе ошибки формы. Но работает совершенно нормально.

На одной странице может быть только одна форма в Django.

Другие вопросы по тегам