Appendix C: Generic View Reference

最后更新于:2022-04-01 04:48:32

Chapter 10 introduced generic views but leaves out some of the gory details. This appendix describes each generic view along with all the options each view can take. Be sure to read Chapter 10 before trying to understand the reference material that follows. You might want to refer back to the `Book`, `Publisher`, and`Author` objects defined in that chapter; the examples that follow use these models. [TOC=3] ## Common Arguments to Generic Views Most of these views take a large number of arguments that can change the generic view’s behavior. Many of these arguments work the same across a large number of views. Table C-1 describes each of these common arguments; anytime you see one of these arguments in a generic view’s argument list, it will work as described in the table. Table C-1\. Common Arguments to Generic Views | Argument | Description | | --- | --- | | `allow_empty` | A Boolean specifying whether to display the page if no objects are available. If this is `False` and no objects are available, the view will raise a 404 error instead of displaying an empty page. By default, this is `True`. | | `context_processors` | A list of additional template-context processors (besides the defaults) to apply to the view’s template. See Chapter 9 for information on template context processors. | | `extra_context` | A dictionary of values to add to the template context. By default, this is an empty dictionary. If a value in the dictionary is callable, the generic view will call it just before rendering the template. | | `mimetype` | The MIME type to use for the resulting document. It defaults to the value of the `DEFAULT_MIME_TYPE` setting, which is `text/html` if you haven’t changed it. | | `queryset` | A `QuerySet` (i.e., something like `Author.objects.all()`) to read objects from. See Appendix B for more information about `QuerySet` objects. Most generic views require this argument. | | `template_loader` | The template loader to use when loading the template. By default, it’s `django.template.loader`. See Chapter 9 for information on template loaders. | | `template_name` | The full name of a template to use in rendering the page. This lets you override the default template name derived from the `QuerySet`. | | `template_object_name` | The name of the template variable to use in the template context. By default, this is `'object'`. Views that list more than one object (i.e., `object_list` views and various objects-for-date views) will append `'_list'` to the value of this parameter. | ## “Simple” Generic Views The module“django.views.generic.base“ contains simple views that handle a couple of common cases: rendering a template when no view logic is needed and issuing a redirect. ### Rendering a Template *View function*: `django.views.generic.base.TemplateView` This view renders a given template, passing it a context with keyword arguments captured in the URL. #### EXAMPLE Given the following URLconf: ~~~ from django.conf.urls import url from myapp.views import HomePageView urlpatterns = [ url(r'^$', HomePageView.as_view(), name='home'), ] ~~~ An a sample views.py: ~~~ from django.views.generic.base import TemplateView from articles.models import Article class HomePageView(TemplateView): template_name = "home.html" def get_context_data(self, **kwargs): context = super(HomePageView, self).get_context_data(**kwargs) context['latest_articles'] = Article.objects.all()[:5] return context ~~~ a request to `/` would render the template `home.html`, returning a context containing a list of the top 5 articles. ### Redirecting to Another URL *View function*: `django.views.generic.base.RedirectView` Redirects to a given URL. The given URL may contain dictionary-style string formatting, which will be interpolated against the parameters captured in the URL. Because keyword interpolation is *always* done (even if no arguments are passed in), any `"%"` characters in the URL must be written as `"%%"` so that Python will convert them to a single percent sign on output. If the given URL is `None`, Django will return an `HttpResponseGone` (410). Example views.py: ~~~ from django.shortcuts import get_object_or_404 from django.views.generic.base import RedirectView from articles.models import Article class ArticleCounterRedirectView(RedirectView): permanent = False query_string = True pattern_name = 'article-detail' def get_redirect_url(self, *args, **kwargs): article = get_object_or_404(Article, pk=kwargs['pk']) article.update_counter() return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs) ~~~ Example urls.py: ~~~ from django.conf.urls import url from django.views.generic.base import RedirectView from article.views import ArticleCounterRedirectView, ArticleDetail urlpatterns = [ url(r'^counter/(?P<pk>[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'), url(r'^details/(?P<pk>[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'), url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'), ] ~~~ Attributes `url` The URL to redirect to, as a string. Or `None` to raise a 410 (Gone) HTTP error. `pattern_name` The name of the URL pattern to redirect to. Reversing will be done using the same args and kwargs as are passed in for this view. `permanent` Whether the redirect should be permanent. The only difference here is the HTTP status code returned. If`True`, then the redirect will use status code 301\. If `False`, then the redirect will use status code 302\. By default, `permanent` is `True`. `query_string` Whether to pass along the GET query string to the new location. If `True`, then the query string is appended to the URL. If `False`, then the query string is discarded. By default, `query_string` is `False`. Methods `get_redirect_url`(**args*, ***kwargs*) Constructs the target URL for redirection. The default implementation uses [`url`](http://masteringdjango.com/django-generic-view-reference/#url "url") as a starting string and performs expansion of `%` named parameters in that string using the named groups captured in the URL. If [`url`](http://masteringdjango.com/django-generic-view-reference/#url "url") is not set, `get_redirect_url()` tries to reverse the [`pattern_name`](http://masteringdjango.com/django-generic-view-reference/#pattern_name "pattern_name") using what was captured in the URL (both named and unnamed groups are used). If requested by [`query_string`](http://masteringdjango.com/django-generic-view-reference/#query_string "query_string"), it will also append the query string to the generated URL. Subclasses may implement any behavior they wish, as long as the method returns a redirect-ready URL string. ## List/Detail Generic Views The list/detail generic views handle the common case of displaying a list of items at one view and individual “detail” views of those items at another. ### Lists of Objects *View function*: `django.views.generic.list.ListView` Use this view to display a page representing a list of objects. #### EXAMPLE Example views.py: ~~~ from django.views.generic.list import ListView from django.utils import timezone from articles.models import Article class ArticleListView(ListView): model = Article def get_context_data(self, **kwargs): context = super(ArticleListView, self).get_context_data(**kwargs) context['now'] = timezone.now() return context ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from article.views import ArticleListView urlpatterns = [ url(r'^$', ArticleListView.as_view(), name='article-list'), ] ~~~ Example myapp/article_list.html: ~~~ <h1>Articles</h1> <ul> {% for article in object_list %} <li>{{ article.pub_date|date }} - {{ article.headline }}</li> {% empty %} <li>No articles yet.</li> {% endfor %} </ul> ~~~ ### Detail Views *View function*: `django.views.generic.detail.DetailView` This view provides a “detail” view of a single object. #### EXAMPLE Example myapp/views.py: ~~~ from django.views.generic.detail import DetailView from django.utils import timezone from articles.models import Article class ArticleDetailView(DetailView): model = Article def get_context_data(self, **kwargs): context = super(ArticleDetailView, self).get_context_data(**kwargs) context['now'] = timezone.now() return context ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from article.views import ArticleDetailView urlpatterns = [ url(r'^(?P<slug>[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), ] ~~~ Example myapp/article_detail.html: ~~~ <h1>{{ object.headline }}</h1> <p>{{ object.content }}</p> <p>Reporter: {{ object.reporter }}</p> <p>Published: {{ object.pub_date|date }}</p> <p>Date: {{ now|date }}</p> ~~~ ## Date-Based Generic Views Date-based generic views, provided in [`django.views.generic.dates`](http://masteringdjango.com/django-generic-view-reference/#module-django.views.generic.dates "django.views.generic.dates"), are views for displaying drilldown pages for date-based data. Note Some of the examples on this page assume that an `Article` model has been defined as follows in`myapp/models.py`: ~~~ from django.db import models from django.core.urlresolvers import reverse class Article(models.Model): title = models.CharField(max_length=200) pub_date = models.DateField() def get_absolute_url(self): return reverse('article-detail', kwargs={'pk': self.pk}) ~~~ ### ArchiveIndexView *class *`django.views.generic.dates.``ArchiveIndexView` A top-level index page showing the “latest” objects, by date. Objects with a date in the *future* are not included unless you set `allow_future` to `True`. Context In addition to the context provided by `django.views.generic.list.MultipleObjectMixin` (via`django.views.generic.dates.BaseDateListView`), the template’s context will be: * `date_list`: A `DateQuerySet` object containing all years that have objects available according to `queryset`, represented as `datetime.datetime` objects, in descending order. Notes * Uses a default `context_object_name` of `latest`. * Uses a default `template_name_suffix` of `_archive`. * Defaults to providing `date_list` by year, but this can be altered to month or day using the attribute`date_list_period`. This also applies to all subclass views. Example myapp/urls.py: ~~~ from django.conf.urls import url from django.views.generic.dates import ArchiveIndexView from myapp.models import Article urlpatterns = [ url(r'^archive/$', ArchiveIndexView.as_view(model=Article, date_field="pub_date"), name="article_archive"), ] ~~~ Example myapp/article_archive.html: ~~~ <ul> {% for article in latest %} <li>{{ article.pub_date }}: {{ article.title }}</li> {% endfor %} </ul> ~~~ This will output all articles. ### YearArchiveView *class *`django.views.generic.dates.``YearArchiveView` A yearly archive page showing all available months in a given year. Objects with a date in the *future* are not displayed unless you set `allow_future` to `True`. `make_object_list` A boolean specifying whether to retrieve the full list of objects for this year and pass those to the template. If `True`, the list of objects will be made available to the context. If `False`, the `None` queryset will be used as the object list. By default, this is `False`. `get_make_object_list`() Determine if an object list will be returned as part of the context. Returns [`make_object_list`](http://masteringdjango.com/django-generic-view-reference/#django.views.generic.dates.YearArchiveView.make_object_list "django.views.generic.dates.YearArchiveView.make_object_list") by default. Context In addition to the context provided by `django.views.generic.list.MultipleObjectMixin` (via`django.views.generic.dates.BaseDateListView`), the template’s context will be: * `date_list`: A `DateQuerySet` object containing all months that have objects available according to `queryset`, represented as `datetime.datetime` objects, in ascending order. * `year`: A `date` object representing the given year. * `next_year`: A `date` object representing the first day of the next year, according to `allow_empty` and`allow_future`. * `previous_year`: A `date` object representing the first day of the previous year, according to `allow_empty` and`allow_future`. Notes * Uses a default `template_name_suffix` of `_archive_year`. Example myapp/views.py: ~~~ from django.views.generic.dates import YearArchiveView from myapp.models import Article class ArticleYearArchiveView(YearArchiveView): queryset = Article.objects.all() date_field = "pub_date" make_object_list = True allow_future = True ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from myapp.views import ArticleYearArchiveView urlpatterns = [ url(r'^(?P<year>[0-9]{4})/$', ArticleYearArchiveView.as_view(), name="article_year_archive"), ] ~~~ Example myapp/article_archive_year.html: ~~~ <ul> {% for date in date_list %} <li>{{ date|date }}</li> {% endfor %} </ul> <div> <h1>All Articles for {{ year|date:"Y" }}</h1> {% for obj in object_list %} <p> {{ obj.title }} - {{ obj.pub_date|date:"F j, Y" }} </p> {% endfor %} </div> ~~~ ### MonthArchiveView *class *`django.views.generic.dates.``MonthArchiveView` A monthly archive page showing all objects in a given month. Objects with a date in the *future* are not displayed unless you set `allow_future` to `True`. Context In addition to the context provided by `MultipleObjectMixin` (via `BaseDateListView`), the template’s context will be: * `date_list`: A `DateQuerySet` object containing all days that have objects available in the given month, according to `queryset`, represented as `datetime.datetime` objects, in ascending order. * `month`: A `date` object representing the given month. * `next_month`: A `date` object representing the first day of the next month, according to `allow_empty` and`allow_future`. * `previous_month`: A `date` object representing the first day of the previous month, according to `allow_empty` and`allow_future`. Notes * Uses a default `template_name_suffix` of `_archive_month`. Example myapp/views.py: ~~~ from django.views.generic.dates import MonthArchiveView from myapp.models import Article class ArticleMonthArchiveView(MonthArchiveView): queryset = Article.objects.all() date_field = "pub_date" make_object_list = True allow_future = True ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from myapp.views import ArticleMonthArchiveView urlpatterns = [ # Example: /2012/aug/ url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/$', ArticleMonthArchiveView.as_view(), name="archive_month"), # Example: /2012/08/ url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$', ArticleMonthArchiveView.as_view(month_format='%m'), name="archive_month_numeric"), ] ~~~ Example myapp/article_archive_month.html: ~~~ <ul> {% for article in object_list %} <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> {% endfor %} </ul> <p> {% if previous_month %} Previous Month: {{ previous_month|date:"F Y" }} {% endif %} {% if next_month %} Next Month: {{ next_month|date:"F Y" }} {% endif %} </p> ~~~ ### WeekArchiveView *class *`django.views.generic.dates.``WeekArchiveView` A weekly archive page showing all objects in a given week. Objects with a date in the *future* are not displayed unless you set `allow_future` to `True`. Context In addition to the context provided by `MultipleObjectMixin` (via `BaseDateListView`), the template’s context will be: * `week`: A `date` object representing the first day of the given week. * `next_week`: A `date` object representing the first day of the next week, according to `allow_empty` and`allow_future`. * `previous_week`: A `date` object representing the first day of the previous week, according to `allow_empty` and`allow_future`. Notes * Uses a default `template_name_suffix` of `_archive_week`. Example myapp/views.py: ~~~ from django.views.generic.dates import WeekArchiveView from myapp.models import Article class ArticleWeekArchiveView(WeekArchiveView): queryset = Article.objects.all() date_field = "pub_date" make_object_list = True week_format = "%W" allow_future = True ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from myapp.views import ArticleWeekArchiveView urlpatterns = [ # Example: /2012/week/23/ url(r'^(?P<year>[0-9]{4})/week/(?P<week>[0-9]+)/$', ArticleWeekArchiveView.as_view(), name="archive_week"), ] ~~~ Example myapp/article_archive_week.html: ~~~ <h1>Week {{ week|date:'W' }}</h1> <ul> {% for article in object_list %} <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> {% endfor %} </ul> <p> {% if previous_week %} Previous Week: {{ previous_week|date:"F Y" }} {% endif %} {% if previous_week and next_week %}--{% endif %} {% if next_week %} Next week: {{ next_week|date:"F Y" }} {% endif %} </p> ~~~ In this example, you are outputting the week number. The default `week_format` in the `WeekArchiveView` uses week format `'%U'` which is based on the United States week system where the week begins on a Sunday. The `'%W'` format uses the ISO week format and its week begins on a Monday. The `'%W'` format is the same in both the `strftime()` and the `date`. However, the `date` template filter does not have an equivalent output format that supports the US based week system. The `date` filter `'%U'` outputs the number of seconds since the Unix epoch. ### DayArchiveView *class *`django.views.generic.dates.``DayArchiveView` A day archive page showing all objects in a given day. Days in the future throw a 404 error, regardless of whether any objects exist for future days, unless you set `allow_future` to `True`. Context In addition to the context provided by `MultipleObjectMixin` (via `BaseDateListView`), the template’s context will be: * `day`: A `date` object representing the given day. * `next_day`: A `date` object representing the next day, according to `allow_empty` and `allow_future`. * `previous_day`: A `date` object representing the previous day, according to `allow_empty` and `allow_future`. * `next_month`: A `date` object representing the first day of the next month, according to `allow_empty` and`allow_future`. * `previous_month`: A `date` object representing the first day of the previous month, according to `allow_empty` and`allow_future`. Notes * Uses a default `template_name_suffix` of `_archive_day`. Example myapp/views.py: ~~~ from django.views.generic.dates import DayArchiveView from myapp.models import Article class ArticleDayArchiveView(DayArchiveView): queryset = Article.objects.all() date_field = "pub_date" make_object_list = True allow_future = True ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from myapp.views import ArticleDayArchiveView urlpatterns = [ # Example: /2012/nov/10/ url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/$', ArticleDayArchiveView.as_view(), name="archive_day"), ] ~~~ Example myapp/article_archive_day.html: ~~~ <h1>{{ day }}</h1> <ul> {% for article in object_list %} <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> {% endfor %} </ul> <p> {% if previous_day %} Previous Day: {{ previous_day }} {% endif %} {% if previous_day and next_day %}--{% endif %} {% if next_day %} Next Day: {{ next_day }} {% endif %} </p> ~~~ ### TodayArchiveView *class *`django.views.generic.dates.``TodayArchiveView` A day archive page showing all objects for *today*. This is exactly the same as[`django.views.generic.dates.DayArchiveView`](http://masteringdjango.com/django-generic-view-reference/#django.views.generic.dates.DayArchiveView "django.views.generic.dates.DayArchiveView"), except today’s date is used instead of the `year`/`month`/`day`arguments. Notes * Uses a default `template_name_suffix` of `_archive_today`. Example myapp/views.py: ~~~ from django.views.generic.dates import TodayArchiveView from myapp.models import Article class ArticleTodayArchiveView(TodayArchiveView): queryset = Article.objects.all() date_field = "pub_date" make_object_list = True allow_future = True ~~~ Example myapp/urls.py: ~~~ from django.conf.urls import url from myapp.views import ArticleTodayArchiveView urlpatterns = [ url(r'^today/$', ArticleTodayArchiveView.as_view(), name="archive_today"), ] ~~~ Where is the example template for `TodayArchiveView`? This view uses by default the same template as the [`DayArchiveView`](http://masteringdjango.com/django-generic-view-reference/#django.views.generic.dates.DayArchiveView "django.views.generic.dates.DayArchiveView"), which is in the previous example. If you need a different template, set the `template_name` attribute to be the name of the new template. ### DateDetailView *class *`django.views.generic.dates.``DateDetailView` A page representing an individual object. If the object has a date value in the future, the view will throw a 404 error by default, unless you set `allow_future` to `True`. Context * Includes the single object associated with the `model` specified in the `DateDetailView`. Notes * Uses a default `template_name_suffix` of `_detail`. Example myapp/urls.py: ~~~ from django.conf.urls import url from django.views.generic.dates import DateDetailView urlpatterns = [ url(r'^(?P<year>[0-9]+)/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<pk>[0-9]+)/$', DateDetailView.as_view(model=Article, date_field="pub_date"), name="archive_date_detail"), ] ~~~ Example myapp/article_detail.html: ~~~ <h1>{{ object.title }}</h1> ~~~ ## Form handling with class-based views Form processing generally has 3 paths: * Initial GET (blank or prepopulated form) * POST with invalid data (typically redisplay form with errors) * POST with valid data (process the data and typically redirect) Implementing this yourself often results in a lot of repeated boilerplate code (see Using a form in a view). To help avoid this, Django provides a collection of generic class-based views for form processing. ### Basic Forms Given a simple contact form: ~~~ # forms.py from django import forms class ContactForm(forms.Form): name = forms.CharField() message = forms.CharField(widget=forms.Textarea) def send_email(self): # send email using the self.cleaned_data dictionary pass ~~~ The view can be constructed using a `FormView`: ~~~ # views.py from myapp.forms import ContactForm from django.views.generic.edit import FormView class ContactView(FormView): template_name = 'contact.html' form_class = ContactForm success_url = '/thanks/' def form_valid(self, form): # This method is called when valid form data has been POSTed. # It should return an HttpResponse. form.send_email() return super(ContactView, self).form_valid(form) ~~~ Notes: * FormView inherits `TemplateResponseMixin` so `template_name` can be used here. * The default implementation for `form_valid()` simply redirects to the `success_url`. ### Model Forms Generic views really shine when working with models. These generic views will automatically create a`ModelForm`, so long as they can work out which model class to use: * If the `model` attribute is given, that model class will be used. * If `get_object()` returns an object, the class of that object will be used. * If a `queryset` is given, the model for that queryset will be used. Model form views provide a `form_valid()` implementation that saves the model automatically. You can override this if you have any special requirements; see below for examples. You don’t even need to provide a `success_url` for `CreateView` or `UpdateView` – they will use `get_absolute_url()`on the model object if available. If you want to use a custom `ModelForm` (for instance to add extra validation) simply set `form_class` on your view. Note When specifying a custom form class, you must still specify the model, even though the `form_class` may be a `ModelForm`. First we need to add `get_absolute_url()` to our `Author` class: ~~~ # models.py from django.core.urlresolvers import reverse from django.db import models class Author(models.Model): name = models.CharField(max_length=200) def get_absolute_url(self): return reverse('author-detail', kwargs={'pk': self.pk}) ~~~ Then we can use `CreateView` and friends to do the actual work. Notice how we’re just configuring the generic class-based views here; we don’t have to write any logic ourselves: ~~~ # views.py from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.core.urlresolvers import reverse_lazy from myapp.models import Author class AuthorCreate(CreateView): model = Author fields = ['name'] class AuthorUpdate(UpdateView): model = Author fields = ['name'] class AuthorDelete(DeleteView): model = Author success_url = reverse_lazy('author-list') ~~~ Note We have to use `reverse_lazy()` here, not just `reverse` as the urls are not loaded when the file is imported. The `fields` attribute works the same way as the `fields` attribute on the inner `Meta` class on `ModelForm`. Unless you define the form class in another way, the attribute is required and the view will raise an`ImproperlyConfigured` exception if it’s not. If you specify both the `fields` and `form_class` attributes, an `ImproperlyConfigured` exception will be raised. Changed in version 1.8: Omitting the `fields` attribute was previously allowed and resulted in a form with all of the model’s fields. Changed in version 1.8: Previously if both `fields` and `form_class` were specified, `fields` was silently ignored. Finally, we hook these new views into the URLconf: ~~~ # urls.py from django.conf.urls import url from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete urlpatterns = [ # ... url(r'author/add/$', AuthorCreate.as_view(), name='author_add'), url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'), url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'), ] ~~~ Note These views inherit `SingleObjectTemplateResponseMixin` which uses `template_name_suffix` to construct the`template_name` based on the model. In this example: * `CreateView` and `UpdateView` use `myapp/author_form.html` * `DeleteView` uses `myapp/author_confirm_delete.html` If you wish to have separate templates for `CreateView` and `UpdateView`, you can set either `template_name` or`template_name_suffix` on your view class. ### Models and request.user To track the user that created an object using a `CreateView`, you can use a custom `ModelForm` to do this. First, add the foreign key relation to the model: ~~~ # models.py from django.contrib.auth.models import User from django.db import models class Author(models.Model): name = models.CharField(max_length=200) created_by = models.ForeignKey(User) # ... ~~~ In the view, ensure that you don’t include `created_by` in the list of fields to edit, and override `form_valid()`to add the user: ~~~ # views.py from django.views.generic.edit import CreateView from myapp.models import Author class AuthorCreate(CreateView): model = Author fields = ['name'] def form_valid(self, form): form.instance.created_by = self.request.user return super(AuthorCreate, self).form_valid(form) ~~~ Note that you’ll need to decorate this view using [`login_required()`](http://masteringdjango.com/django-generic-view-reference/chapter_11.html#django.contrib.auth.decorators.login_required "django.contrib.auth.decorators.login_required"), or alternatively handle unauthorized users in the `form_valid()`. ### AJAX example Here is a simple example showing how you might go about implementing a form that works for AJAX requests as well as ‘normal’ form POSTs: ~~~ from django.http import JsonResponse from django.views.generic.edit import CreateView from myapp.models import Author class AjaxableResponseMixin(object): """ Mixin to add AJAX support to a form. Must be used with an object-based FormView (e.g. CreateView) """ def form_invalid(self, form): response = super(AjaxableResponseMixin, self).form_invalid(form) if self.request.is_ajax(): return JsonResponse(form.errors, status=400) else: return response def form_valid(self, form): # We make sure to call the parent's form_valid() method because # it might do some processing (in the case of CreateView, it will # call form.save() for example). response = super(AjaxableResponseMixin, self).form_valid(form) if self.request.is_ajax(): data = { 'pk': self.object.pk, } return JsonResponse(data) else: return response class AuthorCreate(AjaxableResponseMixin, CreateView): model = Author fields = ['name'] ~~~ ## Using mixins with class-based views Caution This is an advanced topic. A working knowledge of Django’s class-based views is advised before exploring these techniques. Django’s built-in class-based views provide a lot of functionality, but some of it you may want to use separately. For instance, you may want to write a view that renders a template to make the HTTP response, but you can’t use `TemplateView`; perhaps you need to render a template only on `POST`, with `GET`doing something else entirely. While you could use `TemplateResponse` directly, this will likely result in duplicate code. For this reason, Django also provides a number of mixins that provide more discrete functionality. Template rendering, for instance, is encapsulated in the `TemplateResponseMixin`. The Django reference documentation contains full documentation of all the mixins. ## Context and template responses Two central mixins are provided that help in providing a consistent interface to working with templates in class-based views. `TemplateResponseMixin` Every built in view which returns a `TemplateResponse` will call the `render_to_response()` method that`TemplateResponseMixin` provides. Most of the time this will be called for you (for instance, it is called by the`get()` method implemented by both `TemplateView` and `DetailView`); similarly, it’s unlikely that you’ll need to override it, although if you want your response to return something not rendered via a Django template then you’ll want to do it. For an example of this, see the JSONResponseMixin example . `render_to_response()` itself calls `get_template_names()`, which by default will just look up `template_name` on the class-based view; two other mixins (`SingleObjectTemplateResponseMixin` and`MultipleObjectTemplateResponseMixin`) override this to provide more flexible defaults when dealing with actual objects. `ContextMixin` Every built in view which needs context data, such as for rendering a template (including`TemplateResponseMixin` above), should call `get_context_data()` passing any data they want to ensure is in there as keyword arguments. `get_context_data()` returns a dictionary; in `ContextMixin` it simply returns its keyword arguments, but it is common to override this to add more members to the dictionary. ## Building up Django’s generic class-based views Let’s look at how two of Django’s generic class-based views are built out of mixins providing discrete functionality. We’ll consider `DetailView`, which renders a “detail” view of an object, and `ListView`, which will render a list of objects, typically from a queryset, and optionally paginate them. This will introduce us to four mixins which between them provide useful functionality when working with either a single Django object, or multiple objects. There are also mixins involved in the generic edit views (`FormView`, and the model-specific views`CreateView`, `UpdateView` and `DeleteView`), and in the date-based generic views. These are covered in the mixin reference documentation. ### DetailView: working with a single Django object To show the detail of an object, we basically need to do two things: we need to look up the object and then we need to make a `TemplateResponse` with a suitable template, and that object as context. To get the object, `DetailView` relies on `SingleObjectMixin`, which provides a `get_object()` method that figures out the object based on the URL of the request (it looks for `pk` and `slug` keyword arguments as declared in the URLConf, and looks the object up either from the `model` attribute on the view, or the `queryset` attribute if that’s provided). `SingleObjectMixin` also overrides `get_context_data()`, which is used across all Django’s built in class-based views to supply context data for template renders. To then make a `TemplateResponse`, `DetailView` uses `SingleObjectTemplateResponseMixin`, which extends`TemplateResponseMixin`, overriding `get_template_names()` as discussed above. It actually provides a fairly sophisticated set of options, but the main one that most people are going to use is`<app_label>/<model_name>_detail.html`. The `_detail` part can be changed by setting `template_name_suffix` on a subclass to something else. (For instance, the generic edit views use `_form` for create and update views, and `_confirm_delete` for delete views.) ### ListView: working with many Django objects Lists of objects follow roughly the same pattern: we need a (possibly paginated) list of objects, typically a`QuerySet`, and then we need to make a `TemplateResponse` with a suitable template using that list of objects. To get the objects, `ListView` uses `MultipleObjectMixin`, which provides both `get_queryset()` and`paginate_queryset()`. Unlike with `SingleObjectMixin`, there’s no need to key off parts of the URL to figure out the queryset to work with, so the default just uses the `queryset` or `model` attribute on the view class. A common reason to override `get_queryset()` here would be to dynamically vary the objects, such as depending on the current user or to exclude posts in the future for a blog. `MultipleObjectMixin` also overrides `get_context_data()` to include appropriate context variables for pagination (providing dummies if pagination is disabled). It relies on `object_list` being passed in as a keyword argument, which `ListView` arranges for it. To make a `TemplateResponse`, `ListView` then uses `MultipleObjectTemplateResponseMixin`; as with`SingleObjectTemplateResponseMixin` above, this overrides `get_template_names()` to provide `a range of options`, with the most commonly-used being `<app_label>/<model_name>_list.html`, with the `_list` part again being taken from the `template_name_suffix` attribute. (The date based generic views use suffixes such as `_archive`,`_archive_year` and so on to use different templates for the various specialized date-based list views.) ## Using Django’s class-based view mixins Now we’ve seen how Django’s generic class-based views use the provided mixins, let’s look at other ways we can combine them. Of course we’re still going to be combining them with either built-in class-based views, or other generic class-based views, but there are a range of rarer problems you can solve than are provided for by Django out of the box. Warning Not all mixins can be used together, and not all generic class based views can be used with all other mixins. Here we present a few examples that do work; if you want to bring together other functionality then you’ll have to consider interactions between attributes and methods that overlap between the different classes you’re using, and how [method resolution order](https://www.python.org/download/releases/2.3/mro/) will affect which versions of the methods will be called in what order. The reference documentation for Django’s class-based views and class-based view mixins will help you in understanding which attributes and methods are likely to cause conflict between different classes and mixins. If in doubt, it’s often better to back off and base your work on `View` or `TemplateView`, perhaps with`SingleObjectMixin` and `MultipleObjectMixin`. Although you will probably end up writing more code, it is more likely to be clearly understandable to someone else coming to it later, and with fewer interactions to worry about you will save yourself some thinking. (Of course, you can always dip into Django’s implementation of the generic class based views for inspiration on how to tackle problems.) ### Using SingleObjectMixin with View If we want to write a simple class-based view that responds only to `POST`, we’ll subclass `View` and write a`post()` method in the subclass. However if we want our processing to work on a particular object, identified from the URL, we’ll want the functionality provided by `SingleObjectMixin`. We’ll demonstrate this with the `Author` model we used in the generic class-based views introduction. ~~~ # views.py from django.http import HttpResponseForbidden, HttpResponseRedirect from django.core.urlresolvers import reverse from django.views.generic import View from django.views.generic.detail import SingleObjectMixin from books.models import Author class RecordInterest(SingleObjectMixin, View): """Records the current user's interest in an author.""" model = Author def post(self, request, \*args, \*\*kwargs): if not request.user.is_authenticated(): return HttpResponseForbidden() # Look up the author we're interested in. self.object = self.get_object() # Actually record interest somehow here! return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk})) ~~~ In practice you’d probably want to record the interest in a key-value store rather than in a relational database, so we’ve left that bit out. The only bit of the view that needs to worry about using`SingleObjectMixin` is where we want to look up the author we’re interested in, which it just does with a simple call to `self.get_object()`. Everything else is taken care of for us by the mixin. We can hook this into our URLs easily enough: ~~~ # urls.py from django.conf.urls import url from books.views import RecordInterest urlpatterns = [ #... url(r'^author/(?P<pk>[0-9]+)/interest/$', RecordInterest.as_view(), name='author-interest'), ] ~~~ Note the `pk` named group, which `get_object()` uses to look up the `Author` instance. You could also use a slug, or any of the other features of `SingleObjectMixin`. ### Using SingleObjectMixin with ListView `ListView` provides built-in pagination, but you might want to paginate a list of objects that are all linked (by a foreign key) to another object. In our publishing example, you might want to paginate through all the books by a particular publisher. One way to do this is to combine `ListView` with `SingleObjectMixin`, so that the queryset for the paginated list of books can hang off the publisher found as the single object. In order to do this, we need to have two different querysets: `Book` queryset for use by `ListView` Since we have access to the `Publisher` whose books we want to list, we simply override `get_queryset()` and use the `Publisher`‘s reverse foreign key manager. `Publisher` queryset for use in `get_object()` We’ll rely on the default implementation of `get_object()` to fetch the correct `Publisher` object. However, we need to explicitly pass a `queryset` argument because otherwise the default implementation of `get_object()`would call `get_queryset()` which we have overridden to return `Book` objects instead of `Publisher` ones. Note We have to think carefully about `get_context_data()`. Since both `SingleObjectMixin` and `ListView` will put things in the context data under the value of `context_object_name` if it’s set, we’ll instead explicitly ensure the `Publisher` is in the context data. `ListView` will add in the suitable `page_obj` and `paginator` for us providing we remember to call `super()`. Now we can write a new `PublisherDetail`: ~~~ from django.views.generic import ListView from django.views.generic.detail import SingleObjectMixin from books.models import Publisher class PublisherDetail(SingleObjectMixin, ListView): paginate_by = 2 template_name = "books/publisher_detail.html" def get(self, request, *args, **kwargs): self.object = self.get_object(queryset=Publisher.objects.all()) return super(PublisherDetail, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super(PublisherDetail, self).get_context_data(**kwargs) context['publisher'] = self.object return context def get_queryset(self): return self.object.book_set.all() ~~~ Notice how we set `self.object` within `get()` so we can use it again later in `get_context_data()` and`get_queryset()`. If you don’t set `template_name`, the template will default to the normal `ListView` choice, which in this case would be `"books/book_list.html"` because it’s a list of books; `ListView` knows nothing about`SingleObjectMixin`, so it doesn’t have any clue this view is anything to do with a `Publisher`. The `paginate_by` is deliberately small in the example so you don’t have to create lots of books to see the pagination working! Here’s the template you’d want to use: ~~~ {% extends "base.html" %} {% block content %} <h2>Publisher {{ publisher.name }}</h2> <ol> {% for book in page_obj %} <li>{{ book.title }}</li> {% endfor %} </ol> <div class="pagination"> <span class="step-links"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}">previous</a> {% endif %} <span class="current"> Page {{ page_obj.number }} of {{ paginator.num_pages }}. {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}">next</a> {% endif %} </div> {% endblock %} ~~~ ## Avoid anything more complex Generally you can use `TemplateResponseMixin` and `SingleObjectMixin` when you need their functionality. As shown above, with a bit of care you can even combine `SingleObjectMixin` with `ListView`. However things get increasingly complex as you try to do so, and a good rule of thumb is: Hint Each of your views should use only mixins or views from one of the groups of generic class-based views: detail, list editing and date. For example it’s fine to combine `TemplateView` (built in view) with`MultipleObjectMixin` (generic list), but you’re likely to have problems combining `SingleObjectMixin` (generic detail) with `MultipleObjectMixin` (generic list). To show what happens when you try to get more sophisticated, we show an example that sacrifices readability and maintainability when there is a simpler solution. First, let’s look at a naive attempt to combine `DetailView` with `FormMixin` to enable use to `POST` a Django `Form` to the same URL as we’re displaying an object using `DetailView`. ### Using FormMixin with DetailView Think back to our earlier example of using `View` and `SingleObjectMixin` together. We were recording a user’s interest in a particular author; say now that we want to let them leave a message saying why they like them. Again, let’s assume we’re not going to store this in a relational database but instead in something more esoteric that we won’t worry about here. At this point it’s natural to reach for a `Form` to encapsulate the information sent from the user’s browser to Django. Say also that we’re heavily invested in [REST](http://en.wikipedia.org/wiki/Representational_state_transfer), so we want to use the same URL for displaying the author as for capturing the message from the user. Let’s rewrite our `AuthorDetailView` to do that. We’ll keep the `GET` handling from `DetailView`, although we’ll have to add a `Form` into the context data so we can render it in the template. We’ll also want to pull in form processing from `FormMixin`, and write a bit of code so that on `POST` the form gets called appropriately. Note We use `FormMixin` and implement `post()` ourselves rather than try to mix `DetailView` with `FormView` (which provides a suitable `post()` already) because both of the views implement `get()`, and things would get much more confusing. Our new `AuthorDetail` looks like this: ~~~ # CAUTION: you almost certainly do not want to do this. # It is provided as part of a discussion of problems you can # run into when combining different generic class-based view # functionality that is not designed to be used together. from django import forms from django.http import HttpResponseForbidden from django.core.urlresolvers import reverse from django.views.generic import DetailView from django.views.generic.edit import FormMixin from books.models import Author class AuthorInterestForm(forms.Form): message = forms.CharField() class AuthorDetail(FormMixin, DetailView): model = Author form_class = AuthorInterestForm def get_success_url(self): return reverse('author-detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): context = super(AuthorDetail, self).get_context_data(**kwargs) context['form'] = self.get_form() return context def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseForbidden() self.object = self.get_object() form = self.get_form() if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): # Here, we would record the user's interest using the message # passed in form.cleaned_data['message'] return super(AuthorDetail, self).form_valid(form) ~~~ `get_success_url()` is just providing somewhere to redirect to, which gets used in the default implementation of `form_valid()`. We have to provide our own `post()` as noted earlier, and override`get_context_data()` to make the `Form` available in the context data. ### A better solution It should be obvious that the number of subtle interactions between `FormMixin` and `DetailView` is already testing our ability to manage things. It’s unlikely you’d want to write this kind of class yourself. In this case, it would be fairly easy to just write the `post()` method yourself, keeping `DetailView` as the only generic functionality, although writing `Form` handling code involves a lot of duplication. Alternatively, it would still be easier than the above approach to have a separate view for processing the form, which could use `FormView` distinct from `DetailView` without concerns. ### An alternative better solution What we’re really trying to do here is to use two different class based views from the same URL. So why not do just that? We have a very clear division here: `GET` requests should get the `DetailView` (with the `Form`added to the context data), and `POST` requests should get the `FormView`. Let’s set up those views first. The `AuthorDisplay` view is almost the same as when we first introduced AuthorDetail`; we have to write our own `get_context_data()` to make the `AuthorInterestForm` available to the template. We’ll skip the `get_object()` override from before for clarity: ~~~ from django.views.generic import DetailView from django import forms from books.models import Author class AuthorInterestForm(forms.Form): message = forms.CharField() class AuthorDisplay(DetailView): model = Author def get_context_data(self, **kwargs): context = super(AuthorDisplay, self).get_context_data(**kwargs) context['form'] = AuthorInterestForm() return context ~~~ Then the `AuthorInterest` is a simple `FormView`, but we have to bring in `SingleObjectMixin` so we can find the author we’re talking about, and we have to remember to set `template_name` to ensure that form errors will render the same template as `AuthorDisplay` is using on `GET`: ~~~ from django.core.urlresolvers import reverse from django.http import HttpResponseForbidden from django.views.generic import FormView from django.views.generic.detail import SingleObjectMixin class AuthorInterest(SingleObjectMixin, FormView): template_name = 'books/author_detail.html' form_class = AuthorInterestForm model = Author def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseForbidden() self.object = self.get_object() return super(AuthorInterest, self).post(request, *args, **kwargs) def get_success_url(self): return reverse('author-detail', kwargs={'pk': self.object.pk}) ~~~ Finally we bring this together in a new `AuthorDetail` view. We already know that calling `as_view()` on a class-based view gives us something that behaves exactly like a function based view, so we can do that at the point we choose between the two subviews. You can of course pass through keyword arguments to `as_view()` in the same way you would in your URLconf, such as if you wanted the `AuthorInterest` behavior to also appear at another URL but using a different template: ~~~ from django.views.generic import View class AuthorDetail(View): def get(self, request, *args, **kwargs): view = AuthorDisplay.as_view() return view(request, *args, **kwargs) def post(self, request, *args, **kwargs): view = AuthorInterest.as_view() return view(request, *args, **kwargs) ~~~ This approach can also be used with any other generic class-based views or your own class-based views inheriting directly from `View` or `TemplateView`, as it keeps the different views as separate as possible. ## More than just HTML Where class based views shine is when you want to do the same thing many times. Suppose you’re writing an API, and every view should return JSON instead of rendered HTML. We can create a mixin class to use in all of our views, handling the conversion to JSON once. For example, a simple JSON mixin might look something like this: ~~~ from django.http import JsonResponse class JSONResponseMixin(object): """ A mixin that can be used to render a JSON response. """ def render_to_json_response(self, context, **response_kwargs): """ Returns a JSON response, transforming 'context' to make the payload. """ return JsonResponse( self.get_data(context), **response_kwargs ) def get_data(self, context): """ Returns an object that will be serialized as JSON by json.dumps(). """ # 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 context ~~~ Note Check out the serialization documentation for more information on how to correctly transform Django models and querysets into JSON. This mixin provides a `render_to_json_response()` method with the same signature as `render_to_response()`. To use it, we simply need to mix it into a `TemplateView` for example, and override `render_to_response()` to call`render_to_json_response()` instead: ~~~ from django.views.generic import TemplateView class JSONView(JSONResponseMixin, TemplateView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs) ~~~ Equally we could use our mixin with one of the generic views. We can make our own version of `DetailView`by mixing `JSONResponseMixin` with the `django.views.generic.detail.BaseDetailView` – (the `DetailView` before template rendering behavior has been mixed in): ~~~ from django.views.generic.detail import BaseDetailView class JSONDetailView(JSONResponseMixin, BaseDetailView): def render_to_response(self, context, **response_kwargs): return self.render_to_json_response(context, **response_kwargs) ~~~ This view can then be deployed in the same way as any other `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 `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 `JSONResponseMixin` and a `SingleObjectTemplateResponseMixin`, and override the implementation of `render_to_response()` to defer to the appropriate rendering method depending on the type of response that the user requested: ~~~ from django.views.generic.detail import SingleObjectTemplateResponseMixin class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView): def render_to_response(self, context): # Look for a 'format=json' GET argument if self.request.GET.get('format') == 'json': return self.render_to_json_response(context) else: return super(HybridDetailView, self).render_to_response(context) ~~~ Because of the way that Python resolves method overloading, the call to `super(HybridDetailView,self).render_to_response(context)` ends up calling the `render_to_response()` implementation of`TemplateResponseMixin`.
';