mirror of
				https://github.com/django/django.git
				synced 2025-11-04 05:35:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			435 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
.. _Generic views:
 | 
						|
 | 
						|
==================================
 | 
						|
Built-in Class-based 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:
 | 
						|
 | 
						|
* 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.
 | 
						|
 | 
						|
* 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.
 | 
						|
 | 
						|
 | 
						|
Extending generic views
 | 
						|
=======================
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
That said, generic views will have a limit. If you find you're struggling to
 | 
						|
implement your view as a subclass of a generic view, then you may find it more
 | 
						|
effective to write just the code you need, using your own class-based or
 | 
						|
functional views.
 | 
						|
 | 
						|
More examples of generic views are available in some third party applications,
 | 
						|
or you could write your own as needed.
 | 
						|
 | 
						|
 | 
						|
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 start by looking at some examples of showing a list of objects or an
 | 
						|
individual object.
 | 
						|
 | 
						|
.. comment: link here to the other topic pages (form handling, date based, mixins)
 | 
						|
 | 
						|
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 __str__(self):              # __unicode__ on Python 2
 | 
						|
            return self.name
 | 
						|
 | 
						|
    class Author(models.Model):
 | 
						|
        salutation = models.CharField(max_length=10)
 | 
						|
        name = models.CharField(max_length=200)
 | 
						|
        email = models.EmailField()
 | 
						|
        headshot = models.ImageField(upload_to='author_headshots')
 | 
						|
 | 
						|
        def __str__(self):              # __unicode__ on Python 2
 | 
						|
            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()
 | 
						|
 | 
						|
Now we need to define a view::
 | 
						|
 | 
						|
    # views.py
 | 
						|
    from django.views.generic import ListView
 | 
						|
    from books.models import Publisher
 | 
						|
 | 
						|
    class PublisherList(ListView):
 | 
						|
        model = Publisher
 | 
						|
 | 
						|
Finally hook that view into your urls::
 | 
						|
 | 
						|
    # urls.py
 | 
						|
    from django.conf.urls import url
 | 
						|
    from books.views import PublisherList
 | 
						|
 | 
						|
    urlpatterns = [
 | 
						|
        url(r'^publishers/$', PublisherList.as_view()),
 | 
						|
    ]
 | 
						|
 | 
						|
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 adding a
 | 
						|
``template_name`` attribute to the 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`, a template location could be:
 | 
						|
    /path/to/project/books/templates/books/publisher_list.html
 | 
						|
 | 
						|
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:
 | 
						|
 | 
						|
.. code-block:: html+django
 | 
						|
 | 
						|
    {% 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 attributes set on the generic view. The
 | 
						|
:doc:`generic views reference</ref/class-based-views/index>` 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.
 | 
						|
 | 
						|
 | 
						|
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 lower cased version of the model class' name. This is
 | 
						|
provided in addition to the default ``object_list`` entry, but contains exactly
 | 
						|
the same data, i.e. ``publisher_list``.
 | 
						|
 | 
						|
If this 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::
 | 
						|
 | 
						|
    # views.py
 | 
						|
    from django.views.generic import ListView
 | 
						|
    from books.models import Publisher
 | 
						|
 | 
						|
    class PublisherList(ListView):
 | 
						|
        model = Publisher
 | 
						|
        context_object_name = 'my_favorite_publishers'
 | 
						|
 | 
						|
Providing a useful ``context_object_name`` is always a good idea. Your
 | 
						|
coworkers who design templates will thank you.
 | 
						|
 | 
						|
 | 
						|
.. _adding-extra-context:
 | 
						|
 | 
						|
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 how do we get additional information
 | 
						|
in that template?
 | 
						|
 | 
						|
The answer is to subclass :class:`~django.views.generic.detail.DetailView`
 | 
						|
and provide your own implementation of the ``get_context_data`` method.
 | 
						|
The default implementation simply adds the object being displayed to the
 | 
						|
template, but you can override it to send more::
 | 
						|
 | 
						|
    from django.views.generic import DetailView
 | 
						|
    from books.models import Publisher, Book
 | 
						|
 | 
						|
    class PublisherDetail(DetailView):
 | 
						|
 | 
						|
        model = Publisher
 | 
						|
 | 
						|
        def get_context_data(self, **kwargs):
 | 
						|
            # Call the base implementation first to get a context
 | 
						|
            context = super(PublisherDetail, self).get_context_data(**kwargs)
 | 
						|
            # Add in a QuerySet of all the books
 | 
						|
            context['book_list'] = Book.objects.all()
 | 
						|
            return context
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
    Generally, ``get_context_data`` will merge the context data of all parent
 | 
						|
    classes with those of the current class. To preserve this behavior in your
 | 
						|
    own classes where you want to alter the context, you should be sure to call
 | 
						|
    ``get_context_data`` on the super class. When no two classes try to define the
 | 
						|
    same key, this will give the expected results. However if any class
 | 
						|
    attempts to override a key after parent classes have set it (after the call
 | 
						|
    to super), any children of that class will also need to explicitly set it
 | 
						|
    after super if they want to be sure to override all parents. If you're
 | 
						|
    having trouble, review the method resolution order of your view.
 | 
						|
 | 
						|
.. _generic-views-list-subsets:
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
    class PublisherDetail(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:`~django.db.models.query.QuerySet` objects,
 | 
						|
and see the :doc:`class-based views reference </ref/class-based-views/index>`
 | 
						|
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::
 | 
						|
 | 
						|
    from django.views.generic import ListView
 | 
						|
    from books.models import Book
 | 
						|
 | 
						|
    class BookList(ListView):
 | 
						|
        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::
 | 
						|
 | 
						|
    from django.views.generic import ListView
 | 
						|
    from books.models import Book
 | 
						|
 | 
						|
    class AcmeBookList(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/index>` 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.list.MultipleObjectMixin.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::
 | 
						|
 | 
						|
    # urls.py
 | 
						|
    from django.conf.urls import url
 | 
						|
    from books.views import PublisherBookList
 | 
						|
 | 
						|
    urlpatterns = [
 | 
						|
        url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
 | 
						|
    ]
 | 
						|
 | 
						|
Next, we'll write the ``PublisherBookList`` view itself::
 | 
						|
 | 
						|
    # views.py
 | 
						|
    from django.shortcuts import get_object_or_404
 | 
						|
    from django.views.generic import ListView
 | 
						|
    from books.models import Book, Publisher
 | 
						|
 | 
						|
    class PublisherBookList(ListView):
 | 
						|
 | 
						|
        template_name = 'books/books_by_publisher.html'
 | 
						|
 | 
						|
        def get_queryset(self):
 | 
						|
            self.publisher = get_object_or_404(Publisher, name=self.args[0])
 | 
						|
            return Book.objects.filter(publisher=self.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::
 | 
						|
 | 
						|
        # ...
 | 
						|
 | 
						|
        def get_context_data(self, **kwargs):
 | 
						|
            # Call the base implementation first to get a context
 | 
						|
            context = super(PublisherBookList, self).get_context_data(**kwargs)
 | 
						|
            # Add in the publisher
 | 
						|
            context['publisher'] = self.publisher
 | 
						|
            return context
 | 
						|
 | 
						|
.. _generic-views-extra-work:
 | 
						|
 | 
						|
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`` model that we were
 | 
						|
using to keep track of the last time anybody looked at that author::
 | 
						|
 | 
						|
    # models.py
 | 
						|
    from django.db import models
 | 
						|
 | 
						|
    class Author(models.Model):
 | 
						|
        salutation = models.CharField(max_length=10)
 | 
						|
        name = models.CharField(max_length=200)
 | 
						|
        email = models.EmailField()
 | 
						|
        headshot = models.ImageField(upload_to='author_headshots')
 | 
						|
        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::
 | 
						|
 | 
						|
    from django.conf.urls import url
 | 
						|
    from books.views import AuthorDetailView
 | 
						|
 | 
						|
    urlpatterns = [
 | 
						|
        #...
 | 
						|
        url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
 | 
						|
    ]
 | 
						|
 | 
						|
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::
 | 
						|
 | 
						|
    from django.views.generic import DetailView
 | 
						|
    from django.utils import timezone
 | 
						|
    from books.models import Author
 | 
						|
 | 
						|
    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 = timezone.now()
 | 
						|
            object.save()
 | 
						|
            # Return the object
 | 
						|
            return object
 | 
						|
 | 
						|
.. 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 call the group something else, you can set ``pk_url_kwarg``
 | 
						|
    on the view. More details can be found in the reference for
 | 
						|
    :class:`~django.views.generic.detail.DetailView`
 |