| ========================= |
| Class-based generic views |
| ========================= |
| |
| .. versionadded:: 1.3 |
| |
| .. note:: |
| Prior to Django 1.3, generic views were implemented as functions. The |
| function-based implementation has been deprecated in favor of the |
| class-based approach described here. |
| |
| For details on the previous generic views implementation, |
| see the :doc:`topic guide </topics/generic-views>` and |
| :doc:`detailed reference </ref/generic-views>`. |
| |
| Writing Web applications can be monotonous, because we repeat certain patterns |
| again and again. Django tries to take away some of that monotony at the model |
| and template layers, but Web developers also experience this boredom at the view |
| level. |
| |
| Django's *generic views* were developed to ease that pain. They take certain |
| common idioms and patterns found in view development and abstract them so that |
| you can quickly write common views of data without having to write too much |
| code. |
| |
| We can recognize certain common tasks, like displaying a list of objects, and |
| write code that displays a list of *any* object. Then the model in question can |
| be passed as an extra argument to the URLconf. |
| |
| Django ships with generic views to do the following: |
| |
| * Perform common "simple" tasks: redirect to a different page and |
| render a given template. |
| |
| * Display list and detail pages for a single object. If we were creating an |
| application to manage conferences then a ``TalkListView`` and a |
| ``RegisteredUserListView`` would be examples of list views. A single |
| talk page is an example of what we call a "detail" view. |
| |
| * Present date-based objects in year/month/day archive pages, |
| associated detail, and "latest" pages. |
| `The Django Weblog <http://www.djangoproject.com/weblog/>`_'s |
| year, month, and day archives are built with these, as would be a typical |
| newspaper's archives. |
| |
| * Allow users to create, update, and delete objects -- with or |
| without authorization. |
| |
| Taken together, these views provide easy interfaces to perform the most common |
| tasks developers encounter. |
| |
| |
| Simple usage |
| ============ |
| |
| Class-based generic views (and any class-based views that inherit from |
| the base classes Django provides) can be configured in two |
| ways: subclassing, or passing in arguments directly in the URLconf. |
| |
| When you subclass a class-based view, you can override attributes |
| (such as the ``template_name``) or methods (such as ``get_context_data``) |
| in your subclass to provide new values or methods. Consider, for example, |
| a view that just displays one template, ``about.html``. Django has a |
| generic view to do this - :class:`~django.views.generic.base.TemplateView` - |
| so we can just subclass it, and override the template name:: |
| |
| # some_app/views.py |
| from django.views.generic import TemplateView |
| |
| class AboutView(TemplateView): |
| template_name = "about.html" |
| |
| Then, we just need to add this new view into our URLconf. As the class-based |
| views themselves are classes, we point the URL to the ``as_view`` class method |
| instead, which is the entry point for class-based views:: |
| |
| # urls.py |
| from django.conf.urls.defaults import * |
| from some_app.views import AboutView |
| |
| urlpatterns = patterns('', |
| (r'^about/', AboutView.as_view()), |
| ) |
| |
| Alternatively, if you're only changing a few simple attributes on a |
| class-based view, you can simply pass the new attributes into the ``as_view`` |
| method call itself:: |
| |
| from django.conf.urls.defaults import * |
| from django.views.generic import TemplateView |
| |
| urlpatterns = patterns('', |
| (r'^about/', TemplateView.as_view(template_name="about.html")), |
| ) |
| |
| A similar overriding pattern can be used for the ``url`` attribute on |
| :class:`~django.views.generic.base.RedirectView`, another simple |
| generic view. |
| |
| |
| Generic views of objects |
| ======================== |
| |
| :class:`~django.views.generic.base.TemplateView` certainly is useful, |
| but Django's generic views really shine when it comes to presenting |
| views of your database content. Because it's such a common task, |
| Django comes with a handful of built-in generic views that make |
| generating list and detail views of objects incredibly easy. |
| |
| Let's take a look at one of these generic views: the "object list" view. We'll |
| be using these models:: |
| |
| # models.py |
| from django.db import models |
| |
| class Publisher(models.Model): |
| name = models.CharField(max_length=30) |
| address = models.CharField(max_length=50) |
| city = models.CharField(max_length=60) |
| state_province = models.CharField(max_length=30) |
| country = models.CharField(max_length=50) |
| website = models.URLField() |
| |
| class Meta: |
| ordering = ["-name"] |
| |
| def __unicode__(self): |
| return self.name |
| |
| class Book(models.Model): |
| title = models.CharField(max_length=100) |
| authors = models.ManyToManyField('Author') |
| publisher = models.ForeignKey(Publisher) |
| publication_date = models.DateField() |
| |
| To build a list page of all publishers, we'd use a URLconf along these lines:: |
| |
| from django.conf.urls.defaults import * |
| from django.views.generic import ListView |
| from books.models import Publisher |
| |
| urlpatterns = patterns('', |
| (r'^publishers/$', ListView.as_view( |
| model=Publisher, |
| )), |
| ) |
| |
| That's all the Python code we need to write. We still need to write a template, |
| however. We could explicitly tell the view which template to use |
| by including a ``template_name`` key in the arguments to as_view, but in |
| the absence of an explicit template Django will infer one from the object's |
| name. In this case, the inferred template will be |
| ``"books/publisher_list.html"`` -- the "books" part comes from the name of the |
| app that defines the model, while the "publisher" bit is just the lowercased |
| version of the model's name. |
| |
| .. note:: |
| Thus, when (for example) the :class:`django.template.loaders.app_directories.Loader` |
| template loader is enabled in :setting:`TEMPLATE_LOADERS`, the template |
| location would be:: |
| |
| /path/to/project/books/templates/books/publisher_list.html |
| |
| .. highlightlang:: html+django |
| |
| This template will be rendered against a context containing a variable called |
| ``object_list`` that contains all the publisher objects. A very simple template |
| might look like the following:: |
| |
| {% extends "base.html" %} |
| |
| {% block content %} |
| <h2>Publishers</h2> |
| <ul> |
| {% for publisher in object_list %} |
| <li>{{ publisher.name }}</li> |
| {% endfor %} |
| </ul> |
| {% endblock %} |
| |
| That's really all there is to it. All the cool features of generic views come |
| from changing the "info" dictionary passed to the generic view. The |
| :doc:`generic views reference</ref/class-based-views>` documents all the generic |
| views and their options in detail; the rest of this document will consider |
| some of the common ways you might customize and extend generic views. |
| |
| |
| Extending generic views |
| ======================= |
| |
| .. highlightlang:: python |
| |
| There's no question that using generic views can speed up development |
| substantially. In most projects, however, there comes a moment when the |
| generic views no longer suffice. Indeed, the most common question asked by new |
| Django developers is how to make generic views handle a wider array of |
| situations. |
| |
| This is one of the reasons generic views were redesigned for the 1.3 release - |
| previously, they were just view functions with a bewildering array of options; |
| now, rather than passing in a large amount of configuration in the URLconf, |
| the recommended way to extend generic views is to subclass them, and override |
| their attributes or methods. |
| |
| |
| Making "friendly" template contexts |
| ----------------------------------- |
| |
| You might have noticed that our sample publisher list template stores |
| all the publishers in a variable named ``object_list``. While this |
| works just fine, it isn't all that "friendly" to template authors: |
| they have to "just know" that they're dealing with publishers here. |
| |
| Well, if you're dealing with a model object, this is already done for |
| you. When you are dealing with an object or queryset, Django is able |
| to populate the context using the verbose name (or the plural verbose |
| name, in the case of a list of objects) of the object being displayed. |
| This is provided in addition to the default ``object_list`` entry, but |
| contains exactly the same data. |
| |
| If the verbose name (or plural verbose name) still isn't a good match, |
| you can manually set the name of the context variable. The |
| ``context_object_name`` attribute on a generic view specifies the |
| context variable to use. In this example, we'll override it in the |
| URLconf, since it's a simple change: |
| |
| .. parsed-literal:: |
| |
| urlpatterns = patterns('', |
| (r'^publishers/$', ListView.as_view( |
| model=Publisher, |
| **context_object_name="publisher_list",** |
| )), |
| ) |
| |
| Providing a useful ``context_object_name`` is always a good idea. Your |
| coworkers who design templates will thank you. |
| |
| |
| Adding extra context |
| -------------------- |
| |
| Often you simply need to present some extra information beyond that |
| provided by the generic view. For example, think of showing a list of |
| all the books on each publisher detail page. The |
| :class:`~django.views.generic.detail.DetailView` generic view provides |
| the publisher to the context, but it seems there's no way to get |
| additional information in that template. |
| |
| However, there is; you can subclass |
| :class:`~django.views.generic.detail.DetailView` and provide your own |
| implementation of the ``get_context_data`` method. The default |
| implementation of this that comes with |
| :class:`~django.views.generic.detail.DetailView` simply adds in the |
| object being displayed to the template, but you can override it to show |
| more:: |
| |
| from django.views.generic import DetailView |
| from books.models import Publisher, Book |
| |
| class PublisherDetailView(DetailView): |
| |
| context_object_name = "publisher" |
| model = Publisher |
| |
| def get_context_data(self, **kwargs): |
| # Call the base implementation first to get a context |
| context = super(PublisherDetailView, self).get_context_data(**kwargs) |
| # Add in a QuerySet of all the books |
| context['book_list'] = Book.objects.all() |
| return context |
| |
| |
| Viewing subsets of objects |
| -------------------------- |
| |
| Now let's take a closer look at the ``model`` argument we've been |
| using all along. The ``model`` argument, which specifies the database |
| model that the view will operate upon, is available on all the |
| generic views that operate on a single object or a collection of |
| objects. However, the ``model`` argument is not the only way to |
| specify the objects that the view will operate upon -- you can also |
| specify the list of objects using the ``queryset`` argument:: |
| |
| from django.views.generic import DetailView |
| from books.models import Publisher, Book |
| |
| class PublisherDetailView(DetailView): |
| |
| context_object_name = "publisher" |
| queryset = Publisher.objects.all() |
| |
| Specifying ``model = Publisher`` is really just shorthand for saying |
| ``queryset = Publisher.objects.all()``. However, by using ``queryset`` |
| to define a filtered list of objects you can be more specific about the |
| objects that will be visible in the view (see :doc:`/topics/db/queries` |
| for more information about :class:`QuerySet` objects, and see the |
| :doc:`class-based views reference </ref/class-based-views>` for the complete |
| details). |
| |
| To pick a simple example, we might want to order a list of books by |
| publication date, with the most recent first:: |
| |
| urlpatterns = patterns('', |
| (r'^publishers/$', ListView.as_view( |
| queryset=Publisher.objects.all(), |
| context_object_name="publisher_list", |
| )), |
| (r'^books/$', ListView.as_view( |
| queryset=Book.objects.order_by("-publication_date"), |
| context_object_name="book_list", |
| )), |
| ) |
| |
| |
| That's a pretty simple example, but it illustrates the idea nicely. Of course, |
| you'll usually want to do more than just reorder objects. If you want to |
| present a list of books by a particular publisher, you can use the same |
| technique (here, illustrated using subclassing rather than by passing arguments |
| in the URLconf):: |
| |
| from django.views.generic import ListView |
| from books.models import Book |
| |
| class AcmeBookListView(ListView): |
| |
| context_object_name = "book_list" |
| queryset = Book.objects.filter(publisher__name="Acme Publishing") |
| template_name = "books/acme_list.html" |
| |
| Notice that along with a filtered ``queryset``, we're also using a custom |
| template name. If we didn't, the generic view would use the same template as the |
| "vanilla" object list, which might not be what we want. |
| |
| Also notice that this isn't a very elegant way of doing publisher-specific |
| books. If we want to add another publisher page, we'd need another handful of |
| lines in the URLconf, and more than a few publishers would get unreasonable. |
| We'll deal with this problem in the next section. |
| |
| .. note:: |
| |
| If you get a 404 when requesting ``/books/acme/``, check to ensure you |
| actually have a Publisher with the name 'ACME Publishing'. Generic |
| views have an ``allow_empty`` parameter for this case. See the |
| :doc:`class-based-views reference</ref/class-based-views>` for more details. |
| |
| |
| Dynamic filtering |
| ----------------- |
| |
| Another common need is to filter down the objects given in a list page by some |
| key in the URL. Earlier we hard-coded the publisher's name in the URLconf, but |
| what if we wanted to write a view that displayed all the books by some arbitrary |
| publisher? |
| |
| Handily, the ``ListView`` has a |
| :meth:`~django.views.generic.detail.ListView.get_queryset` method we can |
| override. Previously, it has just been returning the value of the ``queryset`` |
| attribute, but now we can add more logic. |
| |
| The key part to making this work is that when class-based views are called, |
| various useful things are stored on ``self``; as well as the request |
| (``self.request``) this includes the positional (``self.args``) and name-based |
| (``self.kwargs``) arguments captured according to the URLconf. |
| |
| Here, we have a URLconf with a single captured group:: |
| |
| from books.views import PublisherBookListView |
| |
| urlpatterns = patterns('', |
| (r'^books/(\w+)/$', PublisherBookListView.as_view()), |
| ) |
| |
| Next, we'll write the ``PublisherBookListView`` view itself:: |
| |
| 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): |
| publisher = get_object_or_404(Publisher, name__iexact=self.args[0]) |
| return Book.objects.filter(publisher=publisher) |
| |
| As you can see, it's quite easy to add more logic to the queryset selection; |
| if we wanted, we could use ``self.request.user`` to filter using the current |
| user, or other more complex logic. |
| |
| We can also add the publisher into the context at the same time, so we can |
| use it in the template:: |
| |
| 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 |
| |
| Performing extra work |
| --------------------- |
| |
| The last common pattern we'll look at involves doing some extra work before |
| or after calling the generic view. |
| |
| Imagine we had a ``last_accessed`` field on our ``Author`` object that we were |
| using to keep track of the last time anybody looked at that author:: |
| |
| # models.py |
| |
| class Author(models.Model): |
| salutation = models.CharField(max_length=10) |
| first_name = models.CharField(max_length=30) |
| last_name = models.CharField(max_length=40) |
| email = models.EmailField() |
| headshot = models.ImageField(upload_to='/tmp') |
| last_accessed = models.DateTimeField() |
| |
| The generic ``DetailView`` class, of course, wouldn't know anything about this |
| field, but once again we could easily write a custom view to keep that field |
| updated. |
| |
| First, we'd need to add an author detail bit in the URLconf to point to a |
| custom view: |
| |
| .. parsed-literal:: |
| |
| from books.views import AuthorDetailView |
| |
| urlpatterns = patterns('', |
| #... |
| **(r'^authors/(?P<pk>\\d+)/$', AuthorDetailView.as_view()),** |
| ) |
| |
| Then we'd write our new view -- ``get_object`` is the method that retrieves the |
| object -- so we simply override it and wrap the call:: |
| |
| import datetime |
| from books.models import Author |
| from django.views.generic import DetailView |
| from django.shortcuts import get_object_or_404 |
| |
| class AuthorDetailView(DetailView): |
| |
| queryset = Author.objects.all() |
| |
| def get_object(self): |
| # Call the superclass |
| object = super(AuthorDetailView, self).get_object() |
| # Record the last accessed date |
| object.last_accessed = datetime.datetime.now() |
| object.save() |
| # Return the object |
| return object |
| |
| .. note:: |
| |
| This code won't actually work unless you create a |
| ``books/author_detail.html`` template. |
| |
| .. note:: |
| |
| The URLconf here uses the named group ``pk`` - this name is the default |
| name that ``DetailView`` uses to find the value of the primary key used to |
| filter the queryset. |
| |
| If you want to change it, you'll need to do your own ``get()`` call |
| on ``self.queryset`` using the new named parameter from ``self.kwargs``. |
| |
| More than just HTML |
| ------------------- |
| |
| So far, we've been focusing on rendering templates to generate |
| responses. However, that's not all generic views can do. |
| |
| Each generic view is composed out of a series of mixins, and each |
| mixin contributes a little piece of the entire view. Some of these |
| mixins -- such as |
| :class:`~django.views.generic.base.TemplateResponseMixin` -- are |
| specifically designed for rendering content to an HTML response using a |
| template. However, you can write your own mixins that perform |
| different rendering behavior. |
| |
| For example, a simple JSON mixin might look something like this:: |
| |
| from django import http |
| from django.utils import simplejson as json |
| |
| class JSONResponseMixin(object): |
| def render_to_response(self, context): |
| "Returns a JSON response containing 'context' as payload" |
| return self.get_json_response(self.convert_context_to_json(context)) |
| |
| def get_json_response(self, content, **httpresponse_kwargs): |
| "Construct an `HttpResponse` object." |
| return http.HttpResponse(content, |
| content_type='application/json', |
| **httpresponse_kwargs) |
| |
| def convert_context_to_json(self, context): |
| "Convert the context dictionary into a JSON object" |
| # Note: This is *EXTREMELY* naive; in reality, you'll need |
| # to do much more complex handling to ensure that arbitrary |
| # objects -- such as Django model instances or querysets |
| # -- can be serialized as JSON. |
| return json.dumps(context) |
| |
| Then, you could build a JSON-returning |
| :class:`~django.views.generic.detail.DetailView` by mixing your |
| :class:`JSONResponseMixin` with the |
| :class:`~django.views.generic.detail.BaseDetailView` -- (the |
| :class:`~django.views.generic.detail.DetailView` before template |
| rendering behavior has been mixed in):: |
| |
| class JSONDetailView(JSONResponseMixin, BaseDetailView): |
| pass |
| |
| This view can then be deployed in the same way as any other |
| :class:`~django.views.generic.detail.DetailView`, with exactly the |
| same behavior -- except for the format of the response. |
| |
| If you want to be really adventurous, you could even mix a |
| :class:`~django.views.generic.detail.DetailView` subclass that is able |
| to return *both* HTML and JSON content, depending on some property of |
| the HTTP request, such as a query argument or a HTTP header. Just mix |
| in both the :class:`JSONResponseMixin` and a |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`, |
| and override the implementation of :func:`render_to_response()` to defer |
| to the appropriate subclass depending on the type of response that the user |
| requested:: |
| |
| class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): |
| def render_to_response(self, context): |
| # Look for a 'format=json' GET argument |
| if self.request.GET.get('format','html') == 'json': |
| return JSONResponseMixin.render_to_response(self, context) |
| else: |
| return SingleObjectTemplateResponseMixin.render_to_response(self, context) |
| |
| Because of the way that Python resolves method overloading, the local |
| :func:`render_to_response()` implementation will override the |
| versions provided by :class:`JSONResponseMixin` and |
| :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. |
| |
| Decorating class-based views |
| ============================ |
| |
| .. highlightlang:: python |
| |
| The extension of class-based views isn't limited to using mixins. You |
| can use also use decorators. |
| |
| Decorating in URLconf |
| --------------------- |
| |
| The simplest way of decorating class-based views is to decorate the |
| result of the :meth:`~django.views.generic.base.View.as_view` method. |
| The easiest place to do this is in the URLconf where you deploy your |
| view:: |
| |
| from django.contrib.auth.decorators import login_required, permission_required |
| from django.views.generic import TemplateView |
| |
| from .views import VoteView |
| |
| urlpatterns = patterns('', |
| (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), |
| (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), |
| ) |
| |
| This approach applies the decorator on a per-instance basis. If you |
| want every instance of a view to be decorated, you need to take a |
| different approach. |
| |
| Decorating the class |
| -------------------- |
| |
| To decorate every instance of a class-based view, you need to decorate |
| the class definition itself. To do this you apply the decorator to the |
| :meth:`~django.views.generic.base.View.dispatch` method of the class. |
| |
| A method on a class isn't quite the same as a standalone function, so |
| you can't just apply a function decorator to the method -- you need to |
| transform it into a method decorator first. The ``method_decorator`` |
| decorator transforms a function decorator into a method decorator so |
| that it can be used on an instance method. For example:: |
| |
| from django.contrib.auth.decorators import login_required |
| from django.utils.decorators import method_decorator |
| from django.views.generic import TemplateView |
| |
| class ProtectedView(TemplateView): |
| template_name = 'secret.html' |
| |
| @method_decorator(login_required) |
| def dispatch(self, *args, **kwargs): |
| return super(ProtectedView, self).dispatch(*args, **kwargs) |
| |
| In this example, every instance of ``ProtectedView`` will have |
| login protection. |
| |
| .. note:: |
| |
| ``method_decorator`` passes ``*args`` and ``**kwargs`` |
| as parameters to the decorated method on the class. If your method |
| does not accept a compatible set of parameters it will raise a |
| ``TypeError`` exception. |