Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django combine DetailView and FormView

Tags:

I have a view where I need to display information about a certain model instance hence I use a DetailView. I also need that same view to handle a regular form (not a model form), both displaying the form on GET and validating it on POST. To do that, I am trying to use a FormView however the combination of both view clases does not work:

class FooView(FormView, DetailView):     # configs here 

In GET (for simplicity of the question I will only show the issue with GET since POST has a different issue), it does not work because the form never gets added to the context. The reason has to do with method resolution order for that class:

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

Within the request, Django has to get the form and add it to the context. That happens in 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)) 

However the first class with the MRO which has get defined is 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) 

As you can see the BaseDetailView.get never calls super hence the ProcessFormView.get will never be called hence the the form will not be added to the context. This can be fixed by creating a mixin view where all these nuances for GET and POST can be taken care of however I do not feel it is a clean solution.

My question: is there any way of accomplishing what I want with Django's default CBV implementation without creating any mixins?

like image 528
miki725 Avatar asked Jun 05 '13 05:06

miki725


1 Answers

One solution would be to use mixins, as per limelights' comment above.

Another approach is to have two separate views, one a DetailView and the other a FormView. Then, in the template for the former, display the same form you're using in the latter, except that you won't leave the action attribute empty -- instead, set it to the url for the FormView. Something along the lines of this (please beware of any errors as I'm writing this without any testing):

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

In my_detail_view.html:

<!-- some representation of the MyModel object -->  <form method="post" action="{% url "my_form_view_url" %}">  {{ form }}  </form> 

In 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'), # ... 

Note that the require_POST decorator is optional, in the case that you don't want the MyFormView to be accessible by GET and want it only to be processed when the form is submitted.

like image 65
Berislav Lopac Avatar answered Nov 09 '22 08:11

Berislav Lopac