guia - Django combina DetailView y FormView
django manual (6)
Django también tiene una documentación bastante larga sobre este problema.
Aconsejan realizar 2 vistas diferentes, y hacer que la vista detallada se refiera a la vista de formulario en la publicación.
Actualmente estoy viendo si este hack podría funcionar:
class MyDetailFormView(FormView, DetailView):
model = MyModel
form_class = MyFormClass
template_name = ''my_template.html''
def get_context_data(self, **kwargs):
context = super(MyDetailFormView, self).get_context_data(**kwargs)
context[''form''] = self.get_form()
return context
def post(self, request, *args, **kwargs):
return FormView.post(self, request, *args, **kwargs)
Tengo una vista en la que necesito mostrar información sobre una determinada instancia de modelo, por lo tanto, uso un DetailView
. También necesito esa misma vista para manejar un formulario regular (no un formulario modelo), tanto para mostrar el formulario en GET
como para validarlo en POST
. Para hacer eso, estoy intentando usar un FormView
pero la combinación de ambas clases de vista no funciona:
class FooView(FormView, DetailView):
# configs here
En GET
(para simplificar la pregunta, solo mostraré el problema con GET
ya que POST
tiene un problema diferente), no funciona porque el formulario nunca se agrega al contexto. La razón tiene que ver con el orden de resolución del método para esa clase:
>>> inspect.getmro(FooView)
(FooView,
django.views.generic.edit.FormView,
django.views.generic.detail.DetailView,
django.views.generic.detail.SingleObjectTemplateResponseMixin,
django.views.generic.base.TemplateResponseMixin,
django.views.generic.edit.BaseFormView,
django.views.generic.edit.FormMixin,
django.views.generic.detail.BaseDetailView,
django.views.generic.detail.SingleObjectMixin,
django.views.generic.base.ContextMixin,
django.views.generic.edit.ProcessFormView,
django.views.generic.base.View,
object)
Dentro de la solicitud, Django tiene que obtener el formulario y agregarlo al contexto. Eso sucede en ProcessFormView.get
:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
Sin embargo, la primera clase con el MRO que se ha definido es BaseDetailView
:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Como puede ver, BaseDetailView.get
nunca llama a super
por lo ProcessFormView.get
, nunca se llamará a ProcessFormView.get
, por lo que el formulario no se agregará al contexto. Esto puede solucionarse creando una vista mixta donde todos estos matices para GET
y POST
puedan ser atendidos, sin embargo, no creo que sea una solución limpia.
Mi pregunta: ¿hay alguna forma de lograr lo que quiero con la implementación por defecto de CBV de Django sin crear mixins?
En Django By Example de lightbird, están usando una biblioteca, MCBV, para mezclar vistas genéricas:
Los tutoriales de mi guía utilizarán una biblioteca de vistas basadas en clases basadas en vistas genéricas modificadas de Django; la biblioteca se llama MCBV (M significa modular) y la principal diferencia en comparación con los CBV genéricos es que es posible combinar y combinar múltiples vistas genéricas fácilmente (por ejemplo, ListView y CreateView, DetailView y UpdateView, etc.)
Puedes seguir la explicación aquí: helper-functions
Y úselo para mezclar FormView y DetailView, o lo que sea
Código: MCBV
Es un post viejo pero bueno para referencia.
Un elegante y reutilizable es utilizar una etiqueta de inclusión personalizada para el formulario.
templatetags / my_forms_tag.py
from django import template
from ..forms import MyFormClass
register = template.Library()
@register.inclusion_tag(''<app>/my_form.html'')
def form_tag(*args, **kwargs):
my_form = MyFormClass()
return {''my_form '':my_form}
mi_forma.html
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
La publicación será tomada por la vista de FormView donde se coloca la etiqueta de inclusión. Y puede recibir cualquier contexto que pase en la inclusión. No olvide cargar my_form_tag y crear la vista para MyForm e incluir la entrada en urls.py
Realicé mi solución utilizando ModelForms y algo como esto: En el método get_context_data de mi DetailView que hice:
form = CommentForm(
instance=Comment(
school=self.object, user=self.request.user.profile
)
)
context[''form''] = form
Y mi FormView era como:
class SchoolComment(FormView):
form_class = CommentForm
def get_success_url(self):
return resolve_url(''schools:school-profile'', self.kwargs.get(''pk''))
def form_valid(self, form):
form.save()
return super(SchoolComment, self).form_valid(form)
Una solución sería utilizar mixins, como se indica en los comentarios anteriores.
Otro enfoque es tener dos vistas separadas, una vista de DetailView
y la otra vista de FormView
. Luego, en la plantilla para el primero, muestre el mismo formulario que está usando en el último, excepto que no dejará el atributo de action
vacío; en su lugar, FormView
en la url para FormView
. Algo parecido a esto (por favor, tenga cuidado con cualquier error, ya que lo estoy escribiendo sin ninguna prueba):
En views.py
:
class MyDetailView(DetailView):
model = MyModel
template_name = ''my_detail_view.html''
def get_context_data(self, **kwargs):
context = super(MyDetailView, self).get_context_data(**kwargs)
context[''form''] = MyFormClass
return context
class MyFormView(FormView):
form_class = MyFormClass
success_url = ''go/here/if/all/works''
En my_detail_view.html
:
<!-- some representation of the MyModel object -->
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
En urls.py
:
# ...
url(''^my_model/(?P<pk>/d+)/$'', MyDetailView.as_view(), name=''my_detail_view_url''),
url(''^my_form/$'', require_POST(MyFormView.as_view()), name=''my_form_view_url''),
# ...
Tenga en cuenta que el decorador require_POST
es opcional, en el caso de que no quiera que MyFormView
pueda acceder a MyFormView
y que solo se procese cuando se MyFormView
el formulario.
Utilizando FormMixin
vistas.py
from django.contrib.auth import get_user_model
from django.core.urlresolvers import (
reverse_lazy
)
from django.http import Http404
from django.shortcuts import (
render,
redirect
)
from django.views.generic import (
DetailView,
FormView,
)
from django.views.generic.edit import FormMixin
from .forms import SendRequestForm
User = get_user_model()
class ViewProfile(FormMixin, DetailView):
model = User
context_object_name = ''profile''
template_name = ''htmls/view_profile.html''
form_class = SendRequestForm
def get_success_url(self):
return reverse_lazy(''view-profile'', kwargs={''pk'': self.object.pk})
def get_object(self):
try:
my_object = User.objects.get(id=self.kwargs.get(''pk''))
return my_object
except self.model.DoesNotExist:
raise Http404("No MyModel matches the given query.")
def get_context_data(self, *args, **kwargs):
context = super(ViewProfile, self).get_context_data(*args, **kwargs)
profile = self.get_object()
# form
context[''form''] = self.get_form()
context[''profile''] = profile
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
#put logic here
return super(ViewProfile, self).form_valid(form)
def form_invalid(self, form):
#put logic here
return super(ViewProfile, self).form_invalid(form)
forms.py
from django import forms
class SendRequestForm(forms.Form):
request_type = forms.CharField()
def clean_request_type(self):
request_type = self.cleaned_data.get(''request_type'')
if ''something'' not in request_type:
raise forms.ValidationError(''Something must be in request_type field.'')
return request_type
urls.py
urlpatterns = [
url(r''^view-profile/(?P<pk>/d+)'', ViewProfile.as_view(), name=''view-profile''),
]
modelo
username -{{object.username}}
id -{{object.id}}
<form action="{% url ''view-profile'' object.id %}" method="POST">
{% csrf_token %}
{{form}}
<input type="submit" value="Send request">
</form>