Skip to content

Instantly share code, notes, and snippets.

@mjtamlyn
Last active April 12, 2019 18:55
Show Gist options
  • Save mjtamlyn/6080039 to your computer and use it in GitHub Desktop.
Save mjtamlyn/6080039 to your computer and use it in GitHub Desktop.
Django generic class based views - version 2?

Django generic class based views - version 2?

Firstly, lets start with some full disclosure. I'm a big fan of class based views. Since they first came out, I've pretty much not written a functional view at all. I use the generic ones almost all the time, when they don't fit I build my own. I've written some websites with wonderfully complex heirarchy of composed features, using almost every individual mixin from Django and a few of my own. When I want to do some work on Django, my first port of call is normally the list of issues tagged as "generic views". I envisaged CCBV and then helped rewrite the official documentation. In short, you could say I'm a class based view evangelist.

At the wonderful DjangoCon in Poland, Russ gave a talk about the history of the class based views. In particular he spoke about the feature set which is currently provided in the generic views. This is actually the feature set which was considered to be helpful when the original generic functional views were written, way back in the LWJ days of Django. Whilst these still cover a good range of the general tasks a web developer may be asked to do at the moment, the web has evolved and there are many other things which are hard to do within this structure.

Since the GCBVs came out, various community projects have sought to extend them to do more things. The most obvious and useful ones of these are django-braces and django-extra-views. In a sense, django rest framework also extends the GCBV API to allow easy construction of APIs. Other common scenarios for the modern web include, but are not limited to:

  • List/Create view
  • Detail/Edit view
  • List views with ordering, search and filtering
  • Continuous scrolling in place of pagination
  • Multiple forms and/or formsets on a single page
  • Authorization in its many forms
  • AJAX capable forms
  • PJAX

The role of a view

I think it's worth taking a step back from these specific problems, and looking at the general situation of what a view does. Views form the glue which combines all the other components of you application together to handle a request. They generally will work through a number of tasks, and all of these tasks may or may not apply to an individual view.

Step 1: Is the request valid? Of course, there are many definitions of what "valid" means. This step actually starts before you even get to your view, when the webserver checks the integrity of the request. Then the URL resolved in Django works out whether the path is valid, and passes the request off to a specific view. You may have middleware in place which checks all requests on their way though. The first thing that happens in any CBV is that we check that the view knows how to handle the HTTP method of the request.

Of course, there are other ways of checking the request is valid. The most common probably relates the checking certain features of the user - are they authenticated and authorised? If not, then we need to respond appropriately.

Step 2: Read some information from a data store For the majority of Django websites, this means reading one or more objects from a database for display. It could also mean loading information from cache, looking up the content of the page for a CMS or reading some statistics from Redis.

This has an interesting interaction with the previous step - we may not know if the user is allowed to view a particular resource until we have loaded it from the database, and we may need to handle the two cases of "this object exists but you can't look at it" and "this object does not exist" differently.

Sometimes we may have other validation concerns which arise from this step - for example the user is requesting some information and has filled in a form which may need validation. We could be requesting page 47 of 23, or the archive view for a page in the future.

Step 3: Write some information to a data store Normally, writing information is done via some sort of form class. This form class encapsulates the validation and processing of the information, and often saving it to the data store as well.

Step 4: Render a response Finally, we want to render all this information to a response, redirect to somewhere else, return a file, or any other response.

Despite the fact these steps are enumerated, in practice they don't necessarily happen in that order. As discussed, validation checking happens at many times during the request handling and can be handled different ways depending on what the problem is. As for reading and writing information, this could happen serveral times within each view, perhaps in different orders. Although most views only do some of these things, they way in which they do them is highly dependant on the use case.

What tools are missing?

I think the majority of these issues can be boiled down to three particular problems in Django and its class based views.

More than one thing at once It's hard to compose the components of the class based views in different ways at present. You can't just add one form view and another together and get a view which handles both forms. In any case, what do you really want to achieve? Should both forms be valid or is it either-or? How do you take the basic functionality of a detail view, and apply it to the view of its children? Normally these situations require custom code, or some delicate patching of the component parts of the GCBVs to ensure they don't conflict.

Handling problems at any point in the cycle In a traditional functional view, you can simply place a return statement anywhere in the code and instantly drop out of your view. This is much harder in a class based view as the method you are in may not allow you to return a response. The simple example here is the situation where you have an unpublished object that only admins can view - you want to check the object (perhaps at the end of get_object()) and redirect away with a message if the user is not an admin. This problem in a way also applies to the login required issue, and other authorization issues traditionally handled by decorators.

Better JSON and AJAX support Like it or loathe it, Javascript is part of the modern web and we need to speak its language. Automatic handling of AJAX requests to form views, a JSON response class and AJAX loading components of the page are important features in a modern web framework, none of which are out-of-the-box tools from Django.

Can we fix it?

More than one thing at once It is probably not reasonable to refactor the existing views to allow arbitrary composition of functionality. For consistency, a number of the views use methods which do the same thing (get_object, get_queryset etc) which would conflict on different objects. The alternative is to make it easier to use more than one view in the same place - via a ViewCollection or ViewSet object. I'm not necessarily fond of this idea to bundle together views and return the response from one or another, rather to allow a particular request to combine multiple "views" together and do all of them. Often, one component may depend on the state of another and the order of execution may matter. The current existing generic views then become simple cases of these problems, where they only have one component.

Handling problems at any point in the cycle I feel that the correct way to handle these situations is with exceptions. And I don't mean the fictional Http302 class! I know it's considered bad to handle flow using exceptions, but I think in many of these cases we have exactly encountered an exception is the correct choice. I think that in particular PermissionDenied and DoesNotExist should be raised in some methods and passed off to specific handlers in the dispatch(). These exception classes describe the actual problem which has been encountered whilst trying to process the request, rather than the way in which that problem will be resolved. ValidationError may also be relevant in some cases.

Better JSON and AJAX support I think that the GCBVs should all have some form of ajax support, especially the form based ones. This should be easily turned on or off. This ajax support should ideally also support the two major ways in which ajax is handled - either by text/html requests or application/json requests. The hardest problem here is the lack of an excellent serialization framework for Django, and the reluctance to condone any particular javascript frameworks. This may involve some extensions to the Form class to allow things like serializing the ErrorDict into json.

So, where's the patch?

Well, I haven't written it yet. I have some ideas of how I can implement some of these concepts to make it easier to make it easier to provide these new general cases. I just want to make sure I'm trying to solve the correct problem before I start working on it. All feedback is very welcome - drop me an email at marc.tamlyn@gmail.com, tweet @mjtamlyn or comment on the gist.

@ptone
Copy link

ptone commented Jul 26, 2013

Some interesting thoughts.

First, I've always been annoyed by the retention of "generic" as it applies to class based views. While the function based generic views were take it or leave it all or nothing things, the CBV classes are much more of a toolkit, and not something you have to only use generically. (I just gave up on some of this view in #16963).

more than one thing at once

Instead of something like a ViewCollection, what about chaining views? This allows each view to be focused on the bit (ie form) it is worried about. You still don't answer the key problems you bring up in your intro of this topic - their is overall logic that can't be easily replaced through pure composition of parts.

handling problems

I agree the exceptions feel OK here, but they would be ripe for abuse. Still, if you think of dispatch as the thing that "oversees" the CBV, than handling an exception makes sense.

I do think Django should consider providing an AJAX battery - and maybe someday a websocket "realtime" battery, but I honestly don't know yet what they look like at the implementation level (I've don't quite a bit of thinking about the realtime side of things).

One of the challenges you don't touch on is how to improve what is already there, vs a clean start. Where are you leaning with these ideas? the 'version 2' moniker seems to imply fresh start, but CBVs are still relatively new.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment