modelo - Django: ¿Pueden las vistas basadas en clases aceptar dos formularios a la vez?
guardar datos de un formulario django (7)
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'':''[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())
y la plantilla se ve así
<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>
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.
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
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
<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>
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 %}
<form action="/testview/" method="post">
<!----- render your forms here -->
{% crispy contact_form %}
{% crispy social_form%}
<input type=''submit'' value="Save" />
</form>
Buena suerte
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
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.
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.