web-dev-qa-db-fra.com

Django: Les vues basées sur les classes peuvent-elles accepter deux formulaires à la fois?

Si j'ai deux formes:

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)

et souhaitait utiliser une vue basée sur les classes et envoyer les deux formulaires au modèle, est-ce même possible?

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

Il semble que FormView ne peut accepter qu'un seul formulaire à la fois. Dans la vue basée sur les fonctions, je peux facilement envoyer deux formulaires à mon modèle et en récupérer le contenu dans la requête.POST.

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

S'agit-il d'une limitation à l'utilisation d'une vue basée sur les classes (vues génériques)?

Merci beaucoup

14
Houman

Voici une solution évolutive. Mon point de départ était ce Gist,

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

j'ai amélioré cette solution afin que plusieurs formulaires puissent être affichés, mais que tout ou une personne puisse être soumise

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

et ceci est un exemple d'utilisation

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

et le modèle ressemble à ceci

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

Une chose importante à noter sur le modèle sont les boutons de soumission. Leur attribut 'name' doit être défini sur 'action' et leur attribut 'value' doit correspondre au nom donné au formulaire dans le dict 'form_classes'. Ceci est utilisé pour déterminer quel formulaire individuel a été soumis.

28
james

Par défaut, les vues basées sur les classes ne prennent en charge qu'un seul formulaire par vue. Mais il existe un autre moyen d'accomplir ce dont vous avez besoin. Mais encore une fois, cela ne peut pas gérer les deux formes en même temps. Cela fonctionne également avec la plupart des vues basées sur les classes ainsi que les formulaires standard.

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

modèle

<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

Il est possible pour une vue basée sur une classe d'accepter deux formulaires à la fois.

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

Prenez une classe de formulaire externe et définissez l'action comme 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>

Bonne chance

8

Ce n'est pas une limitation des vues basées sur les classes. FormView générique n’est tout simplement pas conçu pour accepter deux formulaires (c’est générique). Vous pouvez le sous-classer ou écrire votre propre vue basée sur les classes pour accepter deux formulaires.

1
Marat

C'est un exemple dans lequel, du moins à l'heure actuelle, il est préférable de revenir aux vues traditionnelles basées sur des fonctions. Les vues basées sur les classes ne sont pas une solution miracle, et il est préférable d'utiliser chaque type de vue de manière optimale.

1
Berislav Lopac

J'ai utilisé une vue générique suivante basée sur 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

Cela a l'avantage d'être réutilisable et toute la validation est effectuée sur les formulaires eux-mêmes. 

Il est ensuite utilisé comme suit:

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

Ressemble à @james answer (j'ai eu un point de départ similaire), mais il n'a pas besoin de recevoir un nom de formulaire via POST data. Au lieu de cela, il utilise des préfixes générés automatiquement pour déterminer le ou les formulaires qui ont reçu POST données, attribuer les données, valider ces formulaires et les envoyer à la méthode form_valid appropriée. S'il n'y a qu'un seul formulaire lié, il envoie ce formulaire unique, sinon, il envoie un dictionnaire {"name": bound_form_instance}.

Il est compatible avec forms.Form ou d'autres classes de "formulaire se comportant" auxquelles un préfixe peut être attribué (par exemple, les formulaires Django), mais n'ont pas encore créé de variante ModelForm, bien que vous puissiez utiliser un formulaire de modèle avec cette vue (voir la modification ci-dessous). . Il peut gérer des formulaires dans différentes balises, plusieurs formulaires dans une balise ou une combinaison des deux. 

Le code est hébergé sur github ( https://github.com/AlexECX/Django_MultiFormView ). Il y a quelques directives d'utilisation et une petite démo couvrant certains cas d'utilisation. Le but était d'avoir une classe aussi proche que possible du FormView. 

Voici un exemple avec un cas d'utilisation simple:

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 - à propos de ModelForms

Welp, après avoir examiné ModelFormView, j'ai compris qu'il ne serait pas si facile de créer un MultiModelFormView. J'aurais probablement besoin de réécrire SingleObjectMixin également. En attendant, vous pouvez utiliser un ModelForm à condition d'ajouter un argument de mot-clé 'instance' avec une instance de modèle.

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

Utilisez Django-superform

C’est une façon très soignée de lier un formulaire composé en tant qu’objet unique à des appelants extérieurs, tels que les vues basées sur la classe Django.

from Django_superform import FormField, SuperForm

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

Dans la vue, vous pouvez utiliser form_class = MyClassForm

Dans la méthode de formulaire __init__(), vous pouvez accéder aux formulaires à l’aide de: self.forms['form1']

Il existe également SuperModelForm et ModelFormField pour les formulaires-modèles.

Dans le modèle, vous pouvez accéder aux champs du formulaire à l’aide de: {{ form.form1.field }}. Je recommanderais d'aliaser le formulaire en utilisant {% with form1=form.form1 %} pour éviter de le relire/reconstruire le formulaire tout le temps.

0
vdboor