Caution
This is an advanced topic. A working knowledge of Django’s class-based views is advised before exploring these techniques.
Django内置的基于类的视图提供了许多功能,但有些功能可能需要单独使用。 例如,您可能希望编写一个呈现模板以生成HTTP响应的视图,但不能使用TemplateView
;也许你只需要在POST
上渲染一个模板,GET
完全做其他事情。 虽然您可以直接使用TemplateResponse
,但这可能会导致代码重复。
出于这个原因,Django还提供了许多可提供更多离散功能的mixin。 例如,模板渲染封装在TemplateResponseMixin
中。 Django参考文档包含所有mixins的完整文档。
提供了两个中央混合,有助于提供一致的界面来处理基于类的视图中的模板。
TemplateResponseMixin
每个返回TemplateResponse
的内置视图都将调用TemplateResponseMixin
提供的render_to_response()
方法。 大部分时间都会为你调用它(例如,它由TemplateView
和DetailView
实现的get()
方法调用。 );同样地,你不太可能需要覆盖它,尽管如果你希望你的响应返回一些不通过Django模板呈现的东西,那么你就会想要这样做。 有关此示例,请参阅JSONResponseMixin示例。
render_to_response()
本身调用get_template_names()
,默认情况下只会在基于类的视图中查找template_name
;另外两个mixins(SingleObjectTemplateResponseMixin
和MultipleObjectTemplateResponseMixin
)会覆盖它,以便在处理实际对象时提供更灵活的默认值。
ContextMixin
TemplateResponseMixin
),都应该调用get_context_data()
传递他们想要确保的任何数据在那里作为关键字参数。
get_context_data()
返回一个字典;在ContextMixin
中,它只返回其关键字参数,但通常会覆盖它以向字典中添加更多成员。 您还可以使用extra_context
属性。让我们看一下Django的两个基于类的通用视图是如何用mixins构建的,提供离散功能。 我们将考虑DetailView
,它呈现一个对象的“细节”视图,以及ListView
,它将呈现一个对象列表,通常来自查询集,并且可选择分页他们。 这将向我们介绍四个mixin,它们在使用单个Django对象或多个对象时提供有用的功能。
通用编辑视图中还包含mixin(FormView
,以及特定于模型的视图CreateView
,UpdateView
和DeleteView
),以及基于日期的通用视图。 这些内容包含在mixin参考文档中。
DetailView
:使用单个Django对象¶为了显示对象的细节,我们基本上需要做两件事:我们需要查找对象然后我们需要使用合适的模板创建TemplateResponse
,并将该对象作为上下文。
要获取该对象,DetailView
依赖于SingleObjectMixin
,它提供了一个get_object()
方法,该方法根据请求的URL计算出对象(它查找URLConf中声明的pk
和slug
关键字参数,并从视图上的model
属性查找对象,或queryset
属性(如果提供)。 SingleObjectMixin
还会覆盖get_context_data()
,它用于所有Django内置的基于类的视图,以提供模板渲染的上下文数据。
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
:使用许多Django对象¶对象列表遵循大致相同的模式:我们需要一个(可能是分页的)对象列表,通常是QuerySet
,然后我们需要使用合适的模板创建TemplateResponse
使用该对象列表。
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.)
现在我们已经看到Django基于类的通用视图如何使用提供的mixins,让我们看看我们可以将它们组合起来的其他方法。 当然,我们仍然会将它们与内置的基于类的视图或其他基于类的基于视图的视图相结合,但是您可以解决的问题比Django提供的一系列罕见的问题开箱即用。
Warning
并非所有mixin都可以一起使用,并非所有基于泛型类的视图都可以与所有其他mixin一起使用。 这里我们举几个可行的例子;如果你想将其他功能集合在一起,那么你将不得不考虑在你正在使用的不同类之间重叠的属性和方法之间的交互,以及方法解析顺序将如何影响方法的哪个版本将按什么顺序调用。
Django的基于类的视图和基于类的视图mixins的参考文档将帮助您了解哪些属性和方法可能导致不同类和mixin之间的冲突。
如果有疑问,通常最好退出并基于查看
或TemplateView
,可能使用SingleObjectMixin
和MultipleObjectMixin
。 虽然你最终可能会编写更多的代码,但是对于其他人稍后会更容易理解,并且由于担心你的交互较少,你会省去一些思考。 (当然,您可以随时了解Django对基于类的通用视图的实现,以获得有关如何解决问题的灵感。)
SingleObjectMixin
with View¶如果我们想编写一个简单的基于类的视图,只响应POST
,我们将子类View
并编写一个post()
方法在子类中。 但是,如果我们希望我们的处理工作在从URL标识的特定对象上,我们将需要SingleObjectMixin
提供的功能。
我们将使用基于通用类的视图简介中使用的Author
模型来演示这一点。
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views 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:
from django.urls import path
from books.views import RecordInterest
urlpatterns = [
#...
path('author/<int:pk>/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
.
SingleObjectMixin
与ListView
¶ListView
提供内置分页,但您可能希望将所有链接(通过外键)的对象列表分页到另一个对象。 在我们的发布示例中,您可能希望对特定发布者的所有书籍进行分页。
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
Publisher
,我们只需覆盖get_queryset()
并使用Publisher
的反向外键管理员。Publisher
queryset for use in get_object()
get_object()
的默认实现来获取正确的Publisher
对象。
但是,我们需要显式传递queryset
参数,否则get_object()
的默认实现将调用get_queryset()
,我们已将其覆盖为返回Book
对象而不是Publisher
。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()
.
现在我们可以编写一个新的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().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().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 }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
通常,当您需要其功能时,可以使用TemplateResponseMixin
和SingleObjectMixin
。 如上所示,您可以将SingleObjectMixin
与ListView
组合在一起。 然而,当你尝试这样做时,事情变得越来越复杂,一个好的经验法则是:
Hint
您的每个视图应仅使用其中一组基于类的通用视图的mixins或视图:detail,list,editing和date。 例如,将TemplateView
(内置视图)与MultipleObjectMixin
(通用列表)结合起来很好,但是你可能会遇到组合SingleObjectMixin
的问题。 (通用细节)与MultipleObjectMixin
(通用列表)。
为了说明当你试图变得更复杂时会发生什么,我们展示了一个例子,当有一个更简单的解决方案时,牺牲了可读性和可维护性。 首先,让我们看一下将DetailView
与FormMixin
结合起来的天真尝试,使我们能够POST
一个Django Form
与我们使用DetailView
显示对象的URL相同。
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, 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.
我们的新AuthorDetail
如下所示:
# 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.urls 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().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().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.
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.
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().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.urls 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().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 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.
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:
"""
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 Serializing Django objects documentation for more information on how to correctly transform Django models and querysets into JSON.
这个mixin提供了一个render_to_json_response()
方法,其签名与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)
同样,我们可以将mixin与其中一个通用视图一起使用。 我们可以通过将JSONResponseMixin
与django.views.generic.detail.BaseDetailView
混合来制作我们自己的DetailView
版本 - (DetailView
之前混合模板渲染行为):
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)
然后,可以使用与任何其他DetailView
相同的方式部署此视图,但行为完全相同 - 除了响应的格式。
如果你想真正冒险,你甚至可以混合一个DetailView
子类,它能够返回 HTML和JSON内容,具体取决于HTTP请求的某些属性,例如作为查询参数或HTTP标头。 只需混合JSONResponseMixin
和SingleObjectTemplateResponseMixin
,并覆盖render_to_response()
的实现,以根据类型推迟到适当的渲染方法用户请求的响应:
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().render_to_response(context)
由于Python解析方法重载的方式,对super()。render_to_response(context)
的调用最终调用TemplateResponseMixin的
。render_to_response()
实现
Jan 17, 2018