123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- =========================
- 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()
- def __unicode__(self):
- return self.name
- class Meta:
- ordering = ["-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 Django 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
- from django.views.generic import TemplateView
- urlpatterns = patterns('',
- (r'^about/',login_required(TemplateView.as_view(template_name="secret.html"))),
- )
- 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.
|