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


200

У мене на шаблонній сторінці очікуються дві форми. Якщо я просто використовую одну форму, то все добре, як у цьому типовому прикладі:

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

Якщо я хочу працювати з декількома формами, однак, як я можу повідомити, що я надсилаю лише одну з форм, а не іншу (тобто це все ще запит.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
Це стосується лише однієї форми за один раз, питання стосується обробки декількох форм одночасно
блискучий

Відповіді:


140

У вас є кілька варіантів:

  1. Введіть різні URL-адреси в дії для двох форм. Тоді ви матимете дві різні функції перегляду для роботи з двома різними формами.

  2. Прочитайте значення кнопки подання з даних POST. Ви можете сказати, на яку кнопку натисніть кнопку "Надіслати", як я можу створити форму декількох кнопок подання "


5
3) Визначте, яка форма подається з імен полів у даних POST. Включіть кілька прихованих входів, якщо у ваших осіб немає унікальних полів, усі значення яких не можуть бути порожніми.
Денис Откідач

13
4) Додайте приховане поле, що ідентифікує форму, і перевірте значення цього поля у вашому представленні.
Радянський

Я б тримався подалі від забруднення даних POST, якщо це можливо. Я рекомендую замість цього додати параметр GET до URL-адреси дії форми.
pygeek

6
№1 - це справді найкраща ставка. Ви не хочете забруднювати вас POST прихованими полями, а також не хочете прив'язувати свій погляд до шаблону та / або форми.
meteorainer

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
використання префіксу = справді є "правильним способом"
Річ

префікс-kwarg зробив роботу, приємно!
Стефан Хойєр

1
Чудова ідея з тими префіксами, ми використовували їх зараз, і вони працюють як шарм. Але нам все-таки довелося вставити приховане поле, щоб виявити, яка форма була подана, оскільки обидві форми знаходяться у лайтбоксі (кожна в окремій). Оскільки нам потрібно заново відкрити правильну лайтбокс, нам потрібно точно знати, яка форма була подана, і тоді, якщо перша форма має будь-які помилки перевірки, друга автоматично перемагає, а перша форма скидається, хоча нам ще потрібно відобразити помилки з перша форма. Просто подумав, що ти повинен знати
Ендуріель

Чи не було б незграбним розповсюдити цю закономірність у випадку трьох форм? Мовляв, перевіряючи is_valid () з першої форми, потім перші два, і т. Д. ... Можливо, просто handled = Falseбуде оновлено, Trueколи буде знайдена сумісна форма?
бінкі

14

Класові погляди Django забезпечують загальний FormView, але для всіх намірів і цілей він призначений для обробки однієї форми.

Один із способів обробки декількох форм з однією URL-адресою цільової дії за допомогою загальних представлень Django - це розширення 'TemplateView', як показано нижче; Я використовую такий підхід досить часто, щоб перетворити його на шаблон IDE Eclipse.

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>

Я думаю, що це насправді чисте рішення. Дякую.
chhantyal

Мені дуже подобається таке рішення. Одне запитання: чи є причина, чому _get_form () не є методом класу MyView?
авіанапад

1
@ AndréTerra це, безумовно, могло бути, хоча ви, ймовірно, хочете мати його в загальному класі, який успадковується від TemplateView, щоб ви могли повторно використовувати його в інших представленнях.
ybendana

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

Я думаю, що це хороший і простіший вихід
Shedrack

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>

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')),
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.