Follow us on Twitter

/// Our Blog

05 Jun / 2012
Category: Technology, Productivity, Author: God.el Comments: Django Class Based Generic Views (The Good, The Bad, And the Ugly)
Django Class Based Generic Views (The Good, The Bad, And the Ugly)

Django Class Based Generic Views (The Good, The Bad, And the Ugly)

When they came out, i was excited. I really enjoyed the added functionality that models and forms had implemented using class like interfaces. Turns out, class based views weren't as loved by the lot. By the time I realised this I had already jumped into learning and using them. Several blogposts have been made, reviews written, tears shed, pain shared, guilt exposed and then, the dust has covered them. People have come to terms with their views, functional, or class-based.

I for one choose to use them. Because I like them. I like writing classes. A quality that has stuck, since my C++ days in college. At first, I decided to write this article in adamant defense of what I have begun to depend on more and more (I know almost the entire codebase of class-based views), but then I realised that I couldn't honestly do that, simply because, I had to know almost the entire codebase of class-based views to use them to their fullest. Therefore, my aim in this post is to provide a simple reference for usage of class-based views for simple tasks without knowing the code base or glancing at the docs.

Instead I will write this about them. They can be saviours or villains, one should use them where they save lives, but run from them where they become a burden (though, they will never kill), this is very doable considering we all have functional views to fall back on.

The Good:


  1. ListViews.

    Let us say, I need to do a simple task. I need to create a view, that displays some data from a model. Lets try to do it with class-based views. I google, class based generic-views, something called ListView catches my eye. It looks cool, it allows me to display data from my model very easily, it also allows me to paginate it simply by passing parameters into a url.
    from django.views.generic import ListView
    url(r'^mylistpage/(?P<page>\w+)/$', ListView.as_view(model=MyModel, paginate_by=20, template_name="mytemplate.html")),

    This creates a paginated view with 20 items per page from the model MyModel. It also directly takes you to the page number provided by the page keyword in the url. The list of objects is passed in by the name 'object_list'. The page keyword passed to the url can also be 'last' to directly access the last page. It also disallows page numbers larger than the possible page numbers. Not to bad for a one-liner. The equivalent functional View would be a lot bigger. You can also pass in a queryset instead of model into the url parameter if you like such as..

    url(r'^mylistpage/(?P<page>\w+)/$', ListView.as_view(queryset=MyModel.objects.filter(myfield="foo"), paginate_by=20, template_name="mytemplate.html")),

    Now you say, but i just wanted to display some data.. I didnt want pagination, i didnt ask for power, why thrust it upon me. Well, do not fear, the defaults are always implemented and therefore you can simply do.

    url(r'^mylistpage/$', ListView.as_view(model=MyModel, template_name="mytemplate.html")),

    Finally, you say, that well, you didnt want to clutter your urls so much. Then my friend, you have to write a view, but fear not, since these are class-based views you can easily inherit the generic view. eg.

    class MyListView(ListView):
    	model = MyModel #queryset = MyModel.objects.filter(myfield="foo")
    	paginate_by = 20
    	template_name = "mytemplate.html"
    myview = MyListView.as_view()
    #and in urls:
    url(r'^mylistpage/(?P<page>\w+)$', myview),

    Things to remember:

    • If you want pagination, page must be passed in as a url parameter.
    • The object list will always have the name 'object_list', if you want to change this behaviour, you must pass in context_object_name as a keyword argument to the as_view() in the url alongside model / paginate_by etc. Or you must define it in your class if you are using the class to write views.
    • What if my queryset / model returns no object. This case will raise a 404 error, unless you pass in an allow_empty = True keyword into the as_view() or define it in your class.
  2. FormView

    Okay, now that we're warmed up. Let us look at a feature that i use most, from class based generic views, the FormView. This is the bread and butter of writing forms using class-based views and as such it is extremely useful. Let us jump right in with some code. First, the url shortcut of using it.

    from django.views.generic import FormView
    url(r'^myform/$', FormView.as_view(initial={}, form_class=MyForm, success_url="/success/", template_name="myformpage.html")),

    You can pass three parameters into the FormView, initial is a dictionary of initialization values for the form, form_class is your form and success_url is the url to redirect to after successful(validated) post. If the form is posted but it fails validation conditions defined by your code, the same page will be reloaded and errors will be shown.
    But what happens with the form on successful post. This is missing. Actually, nothing happens. If you want to do something you have to override the FormView class as so.

    class MyFormView(FormView):
    	def form_valid(self, form):
    		form_data = form.cleaned_data
    		#Do something
    		return super(MyFormView, self).form_valid(form)

    This according to me is a deficiency of form_view as it makes you write some view code explicitly each time.
    A senior developer who has been associated with our company had written an extremely useful package that is available via the python package index (pypi) named fhurl , in which he had removed the need of writing form views by making a function that took keywords as arguments to do a lot of work for you without having to write much code. One very cool thing that i saw he did in this was that he required the function to be implemented and all the work would be done here.
    Similarly I recommend overriding FormView in this way:

    class MyFormView(FormView):
    	def form_valid(self, form):
    		return super(MyFormView, self).form_valid(form)

    In the save() function in your form you can write code for saving the form. This class of ours can now be used directly from our urls and handle most of our cases where we would like to make a page that displays a form, collects data, and redirects to a url.

    url(r'^myform/$', MyFormView.as_view(initial={}, form_class=MyForm, success_url="/success/", template_name="myformpage.html")),
    #and in you need to write:
    class MyForm(forms.Form):
    	#form stuff
    	def save(self):
    		form_data = self.cleaned_data
    		#Do something here

    points to be noted: If you want to redirect to the same url you can obviously say success_url = "."

  3. DetailView

    A sibling of the ListView, this view can be used to create api like access to the models of an object, where each page shows details of a single object belonging to a queryset or a model. It can be used in two ways.

    from django.views.generic import DetailView
    url(r'^mydetail/(?P<pk>\w+)/$', DetailView.as_view(model=myModel))

    or as:

    url(r'^mydetail/(?P<slug>\w+)/$', DetailView.as_view(model=myModel, slug_field='myfield'))

    • The first case does myModel.objects.get(id=pk) and passes that object to your page as 'mymodel' i.e. the name is constructed using your model name. If you want to change this behaviour, you can set context_object_name = 'myname' in the as_view() clause.
    • The second case does myModel.objects.get(myfield=slug) and passes this to your tempalte to be rendered.

    The Task of a DetailView seems trivial to do so with a functional view as well, and so i have not found too many special uses for this yet.

  4. ModelFormViews

    I like them, but they are confusing to the beginner. With model forms one can perform CRUD operations on objects of the model. They have all the required functionality. But all this functionality is not implemented as a single view. CreateView is used to create an object, UpdateView is used to update an object and DeleteView is used to delete an object. Thus there are three model form views.

    from django.views.generic import CreateView, UpdateView, DeleteView
    # CreateView
    url(r'^myobject/create/$' CreateView.as_view(model=myModel, template_name="mycreate.html"))
    # UpdateView
    url(r'^myobject/update/(?P<pk>\w+)/$' UpdateView.as_view(model=myModel, template_name="myupdate.html"))
    # DeleteView
    url(r'^myobject/delete/(?P<pk>\w+)/$' DeleteView.as_view(model=myModel, template_name="mydelete/html"))

    As you can see the behaviour is sort of like DetailView. You have to give the model you want to work on and a pk (not in createview). In the case of UpdateView and DeleteView, the object myModel.objects.get(id=pk) will either be put in a form and be available for updating, or the object will be deleted. Similarly the following two are also valid..

    # UpdateView
    url(r'^myobject/update/(?P<slug>\w+)/$') UpdateView.as_view(model=myModel, slug_field="myfield", template_name="myupdate.html")
    # DeleteView
    url(r'^myobject/delete/(?P<slug>\w+)/$') DeleteView.as_view(model=myModel, slug_field="myfield", template_name="mydelete.html")

    These views pass the object myModel.objects.get(myfield="slug") for updation or deletion.

These are a few ways that class based views can easily be converted into simple replacements for the function based generic views.
It is good to know them. These are a few of the good parts of class based generic views. Easily to learn and easy to use for simple purposes. I do not know much about the calender / datetimeviews, If people know uses of them, please enlighten in the comments. I will be writing a part two and part 3 of this series. The Bad which will cover some of the negatives of class-based views(over riding functions), and The Ugly which will teach you how to hack together different mixins of class-based views.

/// Our Twitter Feed

Keep in touch with what we do and how we roll, on twitter.

Visit link
Copyright 2012 Glitterbug Technologies Pvt. Ltd.