Django: ¿Pueden las vistas basadas en clases aceptar dos formularios a la vez?

Si tengo dos formas:

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) 

y quería usar una vista basada en clases, y enviar ambos formularios a la plantilla, ¿eso es posible?

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

Parece que FormView solo puede aceptar un formulario a la vez. Sin embargo, en la vista basada en funciones, puedo enviar fácilmente dos formularios a mi plantilla y recuperar el contenido de ambos dentro de request.POST.

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

¿Es esto una limitación del uso de vistas basadas en clases (vistas genéricas)?

Muchas gracias

Aquí hay una solución escalable. Mi punto de partida fue esta esencia,

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

He mejorado esa solución para que se puedan mostrar formularios múltiples, pero se pueden enviar todos o un individuo

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

y este es un ejemplo de uso

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

y la plantilla se ve así

   

Una cosa importante a tener en cuenta en la plantilla son los botones de envío. Deben tener su atributo ‘nombre’ establecido en ‘acción’ y su atributo ‘valor’ debe coincidir con el nombre dado al formulario en el dict ‘form_classes’. Esto se usa para determinar qué formulario individual se ha enviado.

De forma predeterminada, las vistas basadas en clases admiten solo un único formulario por vista. Pero hay otra manera de lograr lo que necesita. Pero, una vez más, esto no puede manejar ambas formas al mismo tiempo. Esto también funciona con la mayoría de las vistas basadas en clases, así como con las formas regulares.

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

modelo

 
{% csrf_token %} .........
{% csrf_token %} .........

Es posible que una vista basada en clases acepte dos formularios a la vez.

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

Tome una clase de formulario externo y establezca acción como la URL de TestView

 {% load crispy_forms_tags %} 
{% crispy contact_form %} {% crispy social_form%}

Buena suerte

No es una limitación de las vistas basadas en clases. Generic FormView simplemente no está diseñado para aceptar dos formularios (bueno, es genérico). Puede crear una subclase o escribir su propia vista basada en clases para aceptar dos formularios.

Este es un ejemplo cuando, al menos actualmente, es mejor volver a las vistas tradicionales basadas en funciones. Las vistas basadas en clases no son una bala de plata, y es mejor usar cada tipo de vista para sus mejores habilidades.

He usado una vista genérica siguiente basada en la vista de plantilla:

 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 

Esto tiene la ventaja de que es reutilizable y toda la validación se realiza en los formularios mismos.

Luego se usa a continuación:

 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 

Use django-superform

Esta es una forma bastante clara de enhebrar un formulario compuesto como un solo objeto para los llamantes externos, como las vistas basadas en la clase Django.

 from django_superform import FormField, SuperForm class MyClassForm(SuperForm): form1 = FormField(FormClass1) form2 = FormField(FormClass2) 

En la vista, puedes usar form_class = MyClassForm

En el método __init__() , puede acceder a los formularios usando: self.forms['form1']

También hay un SuperModelForm y ModelFormField para modelos de formulario.

En la plantilla, puede acceder a los campos del formulario usando: {{ form.form1.field }} . Recomendaría aliasing el formulario que usa {% with form1=form.form1 %} para evitar volver a leer / reconstruir el formulario todo el tiempo.

Se asemeja a la respuesta de @james (tenía un punto de partida similar), pero no es necesario que reciba un nombre de formulario a través de datos POST. En su lugar, utiliza prefijos autogenerados para determinar qué formulario (s) recibió datos POST, asignar los datos, validar estos formularios y, finalmente, enviarlos al método form_valid apropiado. Si solo hay 1 formulario enlazado, envía ese único formulario, de lo contrario envía un diccionario {"name": bound_form_instance} .

Es compatible con forms.Form u otras clases de “formas de comportamiento” a las que se les puede asignar un prefijo (por ejemplo, django formssets), pero aún no han creado una variante de ModelForm, aunque podría usar un modelo con esta vista (ver edit abajo). Puede manejar formularios en tags diferentes, formularios múltiples en una etiqueta o una combinación de ambos.

El código está alojado en github ( https://github.com/AlexECX/django_MultiFormView ). Hay algunas pautas de uso y una pequeña demostración que cubre algunos casos de uso. El objective era tener una clase que se siente lo más cerca posible, como el FormView.

Aquí hay un ejemplo con un caso de uso 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 %} 
{% csrf_token %} {{ forms.better_name }}
{% csrf_token %} {{ forms.contactform }}
{% endblock content %}

EDITAR – sobre ModelForms

Welp, después de mirar ModelFormView me di cuenta de que no sería tan fácil crear un MultiModelFormView, probablemente necesitaría reescribir SingleObjectMixin también. Mientras tanto, puede usar un ModelForm siempre que agregue un argumento de palabra clave ‘instancia’ con una instancia de modelo.

 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