web-dev-qa-db-ja.com

Django:クラスベースのビューは一度に2つのフォームを受け入れることができますか?

2つのフォームがある場合:

class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

class SocialForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

クラスベースのビューを使用して両方のフォームをテンプレートに送信したかったのですが、それも可能ですか?

class TestView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm

FormViewは一度に1つのフォームしか受け入れることができないようです。関数ベースのビューでは、2つのフォームをテンプレートに簡単に送信し、request.POSTで両方のコンテンツを取得できます。

variables = {'contact_form':contact_form, 'social_form':social_form }
return render(request, 'discussion.html', variables)

これは、クラスベースのビュー(ジェネリックビュー)の使用に関する制限ですか?

どうもありがとう

18
Houman

これがスケーラブルなソリューションです。私の出発点はこの要旨でした

https://Gist.github.com/michelts/1029336

複数のフォームを表示できるようにソリューションを拡張しましたが、すべてまたは個別のいずれかを送信できます

https://Gist.github.com/jamesbrobb/748c47f46b9bd224b07f

これは使用例です

class SignupLoginView(MultiFormsView):
    template_name = 'public/my_login_signup_template.html'
    form_classes = {'login': LoginForm,
                    'signup': SignupForm}
    success_url = 'my/success/url'

    def get_login_initial(self):
        return {'email':'[email protected]'}

    def get_signup_initial(self):
        return {'email':'[email protected]'}

    def get_context_data(self, **kwargs):
        context = super(SignupLoginView, self).get_context_data(**kwargs)
        context.update({"some_context_value": 'blah blah blah',
                        "some_other_context_value": 'blah'})
        return context

    def login_form_valid(self, form):
        return form.login(self.request, redirect_url=self.get_success_url())

    def signup_form_valid(self, form):
        user = form.save(self.request)
        return form.signup(self.request, user, self.get_success_url())

テンプレートは次のようになります

<form class="login" method="POST" action="{% url 'my_view' %}">
    {% csrf_token %}
    {{ forms.login.as_p }}

    <button name='action' value='login' type="submit">Sign in</button>
</form>

<form class="signup" method="POST" action="{% url 'my_view' %}">
    {% csrf_token %}
    {{ forms.signup.as_p }}

    <button name='action' value='signup' type="submit">Sign up</button>
</form>

テンプレートで注意すべき重要な点は、送信ボタンです。それらは「名前」属性を「アクション」に設定する必要があり、「値」属性は「form_classes」辞書でフォームに与えられた名前と一致する必要があります。これは、送信された個々のフォームを判別するために使用されます。

33
james

デフォルトでは、クラスベースのビューは、ビューごとに1つのフォームのみをサポートします。しかし、あなたが必要とするものを達成する他の方法があります。ただし、これも両方のフォームを同時に処理することはできません。これは、通常のフォームだけでなく、ほとんどのクラスベースのビューでも機能します。

views.py

class MyClassView(UpdateView):

    template_name = 'page.html'
    form_class = myform1
    second_form_class = myform2
    success_url = '/'

    def get_context_data(self, **kwargs):
        context = super(MyClassView, self).get_context_data(**kwargs)
        if 'form' not in context:
            context['form'] = self.form_class(request=self.request)
        if 'form2' not in context:
            context['form2'] = self.second_form_class(request=self.request)
        return context

    def get_object(self):
        return get_object_or_404(Model, pk=self.request.session['value_here'])

    def form_invalid(self, **kwargs):
        return self.render_to_response(self.get_context_data(**kwargs))

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if 'form' in request.POST:
            form_class = self.get_form_class()
            form_name = 'form'
        else:
            form_class = self.second_form_class
            form_name = 'form2'

        form = self.get_form(form_class)

        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(**{form_name: form})

テンプレート

<form method="post">
    {% csrf_token %}
    .........
    <input type="submit" name="form" value="Submit" />
</form>

<form method="post">
    {% csrf_token %}
    .........
    <input type="submit" name="form2" value="Submit" />
</form>
19
catherine

1つのクラスベースのビューが一度に2つのフォームを受け入れることが可能です。

view.py

class TestView(FormView):
    template_name = 'contact.html'
    def get(self, request, *args, **kwargs):
        contact_form = ContactForm()
        contact_form.prefix = 'contact_form'
        social_form = SocialForm()
        social_form.prefix = 'social_form'
        return self.render_to_response(self.get_context_data('contact_form':contact_form, 'social_form':social_form ))

    def post(self, request, *args, **kwargs):
        contact_form = ContactForm(self.request.POST, prefix='contact_form')
        social_form = SocialForm(self.request.POST, prefix='social_form ')

        if contact_form.is_valid() and social_form.is_valid():
            ### do something
            return HttpResponseRedirect(>>> redirect url <<<)
        else:
            return self.form_invalid(contact_form,social_form , **kwargs)


    def form_invalid(self, contact_form, social_form, **kwargs):
        contact_form.prefix='contact_form'
        social_form.prefix='social_form'
                return self.render_to_response(self.get_context_data('contact_form':contact_form, 'social_form':social_form ))

forms.py

from Django import forms
from models import Social, Contact
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Button, Layout, Field, Div
from crispy_forms.bootstrap import (FormActions)

class ContactForm(forms.ModelForm):
    class Meta:
        model = Contact
    helper = FormHelper()
    helper.form_tag = False

class SocialForm(forms.Form):
    class Meta:
        model = Social
    helper = FormHelper()
    helper.form_tag = False

[〜#〜] html [〜#〜]

1つの外部フォームクラスを取得し、アクションをTestView Urlとして設定します

{% load crispy_forms_tags %}
<form action="/testview/" method="post">
  <!----- render your forms here -->
  {% crispy contact_form %}
  {% crispy social_form%}
  <input type='submit' value="Save" />
</form>

幸運を

9

私は、templateviewに基づく次の汎用ビューを使用しました:

def merge_dicts(x, y):
    """
    Given two dicts, merge them into a new dict as a shallow copy.
    """
    z = x.copy()
    z.update(y)
    return z


class MultipleFormView(TemplateView):
    """
    View mixin that handles multiple forms / formsets.
    After the successful data is inserted ``self.process_forms`` is called.
    """
    form_classes = {}

    def get_context_data(self, **kwargs):
        context = super(MultipleFormView, self).get_context_data(**kwargs)
        forms_initialized = {name: form(prefix=name)
                             for name, form in self.form_classes.items()}

        return merge_dicts(context, forms_initialized)

    def post(self, request):
        forms_initialized = {
            name: form(prefix=name, data=request.POST)
            for name, form in self.form_classes.items()}

        valid = all([form_class.is_valid()
                     for form_class in forms_initialized.values()])
        if valid:
            return self.process_forms(forms_initialized)
        else:
            context = merge_dicts(self.get_context_data(), forms_initialized)
            return self.render_to_response(context)

    def process_forms(self, form_instances):
        raise NotImplemented

これには、再利用可能であり、すべての検証がフォーム自体で行われるという利点があります。

その後、次のように使用されます。

class AddSource(MultipleFormView):
    """
    Custom view for processing source form and seed formset
    """
    template_name = 'add_source.html'
    form_classes = {
        'source_form': forms.SourceForm,
        'seed_formset': forms.SeedFormset,
    }

    def process_forms(self, form_instances):
        pass # saving forms etc
1
Visgean Skeloru

クラスベースのビューの制限ではありません。 Generic FormViewは、2つのフォームを受け入れるようには設計されていません(まあ、それはジェネリックです)。これをサブクラス化するか、独自のクラスベースのビューを記述して2つのフォームを受け入れることができます。

1
Marat

これは、少なくとも現在のところ、従来の機能ベースのビューに戻す方が良い場合の1つの例です。クラスベースのビューは特効薬ではありません。各タイプのビューを最大限に活用することをお勧めします。

1
Berislav Lopac

@jamesの回答に似ています(同様の出発点がありました)が、POST dataを介してフォーム名を受け取る必要はありません。代わりに、自動生成されたプレフィックスを使用して、どのフォームを特定するかを決定します。 received POST data、データを割り当て、これらのフォームを検証し、最後に適切なform_validメソッドに送信します。バインドされたフォームが1つしかない場合は、その単一のフォームを送信し、そうでない場合は_{"name": bound_form_instance}_辞書。

プレフィックスを割り当てることができる_forms.Form_またはその他の「フォーム動作」クラス(例:Django formsets))と互換性がありますが、ModelFormバリアントはまだ作成していません。このビューでモデルフォームを使用できます(以下の編集を参照してください)。異なるタグのフォーム、1つのタグの複数のフォーム、または両方の組み合わせを処理できます。

コードはgithub( https://github.com/AlexECX/Django_MultiFormView )でホストされています。いくつかの使用ガイドラインといくつかのユースケースをカバーする小さなデモがあります。目標は、FormViewのようにできるだけ近く感じられるクラスを持つことでした。

簡単な使用例を次に示します。

views.py

_    class MultipleFormsDemoView(MultiFormView):
        template_name = "app_name/demo.html"

        initials = {
            "contactform": {"message": "some initial data"}
        }

        form_classes = [
            ContactForm,
            ("better_name", SubscriptionForm),
        ]

        # The order is important! and you need to provide an
        # url for every form_class.
        success_urls = [
            reverse_lazy("app_name:contact_view"),
            reverse_lazy("app_name:subcribe_view"),
        ]
        # Or, if it is the same url:
        #success_url = reverse_lazy("app_name:some_view")

        def get_contactform_initial(self, form_name):
            initial = super().get_initial(form_name)
            # Some logic here? I just wanted to show it could be done,
            # initial data is assigned automatically from self.initials anyway
            return initial

        def contactform_form_valid(self, form):
            title = form.cleaned_data.get('title')
            print(title)
            return super().form_valid(form) 

        def better_name_form_valid(self, form):
            email = form.cleaned_data.get('email')
            print(email)
            if "Somebody once told me the world" is "gonna roll me":
                return super().form_valid(form)
            else:
                return HttpResponse("Somebody once told me the world is gonna roll me")
_

template.html

_{% extends "base.html" %}

{% block content %}

<form method="post">
    {% csrf_token %}
    {{ forms.better_name }}
    <input type="submit" value="Subscribe">
</form>

<form method="post">
    {% csrf_token %}
    {{ forms.contactform }}
    <input type="submit" value="Send">
</form>

{% endblock content %}
_

EDIT-ModelFormsについて

Welp、ModelFormViewを調べた後、MultiModelFormViewを作成するのはそれほど簡単ではないことに気づきました。おそらく、SingleObjectMixinも書き直す必要があります。当面は、モデルインスタンスで「インスタンス」キーワード引数を追加する限り、ModelFormを使用できます。

_def get_bookform_form_kwargs(self, form_name):
    kwargs = super().get_form_kwargs(form_name)
    kwargs['instance'] = Book.objects.get(title="I'm Batman")
    return kwargs
_
0
Alexandre Cox

Django-superform を使用します

これは、Djangoクラスベースのビューなどのように、構成されたフォームを外部の呼び出し元に単一のオブジェクトとしてスレッド化するための非常に優れた方法です。

_from Django_superform import FormField, SuperForm

class MyClassForm(SuperForm):
    form1 = FormField(FormClass1)
    form2 = FormField(FormClass2)
_

ビューでは、_form_class = MyClassForm_を使用できます

フォーム__init__()メソッドでは、次を使用してフォームにアクセスできます:_self.forms['form1']_

モデルフォーム用のSuperModelFormModelFormFieldもあります。

テンプレートでは、_{{ form.form1.field }}_を使用してフォームフィールドにアクセスできます。 _{% with form1=form.form1 %}_を使用してフォームにエイリアスを付けることをお勧めします。これにより、フォームの再読み取り/再構築を常に回避できます。

0
vdboor