Django: могут ли представления на основе классов принимать две формы за раз?

Если у меня две формы:

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 может принимать только одну форму за раз. В представлении на основе функций, хотя я могу легко отправить две формы в свой шаблон и получить содержимое как внутри запроса. POST назад.

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

Является ли это ограничением использования представления на основе класса (общие представления)?

Большое спасибо

Ответ 1

Здесь масштабируемое решение. Моей отправной точкой был этот смысл,

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>

Важно отметить, что на шаблоне есть кнопки отправки. Они должны иметь атрибут "name" для "action", а атрибут "value" должен соответствовать имени, указанному в форме в dict'classes. Это используется, чтобы определить, какая индивидуальная форма была отправлена.

Ответ 2

По умолчанию представления на основе классов поддерживают только одну форму для каждого представления. Но есть и другой способ выполнить то, что вам нужно. Но опять же, это не может обрабатывать обе формы одновременно. Это также работает с большинством представлений на основе классов, а также с регулярными формами.

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>

Ответ 3

В одном представлении на основе класса можно принимать две формы за раз.

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

Возьмите один внешний класс формы и установите действие как URL TestView

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

Удача

Ответ 4

Это не ограничение представлений на основе классов. Generic FormView просто не предназначен для принятия двух форм (ну, это общий). Вы можете подклассифицировать его или написать собственное представление на основе класса, чтобы принять две формы.

Ответ 5

Это один пример, когда - по крайней мере в настоящее время - лучше вернуться к традиционным представлениям на основе функций. Представления, основанные на классах, не являются серебряной пулей, и лучше всего использовать каждый вид представления в своих лучших способностях.

Ответ 6

Я использовал следующее общее представление, основанное на шаблоне:

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

Ответ 7

Используйте 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']

Для модельных форм существует также SuperModelForm и ModelFormField.

В шаблоне вы можете получить доступ к полям формы, используя: {{ form.form1.field }}. Я бы рекомендовал наложить форму с помощью {% with form1=form.form1 %}, чтобы не перечитывать/восстанавливать форму все время.