libros guia filetype espaƱol definitiva curso books book django django-class-based-views

guia - Django combina DetailView y FormView



django manual (6)

Django también tiene una documentación bastante larga sobre este problema.

https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/#using-formmixin-with-detailview

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>