关于Django中的Mixin

面向对象的技术例如Mixin(多继承)可以将代码分解成可重用的组件。

Mixin 是多继承的一种形式,其来自多个父类的行为和属性可以组合在一起。

例如,在通用的基于类的视图中,有一个Mixin 叫做 TemplateResponseMixin,它的主要目的是定义render_to_response() 方法。它与View 基类的组合是TemplateView 类,这个类可以调度请求给正确的方法(View 基类中定义的行为),同时还具有一个render_to_response() 方法,该方法使用template_name 属性来返回一个TemplateResponse 对象( TemplateResponseMixin 中定义的行为)。

Mixin 是重用多个类的代码的一种极好的方法,但是它们需要一些代价。代码在Mixin中越分散,子类将越难阅读并知道它的行为;如果你的继承很深,将难以知道应该覆盖哪一个Mixin 的方法。

还要注意,只能继承一个通用视图 —— 也就是说,只能有一个父类继承View,其它的父类必须是Mixin。继承多个继承自View的类 将不能像预期的那样工作—— 例如,试图在一个列表的顶部使用表单而组合ProcessFormViewListView

举个栗子,在验证用户登录状态的时候,一方面我们可以使用Django提供的装饰器@login_required,还可以自己尝试写一个函数验证用户的登录状态。

我们可以在util中自定义一个用于验证用户是否登录的类,每个需要用户登录后才能进行的操作,我们都要继承于这个类,下面是mixin_utils.py

1
2
3
4
5
6
7
8
9
10
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
# 自己定义验证用户是否登录的视图函数
class LoginRequiredMixin(object):
@method_decorator(login_required(login_url='/users/login/'))
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)

这里我们使用了装饰器@method_decorator

course/views.py中,每次用户点击我要学习都必须是登录的状态,所以需要这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from utils.mixin_utils import LoginRequiredMixin
# 课程详情
class CourseDetailView(LoginRequiredMixin, View):
def get(self, request, course_id):
# 获取当前的课程
...
return render(request,"course-detail.html",{
'course':cur_course,
'has_org_fav':has_org_fav,
'has_course_fav':has_course_fav,
'all_lessons': all_lessons,
'lesson_mun': lesson_num,
'cur_course_org': cur_course_org,
'reco_courses': reco_courses,
})
`


基于类的视图处理表单

一个基本的用于处理表单的视图函数可能是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import MyForm
def myview(request):
if request.method == "POST":
form = MyForm(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
form = MyForm(initial={'key': 'value'})
return render(request, 'form_template.html', {'form': form})

我们首先要定义表单的字段,在这里是MyForm,在视图函数中我们将其实例化,在request中取出数据填入到表单中,并将其存储到数据库中,最后我们还将表单返回给前端页面。

一个基于类的视图看上去是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.generic import View
from .forms import MyForm
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})


还可以在URLconf中装饰基于类的视图,举个例子:

1
2
3
4
5
6
7
8
9
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = [
url(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
url(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]


装饰类

若要装饰基于类的视图的每个实例,你需要装饰类本身。可以将装饰器运用到类的dispatch() 方法上来实现这点。

类的方法和独立的函数不完全相同,所以你不可以直接将函数装饰器运用到方法上 —— 你首先需要将它转换成一个方法装饰器。method_decorator 装饰器将函数装饰器转换成方法装饰器,这样它就可以用于实例方法上。

1
2
3
4
5
6
7
8
9
10
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)

在这个例子中,ProtectedView 的每个实例都将有登录保护。