web-dev-qa-db-de.com

Django: Können klassenbasierte Ansichten zwei Formen gleichzeitig akzeptieren?

Wenn ich zwei Formen habe:

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)

und wollte eine klassenbasierte Ansicht verwenden und beide Formulare an die Vorlage senden, ist das überhaupt möglich?

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

Es scheint, dass die FormView jeweils nur ein Formular akzeptieren kann. In der funktionsbasierten Ansicht kann ich jedoch problemlos zwei Formulare an meine Vorlage senden und den Inhalt beider Formulare in der Anfrage abrufen.

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

Ist dies eine Einschränkung der Verwendung von klassenbasierten Ansichten (generischen Ansichten)?

Danke vielmals

14
Houman

Hier ist eine skalierbare Lösung. Mein Ausgangspunkt war dieser Gist,

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

ich habe diese Lösung so verbessert, dass mehrere Formulare angezeigt werden können, aber entweder alle oder eine einzelne Person eingereicht werden kann

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

und dies ist eine beispielhafte Verwendung

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

und die Vorlage sieht so aus

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

Eine wichtige Sache, die auf der Vorlage beachtet werden muss, sind die Schaltflächen zum Senden. Ihr 'name'-Attribut muss auf' action 'gesetzt sein, und ihr' value'-Attribut muss mit dem Namen übereinstimmen, der dem Formular im 'form_classes'-Diktat gegeben wurde. Hiermit wird festgelegt, welches einzelne Formular übermittelt wurde.

28
james

Klassenbasierte Ansichten unterstützen standardmäßig nur ein einzelnes Formular pro Ansicht. Es gibt jedoch einen anderen Weg, um das zu erreichen, was Sie brauchen. Dies kann jedoch nicht beide Formen gleichzeitig behandeln. Dies funktioniert auch mit den meisten klassenbasierten Ansichten sowie regulären Formularen.

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

Vorlage

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

Es ist möglich, dass eine Klassenansicht zwei Formulare gleichzeitig akzeptiert.

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

Nehmen Sie eine äußere Formularklasse und legen Sie die Aktion als TestView-URL fest

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

Viel Glück

8

Es ist keine Einschränkung für klassenbasierte Ansichten. Generic FormView ist nicht dafür ausgelegt, zwei Formulare zu akzeptieren (naja, es ist generisch). Sie können eine Unterklasse erstellen oder Ihre eigene klassenbasierte Ansicht schreiben, um zwei Formulare zu akzeptieren.

1
Marat

Dies ist ein Beispiel, wenn es - zumindest derzeit - besser ist, zu den traditionellen funktionsbasierten Ansichten zurückzukehren. Klassenbasierte Ansichten sind keine Aufzählungszeichen, und es empfiehlt sich, jeden Ansichtstyp optimal zu nutzen.

1
Berislav Lopac

Ich habe eine folgende generische Ansicht basierend auf Templateview verwendet:

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

Dies hat den Vorteil, dass es wiederverwendbar ist und die gesamte Validierung auf den Formularen selbst erfolgt. 

Es wird dann wie folgt verwendet:

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
0
Visgean Skeloru

Ähnelt der Antwort von @james (ich hatte einen ähnlichen Ausgangspunkt), es muss jedoch kein Formularname über POST data empfangen werden. Stattdessen verwendet er automatisch generierte Präfixe, um zu ermitteln, welche Formulare POST -Daten erhalten haben, die Daten zuzuordnen, diese Formulare zu validieren und schließlich an die entsprechende form_valid-Methode zu senden. Wenn es nur ein gebundenes Formular gibt, wird dieses einzelne Formular gesendet, ansonsten wird ein {"name": bound_form_instance}-Wörterbuch gesendet.

Es ist kompatibel mit forms.Form oder anderen "formverhaltens" -Klassen, denen ein Präfix zugewiesen werden kann (z. B. Django-Formulare), aber noch keine ModelForm-Variante erstellt haben. In dieser Ansicht könnten Sie ein Modellformular verwenden (siehe Bearbeiten unten). . Es kann Formulare in verschiedenen Tags, mehrere Formulare in einem Tag oder eine Kombination aus beiden verarbeiten. 

Der Code wird auf github gehostet ( https://github.com/AlexECX/Django_MultiFormView ). Es gibt einige Anwendungsrichtlinien und eine kleine Demo, die einige Anwendungsfälle abdeckt. Das Ziel war, eine Klasse zu haben, die sich so nah wie möglich anfühlt, wie das FormView. 

Hier ist ein Beispiel mit einem einfachen Anwendungsfall:

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 - über ModelForms

Welp, nachdem ich mir ModelFormView angeschaut hatte, wurde mir klar, dass es nicht so einfach wäre, ein MultiModelFormView zu erstellen. Ich würde wahrscheinlich auch SingleObjectMixin neu schreiben müssen. In der Zwischenzeit können Sie eine ModelForm verwenden, solange Sie mit einer Modellinstanz ein Argumentargument "Instanz" hinzufügen.

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

Verwenden Sie Django-Superform

Dies ist eine ziemlich nette Methode, um ein zusammengesetztes Formular als einzelnes Objekt an externe Anrufer weiterzuleiten, z. B. Ansichten, die auf der Django-Klasse basieren.

from Django_superform import FormField, SuperForm

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

In der Ansicht können Sie form_class = MyClassForm verwenden.

In der form __init__()-Methode können Sie auf die Formulare zugreifen: self.forms['form1']

Es gibt auch eine SuperModelForm und ModelFormField für Modellformen.

In der Vorlage können Sie auf die Formularfelder zugreifen: {{ form.form1.field }}. Ich würde das Aliasing des Formulars mit {% with form1=form.form1 %} empfehlen, um zu vermeiden, dass das Formular ständig neu gelesen/rekonstruiert wird.

0
vdboor