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
, andAuthor
objects defined in that chapter; the examples that follow use these models.
- Date-Based Generic Views
- ArchiveIndexView
- YearArchiveView
- MonthArchiveView
- WeekArchiveView
- DayArchiveView
- TodayArchiveView
- DateDetailView
Common Arguments to Generic Views
Most of these views take a large number of arguments that can change the generic views 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 views 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 views 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 havent 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, its 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. IfTrue
, 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
as a starting string and performs expansion of %
named parameters in that string using the named groups captured in the URL.
If url
is not set, get_redirect_url()
tries to reverse the pattern_name
using what was captured in the URL (both named and unnamed groups are used).
If requested by 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
, 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 inmyapp/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
(viadjango.views.generic.dates.BaseDateListView
), the templates context will be:
date_list
: ADateQuerySet
object containing all years that have objects available according toqueryset
, represented asdatetime.datetime
objects, in descending order.
Notes
- Uses a default
context_object_name
oflatest
. - 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 attributedate_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
by default.
Context
In addition to the context provided by django.views.generic.list.MultipleObjectMixin
(viadjango.views.generic.dates.BaseDateListView
), the templates context will be:
date_list
: ADateQuerySet
object containing all months that have objects available according toqueryset
, represented asdatetime.datetime
objects, in ascending order.year
: Adate
object representing the given year.next_year
: Adate
object representing the first day of the next year, according toallow_empty
andallow_future
.previous_year
: Adate
object representing the first day of the previous year, according toallow_empty
andallow_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 templates context will be:
date_list
: ADateQuerySet
object containing all days that have objects available in the given month, according toqueryset
, represented asdatetime.datetime
objects, in ascending order.month
: Adate
object representing the given month.next_month
: Adate
object representing the first day of the next month, according toallow_empty
andallow_future
.previous_month
: Adate
object representing the first day of the previous month, according toallow_empty
andallow_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 templates context will be:
week
: Adate
object representing the first day of the given week.next_week
: Adate
object representing the first day of the next week, according toallow_empty
andallow_future
.previous_week
: Adate
object representing the first day of the previous week, according toallow_empty
andallow_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 templates context will be:
day
: Adate
object representing the given day.next_day
: Adate
object representing the next day, according toallow_empty
andallow_future
.previous_day
: Adate
object representing the previous day, according toallow_empty
andallow_future
.next_month
: Adate
object representing the first day of the next month, according toallow_empty
andallow_future
.previous_month
: Adate
object representing the first day of the previous month, according toallow_empty
andallow_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 asdjango.views.generic.dates.DayArchiveView
, except todays 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
, 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 theDateDetailView
.
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
sotemplate_name
can be used here. - The default implementation for
form_valid()
simply redirects to thesuccess_url
.
Model Forms
Generic views really shine when working with models. These generic views will automatically create aModelForm
, 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 dont 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 were just configuring the generic class-based views here; we dont 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 anImproperlyConfigured
exception if its 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 models 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 thetemplate_name
based on the model.
In this example:
CreateView
andUpdateView
usemyapp/author_form.html
DeleteView
usesmyapp/author_confirm_delete.html
If you wish to have separate templates for CreateView
and UpdateView
, you can set either template_name
ortemplate_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 dont 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 youll need to decorate this view using 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 Djangos class-based views is advised before exploring these techniques.
Djangos 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 cant 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 thatTemplateResponseMixin
provides. Most of the time this will be called for you (for instance, it is called by theget()
method implemented by both TemplateView
and DetailView
); similarly, its unlikely that youll need to override it, although if you want your response to return something not rendered via a Django template then youll 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
andMultipleObjectTemplateResponseMixin
) 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 (includingTemplateResponseMixin
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 Djangos generic class-based views
Lets look at how two of Djangos generic class-based views are built out of mixins providing discrete functionality. Well 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 viewsCreateView
, 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 thats provided). SingleObjectMixin
also overrides get_context_data()
, which is used across all Djangos built in class-based views to supply context data for template renders.
To then make a TemplateResponse
, DetailView
uses SingleObjectTemplateResponseMixin
, which extendsTemplateResponseMixin
, 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 aQuerySet
, 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()
andpaginate_queryset()
. Unlike with SingleObjectMixin
, theres 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 withSingleObjectTemplateResponseMixin
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 Djangos class-based view mixins
Now weve seen how Djangos generic class-based views use the provided mixins, lets look at other ways we can combine them. Of course were 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 youll have to consider interactions between attributes and methods that overlap between the different classes youre using, and how method resolution order will affect which versions of the methods will be called in what order.
The reference documentation for Djangos 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, its often better to back off and base your work on View
or TemplateView
, perhaps withSingleObjectMixin
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 Djangos 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
, well subclass View
and write apost()
method in the subclass. However if we want our processing to work on a particular object, identified from the URL, well want the functionality provided by SingleObjectMixin
.
Well 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 youd probably want to record the interest in a key-value store rather than in a relational database, so weve left that bit out. The only bit of the view that needs to worry about usingSingleObjectMixin
is where we want to look up the author were 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()
Well 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 its set, well 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()
andget_queryset()
. If you dont set template_name
, the template will default to the normal ListView
choice, which in this case would be "books/book_list.html"
because its a list of books; ListView
knows nothing aboutSingleObjectMixin
, so it doesnt have any clue this view is anything to do with a Publisher
.
The paginate_by
is deliberately small in the example so you dont have to create lots of books to see the pagination working! Heres the template youd 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 its fine to combine TemplateView
(built in view) withMultipleObjectMixin
(generic list), but youre 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, lets look at a naive attempt to combine DetailView
with FormMixin
to enable use to POST
a Django Form
to the same URL as were 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 users interest in a particular author; say now that we want to let them leave a message saying why they like them. Again, lets assume were not going to store this in a relational database but instead in something more esoteric that we wont worry about here.
At this point its natural to reach for a Form
to encapsulate the information sent from the users browser to Django. Say also that were heavily invested in REST, so we want to use the same URL for displaying the author as for capturing the message from the user. Lets rewrite our AuthorDetailView
to do that.
Well keep the GET
handling from DetailView
, although well have to add a Form
into the context data so we can render it in the template. Well 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 overrideget_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. Its unlikely youd 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 were 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
. Lets 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. Well 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 were 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 youre 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 callrender_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 bothHTML 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 ofTemplateResponseMixin
.