Django:基于类的视图一次可以接受两种形式吗?

时间:2023-01-11 19:22:12

If I have two forms:

如果我有两种形式:

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)

and wanted to use a class based view, and send both forms to the template, is that even possible?

并且想要使用基于类的视图,并将两个表单发送到模板,是否可能?

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

It seems the FormView can only accept one form at a time. In function based view though I can easily send two forms to my template and retrieve the content of both within the request.POST back.

看起来FormView一次只能接受一个表单。在基于函数的视图中,虽然我可以轻松地将两个表单发送到我的模板并在request.POST中检索两者的内容。

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

Is this a limitation of using class based view (generic views)?

这是使用基于类的视图(通用视图)的限制吗?

Many Thanks

非常感谢

7 个解决方案

#1


25  

Here's a scaleable solution. My starting point was this gist,

这是一个可扩展的解决方案。我的出发点是这个要点,

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

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

i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted

我已经增强了该解决方案,以便可以显示多个表单,但可以提交全部或个人

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

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

and this is an example usage

这是一个示例用法

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':'dave@dave.com'}

    def get_signup_initial(self):
        return {'email':'dave@dave.com'}

    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())

and the template looks like this

模板看起来像这样

<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>

An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.

在模板上需要注意的一件重要事情是提交按钮。他们必须将'name'属性设置为'action',并且他们的'value'属性必须与'form_classes'dict中给表单的名称相匹配。这用于确定已提交的个人表单。

#2


15  

By default, class-based views support just a single form per view. But there is other way to accomplish what you need. But again, this cannot handle both forms at the same time. This is also work with most of the class-based views as well as regular forms.

默认情况下,基于类的视图仅支持每个视图一个表单。但还有其他方法可以实现您的需求。但同样,这不能同时处理这两种形式。这也适用于大多数基于类的视图以及常规表单。

views.py

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})

template

模板

<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>

#3


7  

Its is possible for one class-based view to accept two forms at a time.

一个基于类的视图可以一次接受两个表单。

view.py

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

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

HTML

Take one outer form class and set action as TestView Url

获取一个外部表单类并将操作设置为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>

Good Luck

祝你好运

#4


1  

It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.

它不是基于类的视图的限制。 Generic FormView的设计并不是为了接受两种形式(嗯,它是通用的)。您可以对其进行子类化或编写自己的基于类的视图以接受两个表单。

#5


1  

This is one example when -- at least currently -- it's better to revert to the traditional function-based views. Class-based views are not a silver bullet, and it's best to use each type of the view to its best abilities.

这是一个例子 - 至少目前 - 最好还原到传统的基于功能的视图。基于类的视图不是灵丹妙药,最好将每种类型的视图用于其最佳能力。

#6


0  

I have used a following generic view based on templateview:

我使用了基于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

This has the advantage that it is reusable and all the validation is done on the forms themselves.

这具有可重用的优点,并且所有验证都在表单本身上完成。

It is then used followingly:

然后使用它:

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

#7


0  

Use django-superform

使用django-superform

This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views.

这是一种非常巧妙的方法,可以将组合表单作为单个对象线程化到外部调用者,例如基于Django类的视图。

from django_superform import FormField, SuperForm

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

In the view, you can use form_class = MyClassForm

在视图中,您可以使用form_class = MyClassForm

In the form __init__() method, you can access the forms using: self.forms['form1']

在__init __()方法中,您可以使用以下命令访问表单:self.forms ['form1']

There is also a SuperModelForm and ModelFormField for model-forms.

模型表单还有一个SuperModelForm和ModelFormField。

In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.

在模板中,您可以使用以下命令访问表单字段:{{form.form1.field}}。我建议使用{%with form1 = form.form1%}对表单进行别名,以避免一直重新读取/重构表单。

#1


25  

Here's a scaleable solution. My starting point was this gist,

这是一个可扩展的解决方案。我的出发点是这个要点,

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

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

i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted

我已经增强了该解决方案,以便可以显示多个表单,但可以提交全部或个人

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

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

and this is an example usage

这是一个示例用法

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':'dave@dave.com'}

    def get_signup_initial(self):
        return {'email':'dave@dave.com'}

    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())

and the template looks like this

模板看起来像这样

<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>

An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.

在模板上需要注意的一件重要事情是提交按钮。他们必须将'name'属性设置为'action',并且他们的'value'属性必须与'form_classes'dict中给表单的名称相匹配。这用于确定已提交的个人表单。

#2


15  

By default, class-based views support just a single form per view. But there is other way to accomplish what you need. But again, this cannot handle both forms at the same time. This is also work with most of the class-based views as well as regular forms.

默认情况下,基于类的视图仅支持每个视图一个表单。但还有其他方法可以实现您的需求。但同样,这不能同时处理这两种形式。这也适用于大多数基于类的视图以及常规表单。

views.py

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})

template

模板

<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>

#3


7  

Its is possible for one class-based view to accept two forms at a time.

一个基于类的视图可以一次接受两个表单。

view.py

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

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

HTML

Take one outer form class and set action as TestView Url

获取一个外部表单类并将操作设置为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>

Good Luck

祝你好运

#4


1  

It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.

它不是基于类的视图的限制。 Generic FormView的设计并不是为了接受两种形式(嗯,它是通用的)。您可以对其进行子类化或编写自己的基于类的视图以接受两个表单。

#5


1  

This is one example when -- at least currently -- it's better to revert to the traditional function-based views. Class-based views are not a silver bullet, and it's best to use each type of the view to its best abilities.

这是一个例子 - 至少目前 - 最好还原到传统的基于功能的视图。基于类的视图不是灵丹妙药,最好将每种类型的视图用于其最佳能力。

#6


0  

I have used a following generic view based on templateview:

我使用了基于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

This has the advantage that it is reusable and all the validation is done on the forms themselves.

这具有可重用的优点,并且所有验证都在表单本身上完成。

It is then used followingly:

然后使用它:

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

#7


0  

Use django-superform

使用django-superform

This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views.

这是一种非常巧妙的方法,可以将组合表单作为单个对象线程化到外部调用者,例如基于Django类的视图。

from django_superform import FormField, SuperForm

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

In the view, you can use form_class = MyClassForm

在视图中,您可以使用form_class = MyClassForm

In the form __init__() method, you can access the forms using: self.forms['form1']

在__init __()方法中,您可以使用以下命令访问表单:self.forms ['form1']

There is also a SuperModelForm and ModelFormField for model-forms.

模型表单还有一个SuperModelForm和ModelFormField。

In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.

在模板中,您可以使用以下命令访问表单字段:{{form.form1.field}}。我建议使用{%with form1 = form.form1%}对表单进行别名,以避免一直重新读取/重构表单。