123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- ==================================
- 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 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 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 to help generate list and detail views of objects.
- Let's start by looking at some examples of showing a list of objects or an
- individual object.
- 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):
- 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):
- return self.name
- class Book(models.Model):
- title = models.CharField(max_length=100)
- authors = models.ManyToManyField("Author")
- publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
- 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 PublisherListView(ListView):
- model = Publisher
- Finally hook that view into your urls::
- # urls.py
- from django.urls import path
- from books.views import PublisherListView
- urlpatterns = [
- path("publishers/", PublisherListView.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 the lowercased version of the model's name.
- .. note::
- Thus, when (for example) the ``APP_DIRS`` option of a ``DjangoTemplates``
- backend is set to True in :setting:`TEMPLATES`, 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 template might look
- like this:
- .. 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 lowercased 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 PublisherListView(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 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 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 Book, Publisher
- class PublisherDetailView(DetailView):
- model = Publisher
- def get_context_data(self, **kwargs):
- # Call the base implementation first to get a context
- context = super().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.
- Another consideration is that the context data from class-based generic
- views will override data provided by context processors; see
- :meth:`~django.views.generic.detail.SingleObjectMixin.get_context_data` for
- an example.
- .. _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 PublisherDetailView(DetailView):
- context_object_name = "publisher"
- queryset = Publisher.objects.all()
- Specifying ``model = Publisher`` is 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 an 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 BookListView(ListView):
- queryset = Book.objects.order_by("-publication_date")
- context_object_name = "book_list"
- That's a pretty minimal example, but it illustrates the idea nicely. 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 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/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. By default, it returns the value of the ``queryset`` attribute,
- but we can use it to 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.urls import path
- from books.views import PublisherBookListView
- urlpatterns = [
- path("books/<publisher>/", PublisherBookListView.as_view()),
- ]
- Next, we'll write the ``PublisherBookListView`` 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 PublisherBookListView(ListView):
- template_name = "books/books_by_publisher.html"
- def get_queryset(self):
- self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
- return Book.objects.filter(publisher=self.publisher)
- Using ``get_queryset`` to add logic to the queryset selection is as convenient
- as it is powerful. For instance, 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().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 wouldn't know anything about this field, but
- once again we could 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.urls import path
- from books.views import AuthorDetailView
- urlpatterns = [
- # ...
- path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
- ]
- Then we'd write our new view -- ``get_object`` is the method that retrieves the
- object -- so we override it and wrap the call::
- from django.utils import timezone
- from django.views.generic import DetailView
- from books.models import Author
- class AuthorDetailView(DetailView):
- queryset = Author.objects.all()
- def get_object(self):
- obj = super().get_object()
- # Record the last accessed date
- obj.last_accessed = timezone.now()
- obj.save()
- return obj
- .. 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
- :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
- on the view.
|