I don’t like Django class-based generic views.


Django had function-based generic views through version 1.25. They were replaced in version 1.3 by class-based generic views.

Some caveats: I’m not the sharpest knife in the drawer. I’m not FSM’s gift to web development. I have a lot of experience with the function-based generic views, and little experience with the class-based ones. (Because they’re new. Duh.)

From yesterday’s experience, the new generic views use a very powerful but excessively complicated abstraction. I can only symptomatically describe the problem and I don’t have a good answer, but this is my blog so I’m going to bitch about it. If you don’t agree then move along, these aren’t the droids you’re looking for.


My task was simple: Code a, “display detail about an object,” page. The object is a db table row. That’s all.

After two hours, I wanted to stab my eyes with live yellow jackets.

I wanted this page accessible to only logged-in users, so I needed to override as_view() and use @login_required(). I also needed one of our decorators to verify that the user owned the db row. And, of course, I had to pass the db row object (from a Foobar.objects.get(yadda yadda yadda) call) to the as_view(). And use url-reversing in the urlconf, which turns out to have multiple bugs with parameterized regex’s, but I can’t blame that on class-based views.

Subclass the class, override the methods, specify the template. Easy! Well, no. I nearly got it working after three hours (some of that time was spent hunting the url-reverse bug). I then gave up and wrote a dead-simple view to display the template.

The kicker: The dead-simple view took fewer lines of code than the class-based generic view.

Something’s wrong here. This shouldn’t result in more lines of code. I love DRY as much as the next Pythonista, but I shouldn’t need an organic chemistry degree to figure out how to display a simple template.

I want to use generic views to quickly and simply display simple pages. I can’t do that anymore.

I don’t need über configuration flexibility in my generic views; I need simplicity. A couple of other folks have felt a similar unease. Agos asked about this on Stack Overflow. Luke Plant wrote about it on his blog.

17 comments
  1. Dan said:

    I tried this exact same exercise yesterday – class based generic detail view.

    After about an hour, I back out my changes and left things as is. (Luckily, the old style functional views are still available in v1.3). I’m hoping that someone in the know will soon post a tutorial with examples on class based views and how the generics are implemented.

  2. Torsten said:

    Hm, I understand your frustration about it, but I feel you misunderstand the usage of class-based generic views.

    You got a very special case here and if you try to fit this into a generic view it is not necessarily the right approach. For me a generic view is a view which does one thing and in case of class based views it has a little room for simple extensions (nothing to complicated). If you keep this in mind you will be very happy with the class-based generic views over the function-based ones and your code will definitely be shorter.

    For very specific tasks like in your case use a normal function-based view (they are not deprecated, the generic ones are). If you have to make your view extend-able (lets say for a reusable app), you can write your own class-based view with django.views.generic.base.View as their base class. Of course this will be more code and more work, but in the end you made it easier for other people to use.

    Of course this is very subjective coming from personal experience.

  3. Cody Somerville said:

    I don’t agree with some of the design decisions made regarding class-based generic views. However, anything you could do with the old function-based generic views you can do with the new class-based generic views pretty much as easily. With function-based views, the only way you could modify their behaviour was by wrapping your call to the function-based generic view in a custom function-based view, passing arguments to the function-based views (as supported), or wrapping the function-based view in decorators. With class-based views, you can do all that plus use inheritance. So in your case, it sounds like you should have just wrapped the view returned by the object_detail class-based generic view almost in the same fashion you would have wrapped the function-based generic view. See https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating-class-based-views.

  4. Josh Smeaton said:

    I’ll agree with you on parts of your article. Class-based views are a lot more complicated, especially considering the code paths through multiple mixins. Knowing which methods to override and which methods are available to override is nearly impossible without inspecting the django source code regularly.

    A few things though. Decorators can be used directly within the URLConf. So you can wrap the call to View.as_view() like this: login_required(View.as_view()). This is the easiest way, but probably not the best way. Your view may expect a logged in user, and then the view has a dependency on the URL conf doing the correct thing. But you can create a LoggedInMixin that will ensure that a user is logged in, and it becomes totally reusable.

    I like the declarative approach to defining class based views, even if the alternative approach is sometimes shorter. Different views look very similar, and are easy to navigate once they’re written. Form views are the biggest win. No confusion about how to render or process a form. You override the is_valid and go from there.

    If you’re doing standard generic stuff, there is very little work that needs to be done. Detail, List, Form.. mostly things you could do before using the old generic views, but somehow now, to me, they feel more natural to use even if you need custom code that does more than just return a list of objects.

    • babeleGrande said:

      Well, i’ve been using class based views for almost as long as they exist in trunk, and i too have a feeling that they’re complicated, even though powerful, and quite elegantly implemented — mainly due to the mixins. I believe what is lacking is the documentation regarding the generic views — it’s a replication of the structure of the code — ie. ProcessForm mixin extends Form mixin (or whatever), so click here to read the provided Form mixin methods. It’s not easy to track which methods exist on each class, you go back and forth all the time. Maybe aggregating all methods together for each class would help matters a lot.

      • babeleGrande said:

        Oh, sorry, my previous comment wasn’t intended as a reply to you Josh (even though i agree with your comments), but to the OP.

  5. Jonathan Buchanan said:

    You haven’t given enough detail for someone to comment with an example of exactly what you’d need to do to implement your particular requirements, but assuming you were using the generic DetailView ( https://docs.djangoproject.com/en/dev/ref/class-based-views/#detailview ), your description did indicate some issues.

    You don’t need to override as_view to decorate a generic view – the docs show how you would do it in either the URLConf or by overriding and decorating dispatch: https://docs.djangoproject.com/en/1.3/topics/class-based-views/#decorating-class-based-views

    You shouldn’t need to pass the object in to as_view – if you’re looking up by pk or slug, the view can do that for you (with a pk named URL parameter or a slug) or you can override get_object and do the yadda yadda yadda filtering using self.args and self.kwargs to access URL parameters.

    The new class-based generic views are definitely harder to initially get a handle on (mixins, subclassing, overriding methods, setting class attributes, shadowing class attributes with instance attributes via as_view… compared to passing in configuration arguments to the old function-based views), but if you look at the docs for each of the mixins the class-based generic view you want to make use of includes, you can get an idea of how the old function-based logic has been split up for reuse, with the default behaviour making use of the class attributes you can configure.

    It’s worth fighting through that initial pain to get a feel for how they’re put together, as defining view logic which can be fully customised by subclassing and overriding methods is extremely powerful and flexible for creating your own reusable views.

  6. I really like class based generic views. I probably use them for about 2% of all my views. The reason is that I feel you only need them when you have one view that could serve 2 or more purposes with the same code. I think I’d end up hating them if they were the only type of view and I had to write all my views with them. You do end up writing more code which is why the standard function view is used more often. Only write more code if there is an advantage to doing so at the end.

    By the way, you can decorate the get & post methods in a generic view using ‘django.utils.decorators.method_decorator’ like this:

    @method_decorator(login_required)
    def get(self, request):

  7. Sigurd Gartmann said:

    It is not straight forward when you want to control access depending on the object. Jonathan has explained how he is doing similar things, and I ended up doing it like him (without knowing about his comment).

    In a child of DetailView, I set the queryset variable to filter what objects the user can see (I have an extra method in my manager, called “for_user(user_obj)”.

    For the UpdateView child, I ended up (after a few hours of struggling) overriding the get_object method, which made it work for both GET and POST requests. Before that I had tried making my own decorator (which was not working well when you need to compare the user to the permissions of the object).

    I use a Http403 middleware extension to show “access denied” messages (as detailed as you need). I also tried doing permission checks like this on the form object, but that one did not know of the user, I had to do it in the view object, but it was possible.

    If it was possible to do form validation with both the object and the request.user at the same time, it would have made it easier.

    The migration guide was necessary all the time, and could have been written a bit better (for example list all possible variables/methods you can set/override, without you having to click around that much).

  8. I use class Based Views for everything except super simple views. You didn’t give a ton of details in your post, but just for fun I went ahead and implemented something that I believe should have done what you wanted it to do…

    http://paste.pocoo.org/show/455800/

    Though honestly. If you are using a decorator to check that the user owns a particular view, then in the CBV world, that would probably be better off as a mixin that override get_object and threw an authorized (or 404).

  9. Dan said:

    The way I see it, generic views are valuable for providing view code that can be used across multiple types model objects. If you’re writing a really big application and it always has the same type of list display, it makes sense to put the time into either using django’s built in list display generic views, or writing your own generic view that you can use across your application(s).

    That generalization will likely come at a cost ( a few more lines of code) – but then in the future you can sub-class that and not write that code again.

    If you’re doing something very specific, then it makes more sense to just implement a view – and in many cases this is the right approach.

    Anyways, thats my take on it, but I’m still trying to figure the whole thing out myself.

  10. … “The dead-simple view took fewer lines of code than the class-based generic view.”

    If your class-based (generic) view is longer than your function view… you’re doing it wrong. I can’t seem to grasp how you couldn’t achieve what you needed (from your description) in < 5 lines of code. Please share code as an example.

    • John said:

      I don’t have the time to dredge up the discarded code. There are multiple examples on the web of developers saying that class-based generic views need more lines of code. One example is yesterday’s softwaremaniacs.org article, which you commented on after me. The code reuse provided by subclassing and method overrides can also be provided by function (callables) parameters.

      • Sure you can hack an inheritance by chaining functions and a slew of arguments but I can’t see how that’s any better. It just makes maintaining, updating, and reusing views a pain. I have a large application with almost ~40 complex views (auth check, permission checking, forms, rest, etc. in each view) with 406 LOC.

        I will admit that the documentation is lacking on class-based views and that I’ve religiously bisected the source to understand them in all their glory. Everyone i’ve seen bashing class-based (generic) views have been doing something fundamentally wrong to cause their frustration. On that note, i’m willing to help anyone understand them better.

  11. Stu said:

    Been trying to use django formwizard as my first experience of a CBV … here are the things I tried to do:
    Pass parameters from the URL.
    Change the steps as you go.
    Ommit certain steps depending on the entry point.

    It’s HORRIBLE I *hate* it, am never going to use formwizward again, all of these things take monumental effort and overriding so many methods!

    I hope other CBVs are not like this.

  12. John said:

    Luke Plant wrote another awesome post about class-based views, just after I wrote this one. Check it out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 9,342 other followers

%d bloggers like this: