Djangoの1つのページで複数のフォームを処理する適切な方法


202

2つのフォームを想定したテンプレートページがあります。1つのフォームのみを使用する場合、この典型的な例のように問題はありません。

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

ただし、複数のフォームを操作したい場合は、フォームの1つだけを送信し、他のフォームは送信しないことをビューに通知するにはどうすればよいですか(つまり、request.POSTのままですが、送信したフォームのみを処理します)起こりました)?


これは、expectedphrasebannedphraseがさまざまなフォームの送信ボタンの名前で、expectedphraseformbannedphraseformがフォームであるという回答に基づくソリューションです。

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
ソリューションに論理エラーはありませんか?「禁止フレーズ」を投稿すると、expectedphraseformは入力されません。
Ztyx 2011

2
これは、問題は、同時に複数のフォームを扱う程度で、一度に一つだけのフォームを処理します
輝く

回答:


141

いくつかのオプションがあります。

  1. 2つのフォームのアクションに異なるURLを配置します。次に、2つの異なるフォームを処理する2つの異なるビュー関数を用意します。

  2. POSTデータから送信ボタンの値を読み取ります。どの送信ボタンがクリックされたがわかります:複数の送信ボタンのdjangoフォームを作成するにはどうすればよいですか?


5
3)POSTデータのフィールド名から送信されるフォームを決定します。fromsに一意のフィールドがなく、すべての可能な値が空ではない場合は、いくつかの非表示の入力を含めます。
Denis Otkidach、2009年

13
4)フォームを識別する非表示フィールドを追加し、ビューでこのフィールドの値を確認します。
ソビエト

可能であれば、POSTデータを汚染しないようにします。代わりに、GETパラメーターをフォームのアクションURLに追加することをお勧めします。
pygeek

6
ここで#1が本当に最善の策です。非表示フィールドでPOSTを汚染したり、ビューをテンプレートやフォームにつなげたりしたくありません。
meteorainer 2013

5
@meteorainerを使用している場合、メッセージフレームワークまたはクエリ文字列を使用せずに、これらをインスタンス化する親ビューのフォームにエラーを返す方法はありますか?この答えは、最も近いと思われるが、ここではまだ両方の形式を扱うただ一つのビューです:stackoverflow.com/a/21271659/2532070
YPCrumble

45

今後の参考になる方法はこんな感じです。bannedphraseformは最初の形式で、expectedphraseformは2番目の形式です。最初のものがヒットした場合、2番目はスキップされます(これは、この場合は妥当な仮定です)。

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
prefix =の使用は確かに「適切な方法」です
Rich

prefix-kwargがうまくいきました。
Stephan Hoyer 2013年

1
これらの接頭辞については素晴らしいアイデアでした。今はそれらを使用しており、魅力のように機能します。ただし、どちらのフォームもライトボックス(それぞれ別のフォーム)にあるため、送信されたフォームを検出するために非表示フィールドを挿入する必要がありました。正しいライトボックスを再度開く必要があるため、送信されたフォームを正確に知る必要があり、最初のフォームに検証エラーがある場合、2番目のフォームが自動的に優先され、最初のフォームがリセットされます。ただし、最初のフォーム。あなたが知っておくべきことを考えた
ばかり

このパターンを3つのフォームの場合に拡張するのは不格好ではないでしょうか。同様に、最初のフォームからis_valid()をチェックし、次に最初の2つをチェックするなど… 互換性のあるフォームが見つかったときhandled = Falseに更新されるを持っているだけTrueでしょうか?
binki

14

Djangoのクラスベースのビューは、汎用のFormViewを提供しますが、すべての目的と目的のために、1つのフォームのみを処理するように設計されています。

Djangoの汎用ビューを使用して同じターゲットアクションURLを持つ複数のフォームを処理する1つの方法は、以下に示すように「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
私はこれと同じ問題に取り組んでおり、各投稿を個別のフォームビューで処理してから、共通のテンプレートビューにリダイレクトする方法を見つけようとしていました。重要なのは、テンプレートビューにコンテンツの取得とフォームビューの保存を行わせることです。検証は問題です。フォームをセッションに保存することは私の心を超えました...それでもクリーンな解決策を探しています。
Daniele Bernardini

14

同じページで個別に検証される複数のフォームが必要でした。私が欠けていた主要な概念は、1)送信ボタン名にフォーム接頭辞を使用すること、および2)無制限のフォームが検証をトリガーしないことでした。他の人を助ける場合、@ adam-nelsonと@ daniel-sokolowskiによる回答と@zeraienによるコメント(https://stackoverflow.com/a/17303480)に基づくTemplateViewを使用した2つのフォームAFormとBFormの簡単な例を次に示します。 / 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>

これは実際にはクリーンなソリューションだと思います。ありがとう。
チャンティアル2016年

私はこのソリューションが本当に好きです。1つの質問:_get_form()がMyViewクラスのメソッドではない理由はありますか?
2016

1
@AndréTerraそれは間違いなく可能性がありますが、他のビューで再利用できるように、おそらくTemplateViewから継承するジェネリッククラスにそれを置きたいと思います。
ybendana

1
これは素晴らしいソリューションです。それが機能するように__get_formの1行を変更する必要 がありました。data = request.POST if prefix in next(iter(request.POST.keys())) else None それ以外の場合inは機能しませんでした。
ララプソディア2016

このように1つの<form>タグを使用するということは、どの送信ボタンをクリックしたかに応じてフォームごとにする必要がある場合、必須フィールドがグローバルに必須であることを意味します。(同じアクションで)2つの<form>タグに分割すると機能します。
フラッシュ

3

Djangoフォームが使用されていない私のソリューションを共有したかった。1つのページに複数のフォーム要素があり、1つのビューを使用して、すべてのフォームからのすべてのPOSTリクエストを管理したいと考えています。

私がやったことは、ビューにパラメーターを渡してどのフォームが送信されたかを確認できるように、非表示のinputタグを導入したことです。

<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

クラスベースのビューと異なる「アクション」属性を持つアプローチを使用している場合、つまり

2つのフォームのアクションに異なるURLを配置します。次に、2つの異なるフォームを処理する2つの異なるビュー関数を用意します。

オーバーロードされた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
あなたの答えを説明していただけませんか?それは同様の問題で他の人を助け、あなたまたは質問者のコードをデバッグするのを助けるかもしれません...
creyD

0

これは、上記を処理する簡単な方法です。

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')),
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.