前言
Django的类视图提供了许多功能,但有时你可能仅仅只希望用到其中一两个功能。例如,你可能仅仅只想实现一个渲染模板并生成响应的视图,但是又不想使用TemplateView,使用函数视图的话,又会出现重复的代码逻辑。出于这个原因,Django还提供了许多功能独立的混合器,即mixins,例如,仅仅实现渲染模板的功能可以使用 TemplateResponseMixin。通过继承不同的mixin,我们可以实现各种功能。Django的mixins其实正是组合模式的一种体现。
一、SimpleMixins
在类视图中有两个核心的Mixin,它们用于提供一致的接口来处理模板,这两个核心的Mixin也称为SimpleMixins,分别是TemplateResponseMixin和 ContextMixin。
(1)ContextMixin
class django.views.genric.base.ContextMixin
属性 :
- extra_context :一个包含上下文数据的字典。你也可以向它添加更多的参数以此来修改上下文数据。例子:
from django.views.genric import TemplateView
TemplateView.as_view(extra_context={'title': 'Custom Title'})
TemplateView继承了ContextMixin,因此具备该属性。
方法:
- get_context_data(kwargs) :返回传递到模板的上下文字典数据,如果kwargs被提供,那么该数据也会被传递进上下文字典中。示例:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['number'] = random.randrange(1, 100)
return context
需要注意的是,通过get_context_data()我们可以得到全部的上下文数据,这类方法被传递进模板中可能是不安全的,用户的任何行为都有可能导致数据的非法篡改。比如:
I will now delete this valuable data. {{ data.delete }}
这样肯定不行,我们将data暴露出去,但不希望任何人可以篡改或者删除它,那么就不能暴露它的delete()或者sava()方法,Django为了防止这种情况,设置了一个alters_data属性,当alters_data属性为True时,该方法将不会被模板系统调用。
如下:
def sensitive_function(self):
self.database_record.delete()
sensitive_function.alters_data = True
通过这种方法,delete()和save()就可以被隐藏起来,你应该也要考虑在涉及到安全问题时给你的方法加上alters_data属性。
(2)TemplateResponseMixin
class django.views.generic.base.TemplateResponseMixin
该混合器提供了一种机制,用于在适当的上下文中构造TemplateResponse。要使用的模板是可自定义的,你还可以通过继承它进一步定制功能。
属性:
- template_name :要使用的模板,它的值应该是一个字符串。
- template_engine :用于加载模板的引擎,默认值是None,这个时候Django会在所有的配置引擎中搜索模板。
- response_class :render_to_response()方法返回的响应类。默认是TemplateResponse。如果你需要自定义模板或者自定义上下文数据,请创建TemplateResponse的子类并将它赋值给response_class。
- content_type :content_type作为一个关键字参数传递给response_class,默认值是None,这时Django将使用 ‘text/html’ 作为content type。
方法:
- render_to_response(context, **response_kwargs) :返回一个response_class的实例。如果你额外提供了一些关键字参数,那么这些数据也将被作为上下文数据传递给response_class。
- get_template_names() :返回一个包含所有模板的列表,并默认使用第一个模板。如果template_name已经被指定了,则只返回列表。
二、DetailView与ListView依赖的Mixins
DetailView与ListView是使用非常广泛的一类通用视图,这两个视图依赖于四个Mixin,这些Mixins提供的功能不管是在处理单个对象还是多个对象时都提供了非常有用的帮助。
DetailView
为了展示对象的细节,我们一般会做两件事:查找将被展示的对象并创建TemplateResponse,然后将该对象作为上下文数据传递给合适的模板。
为了找到这个对象,DetailView依赖于SingleObjectMixin,它提供了一个get_object()方法,该方法会从urlconf中的参数里捕获pk或者slug,并以此为依据来进行查找,它还会从model参数或者queryset参数中进行查找,当然前提是这些参数被提供了的话。SingleObjectMixin也重写了get_context_data()方法,以此来返回合适的上下文模板数据。
在生成TemplateResponse这一步骤中,DetailView使用了SingleObjectTemplateResponseMixin,该Mixin继承自TemplateResponseMixin,并重写了它的get_template_names()方法,以便在处理实际对象时提供更灵活的默认设置。
接下来对这两个Mixin进行详解介绍:
(1)SingleObjectMixin
class django.views.generic.detail.SingleObjectMixin
该Mixin提供了一种查找与当前HTTPRequest相关联的对象的机制。
属性:
- model :此视图基于的模型。model = Foo与query = Foo.objects.all()是等效的。
- queryset :一个视图基于一个模型,但有可能该视图仅仅只想用到该模型中的部分数据,那么这个时候使用queryset就好了,如果queryset和model同时被提供,那么queryset将覆盖model。
【注意】queryset提供有可以直接修改数据的接口,比如delete(),而在queryset上的修改是可以直接作用于数据库的,因此在使用queryset时必须格外小心,要么你只调用它的all()方法,要么你用get_query_set()方法获得queryset的copy,再进行相关的数据处理。
- slug_field :模型包含的slug字段的值,默认是 ‘slug’。
- slug_url_kwarg :在urlconf中包含slug参数的对应的值,默认是’slug’。
- pk_url_kwarg :类似于slug_url_kwarg,默认是’pk’。
- context_object_name :用于指定要在上下文中使用的变量的名称。
- query_pk_and_slug :该值为True的话,get_object()方法将同时以pk和slug为参考进行查找,默认是False。
【注意】该属性可以帮你减轻不安全的直接对象引用的危险,当应用程序允许通过主键来方法某个对象时,有恶意的用户可能以此来获得所有的对象,因为pk暴露在url中并且它是自增的。因此采用pk和slug兼备才能查找到对象的策略可以降低这种安全风险。
方法:
- get_object() :查找并返回一个该视图需要使用的对象,即需要呈现详情页面的那个对象。如果queryset参数被提供,那么该方法将以queryset作为查找源进行查找,如果未被提供,将调用get_queryset()方法以此来获得queryset。该方法默认使用基于pk的查找,如果找不到pk,则使用基于slug的查找。如果query_pk_and_slug是True,则使用同时给予pk和slug的查找。
- get_queryset() :返回queryset。如果queryset未被指定,它将自动创建一个queryset并调用all()方法。
- get_context_object_name(obj) :返回context_object_name,如果context_object_name没有被指定,则默认返回 model_name_detail,例如,模型Article对应的context_object_name就是article_detail。
- **get_context_data(kwargs) :返回上下文数据。这将返回self.object以及其它可能存在的额外数据。
- get_slug_field() :返回slug的值,默认是模型中的slug字段的名字。
(2)SingleObjectTemplateResponseMixin
class django.views.generic.detail.SingleObjectTemplateResponseMixin
该Mixin用于基于object构造TemplateResponse,它要求继承自它的视图提供一个self.object属性,它的值通常是视图所基于的模型中的一个对象。不过该属性也可以为None,如果该视图想要自己创建一个新的object的话。
继承:
- 继承自TemplateResponseMixin。
属性:
- template_name_field :以self.object的名字指定模板的名称。
- template_name_suffix :指定模板的后缀名,默认是 ‘_detail’。
方法:
- get_template_names() :返回一个包含候选模板名称的列表。这将包括:
template_name
template_name_field
<app_label>/<model_name><template_name_suffix>.html
ListView
LIstView与DetailView类似,它们遵循相同的模式,只不过DetailView是获取一个对象,ListView是获取一个对象的列表,类似一个QuerySet。
为了获得这个列表,ListView使用了 MultipleObjectMixin,它提供了两个方法,get_queryset()和 paginate_queryset()。和SingleObjectMixin不同的是,MultipleObjectMixin不需要urlconf中的参数来确定对象,因此默认情况下quryset或者model的值将作为对象列表。当然,这里的get_queryset()也被重写了,以便能动态的改变对象列表的内容。
为了返回一个TemplateResponse对象,ListView还使用了 MultipleObjectTemplateResponseMixin。
(1)MultipleObjectMixin
class django.views.generic.list.MultipleObjectMixin
该Mixin被用来处理对象列表。
继承:
- django.views.generic.base.ContextMixin
属性:
- allow_empty :当没有对象可展示时是否允许呈现一个空的页面,默认值是True,当它为False并且对象列表为空时会抛出404错误。
- model :参考DetailVIew。
- queryset :参考DetailVIew。
- ordering :指定queryset的排序方法,类似于order_by()。
- paginate_by :一个整型数,指定每页有多少对象。
- paginate_orphans :一个整型数,指定最后一页可以包含多少上一页的“溢出数据”,该参数可以避免最后一页对象非常少的情况。
- page_kwarg :指定分页数据的查询参数的名字,该参数的值将被用于request.GET中,或者用于url后缀中,用于查询特定页的对象,默认值是’page’。
- paginator_class :指定用于分页的paginator。默认情况下它是 django.core.paginator.Paginator,如果你自定义了paginator,并且它没有和 django.core.paginator.Paginator 一致的构造器接口,这时你还要去提供一个get_paginator()方法。
- context_object_name :参考DetailVIew。
【注】
如果你使用了分页,那么你可以通过分页来获取指定页,这通常有两种使用方式:
一种是在urlconf中设置page参数:
path('objects/page<int:page>/', PaginatedView.as_view()),
一种是通过查询参数传递:
/objects/?page=3
还有,页码是从1开始,不是从0开始。
如果你想快速获取最后一页的数据,还可以考虑这样:
/objects/?page=last
方法:
- get_queryset() : 参考DetailVIew。
- get_ordering() :返回一个字符串,它指定queryset应该以什么方式排序,默认返回ordering属性的值。
- paginate_queryset(queryset, page_size) :返回一个四元组,它将包含:(paginator, page, object_list, is_paginated)。
- get_paginate_by(queryset) :返回每页的对象的数目,默认返回paginate_by。
- get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) :返回paginator的实例对象,默认情况下 paginator_class 将会被实例化并返回。
- get_paginate_orphans() :默认返回paginate_orphans。
- get_allow_empty() :默认返回 allow_empty 的值。
- get_context_object_name(object_list) : 返回context_object_name,如果context_object_name没有被指定,则默认返回 model_name_list,例如,模型Article对应的context_object_name就是article_list。
- get_context_data(**kwargs) :返回用于显示对象列表的上下文数据,它最基本的将要包含以下四个数据:object_list,is_paginated(布尔值,表示是否分页),paginator( django.core.paginator.Paginator的实例对象,页面未分页则为None),page_obj( django.core.paginator.Page的实例,页面未分页则为None)。
(2)MultipleObjectTemplateResponseMixin
class django.views.generic.list.MultipleObjectTemplateResponseMixin
该Mixin用于基于object_list构造TemplatResponse,它要求使用它的视图提供一个self.object_list属性。
继承:
- TemplateResponseMixin
属性和方法:
- template_name_suffix :模板的后缀,默认是’_list’。
- get_template_names() :返回模板名称的列表,这将包括 template_name(如果它被提供的话),<app_label>/<model_name><template_name_suffix>.html。
接下来举一个例子:
现在,我们想要编写一个只响应post方法的视图,并且我们希望该视图可以处理某个单个对象,该对象可以从urlconf中捕捉到的关键参数获得。
那么想一想,只响应post方法,那么在类中定义一个post方法就好了,当然该类可以继承自View(你最好让它继承自View,继承自其它的类视图的话,可能会造成许多方法与属性的重叠,给逻辑处理带来不便),然后,我们要让它能够处理一个特定对象,那么自然想到了SingleObjectMixin。
于是views.py如下:
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):
model = Author
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
urls.py:
from django.urls import path
from books.views import RecordInterest
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]