82

I read today that Django 1.3 alpha is shipping, and the most touted new feature is the introduction of class-based views.
I've read the relevant documentation, but I find difficult to see the big advantage™ that I could get by using them, so I'm asking here for some help in understanding them.
Let's take an advanced example from the documentation.

urls.py

from books.views import PublisherBookListView

urlpatterns = patterns('',
    (r'^books/(\w+)/$', PublisherBookListView.as_view()),
)

views.py

from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

class PublisherBookListView(ListView):

    context_object_name = "book_list"
    template_name = "books/books_by_publisher.html",

    def get_queryset(self):
        self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
        return Book.objects.filter(publisher=self.publisher)

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(PublisherBookListView, self).get_context_data(**kwargs)
        # Add in the publisher
        context['publisher'] = self.publisher
        return context

And now let's compare it to a “plain-old-views” solution, made by myself in 5 minutes for this question (I apologize for any error you may find in it).

urls.py

urlpatterns = patterns('books.views',
    url(r'^books/(\w+)/$', 'publisher_books_list', name="publisher_books_list"),
)

views.py

from django.shortcuts import get_object_or_404
from books.models import Book, Publisher

def publisher_books_list(request, publisher_name):
    publisher = get_object_or_404(Publisher, name__iexact=publisher_name)
    book_list = Book.objects.filter(publisher=publisher)

    return render_to_response('books/books_by_publisher.html', {
        "book_list": book_list,
        "publisher": publisher,
    }, context_instance=RequestContext(request))

The second version to me looks:

  • Equivalent in functionality
  • A lot more readable (self.args[0]? awful!)
  • Shorter
  • Not less DRY-compliant

Is there something big I'm missing? Why should I use them? Are those on the documentation? If so then what would be the ideal use case? Are mixins that useful?

Thanks in advance to anybody who contributes!

P.S. for those who might wonder, I was never enthralled by generic views as well: as soon as I needed some advanced functionality, they became no shorter than regular views.

Agos
  • 18,542
  • 11
  • 56
  • 70
  • 4
    Yeah I'm not seeing the big advantage either. Would love to see a big answer on this. – M. Ryan Dec 06 '10 at 21:46
  • 1
    Completely agree. I'm especially disgusted by self.args[0] or self.kwargs['slug']. It's now also a lot harder to provide default values for url parameters, like this: def publisher_books_list(request, publisher_name='Herbert') – Kevin Renskers Mar 24 '11 at 11:36

5 Answers5

49

You can subclass a class and refine methods like get_context_data for specific cases, and leave the rest as-is. You can't do that with functions.

For instance, you might need to create a new view that does everything a previous one does, but you need to include extra variable in the context. Subclass the original view and override the get_context_data method.

Also, separating the steps needed to render the template into separate methods promotes clearer code - the less done in a method, the easier it is to understand. With regular view functions, it's all dumped into the one processing unit.

Evan Porter
  • 2,987
  • 3
  • 32
  • 44
  • 2
    Yeah, I can see this. It makes for an ease-of-override, and frequent mix-in, case when trying to decide if you should go RESTful, have a full site, or a mobile site. With this, that decision can be put off as long as possible while the functionality is derived. The Webware module in Pylons had this, and it was very useful. That said, class-based views have long been possible with Django by overriding the __call__() method. – Elf Sternberg Dec 07 '10 at 20:49
  • 9
    Accepting the answer since it provides a very good point... but still not feeling the need to use them, since I seldom have such problems to solve. Thanks! – Agos Dec 08 '10 at 21:51
17

If self.args[0] is bothering you, the alternative is:

urlpatterns = patterns('books.views',
    url(r'^books/(?P<slug>\w+)/$', 'publisher_books_list', name="publisher_books_list"),
)

Then you could use self.kwargs['slug'] instead, making it slightly more readable.

viam0Zah
  • 25,949
  • 8
  • 77
  • 100
Alex
  • 281
  • 2
  • 6
10

Your example function and class are not equal in features.

The class based version provide pagination for free and forbid the use of other HTTP verbs than GET.

If you want to add this to your function, it's going to be much longer.

But it is, indeed, more complicated.

Bite code
  • 578,959
  • 113
  • 301
  • 329
  • 2
    +1 for pointing the difference, but personally I think that [require_GET](https://docs.djangoproject.com/en/1.3/topics/http/decorators/#django.views.decorators.http.require_GET) and [django-pagination](https://github.com/ericflo/django-pagination/blob/master/docs/usage.txt) are trivial to use, concise, explicit etc. and I prefer them to cbvs at (almost :)) all times. – Tomasz Zieliński Sep 17 '12 at 20:57
4

This is the first I'm hearing of this -- and I like it.

The advantage I see here, honestly, is that it makes views more consistent with Django overall. Models are classes and I've always felt that views should be too. I know not everything is but views and models are the two heavily used types.

As for the technical advantage? Well, in Python everything is a class (or object?) -- so is there really a difference? Isn't it 99% syntactical sugar in the first place?

Frank V
  • 25,141
  • 34
  • 106
  • 144
  • I would say that the consistency allows for greater code reuse. They basically cut down on a lot of boilerplate, if your views conform to certain patterns. e.g a form based on a a model is extremely quick to generate with class based views. If it needs a couple of extra fields, it starts getting a bit trickier. If you need a form based on three models plus two extra fields, they probably are not going to save you much effort at all. – wobbily_col Mar 19 '15 at 14:20
1

One way to think about class based views, is that they are like a the Django admin with training wheels off and therefore a lot more flexible (but more difficult to understand).

For example the list-display in the admin is clearly based on the generic ListView. The simplest list view you would only define a model or queryset.

class MyExampleView(ListView);
    model = ExampleModel 

You will need to supply your own template, but it will basically be the same as the most basic ModelAdmin. The list_display attribute in the model admin will tell it what fields to display, whereas in the ListView you would do this in the template.

class SpeciesAdmin(admin.ModelAdmin):
    list_display = ['name']
admin.site.register(ExampleModel , ExampleModelAdmin)

With the admin you have a parameter

list_per_page = 100

which defines how many objects per page. List view has

paginate_by = 100

which achieves the same thing. Likewise if you look into customising the admin heavily, you will see a lot of overlap.

This site here should give you a better idea of what they do as well.

http://ccbv.co.uk/

wobbily_col
  • 11,390
  • 12
  • 62
  • 86