Django的视图分为FBV(基于函数的视图)和CBV(基于类的视图)两种实现方法。FBV比较简单,但是缺点在于很难通过继承等方式重复利用已有代码;CBV使用起来略微繁琐一些,但是却很容易通过类继承等方法重复利用已有代码。当然,FBV如果通过一些方法把内部功能抽取出来做成单独的外部函数也是可以实现重复利用的,只不过CBV的继承更加直观。
Python装饰器用于函数的时候是很方便的,类中的方法与独立函数略有不同,所以用于FBV的装饰器不能直接用在CBV上,所以说用于类就不太方便。当Django使用CBV时,装饰器的使用就要比FBV繁琐一些。
大体上分为3种方法:
Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器
- from django.views import View
- from django.utils.decorators import method_decorator
- from lib.decorator import my_decorator
-
- class AddClass(View):
-
- @method_decorator(my_decorator)
- def get(self, request):
- return render(request, "add_class.html")
-
- def post(self, request):
- class_name = request.POST.get("class_name")
- models.Classes.objects.create(name=class_name)
- return redirect("/class_list/")
比如说开发者在lib.decorator中自己开发了一个装饰器my_decorator,就可以通过
@method_decorator(my_decorator)
把这个my_decorator加到CBV类内特定的函数上。比如说上面的例子就是加载了get函数上,这样当访问者通过GET方法访问到这个AddClass时,就会调用这个装饰器。
在CBV的各个方法中,有一个比较特殊的方法dispatch,这个方法执行的时间比get、post等函数更早,所以可以添加一些对request.method无关的装饰器
- class AddClass(View):
-
- def get(self,request, year):
- print('this is add method')
- return return render(request, "add_class.html")
-
- @method_decorator(my_decorator)
- def dispatch(self, request, *args, **kwargs):
- print('this is dispatch method')
- return None
要特别说明的是:Django自带的各个_required方法都无法加在get、post等方法上,而是必须要加在dispatch方法上。另外csrf_exempt和csrf_protect这两个装饰器也只能加在dispatch方法上。
通过method_decorator可以给类加装饰器,在添加的时候只要增加一个name参数就可以指定该装饰器是对CBV内哪个方法来添加
- @method_decorator(my_decorator_post, name='post')
- @method_decorator(my_decorator_get, name='get')
- class AddClass(View):
-
- def get(self, request):
- return render(request, "add_class.html")
-
- def post(self, request):
- class_name = request.POST.get("class_name")
- models.Classes.objects.create(name=class_name)
- return redirect("/class_list/")
-
在
https://stackoverflow.com/questions/6069070/how-to-use-permission-required-decorators-on-django-class-based-views
对如何在CBV上加装饰器有一些讨论,其中提到,其实可以在路由文件urls.py中加装饰器。
- from django.urls import path
- from django.contrib.auth.decorators import login_required
- from django.views.decorators.csrf import csrf_exempt
- from . import views
-
- app_name = 'rbac' # pylint: disable=invalid-name
- urlpatterns = [
- path(r'Login/', csrf_exempt(views.LoginView.as_view()), name='Login'),
- path(r'Logout/', login_required(views.LogoutView.as_view()), name='Logout'),
- ]
这种方法的效果和在dispatch方法上加装饰器是一样的,代码可以少很多;不过缺点就是在视图文件views.py中看不出来,有可能会导致误解,如果urls.py和view.py是两个不同的人编写的,可能会冲突。
其实还有一种方法也可以实现类似的功能,不过这种方法已经不能被视为装饰器了。
其实装饰器的功能说到底其实就是在被装饰的函数运行前和运行后进行一些特定的运算罢了。如果把这些运算直接写到类当中,其实也是可以的。Django在CBV的基类View中在__init__构造函数中保留了特定的接口,只不过默认的内容是pass。采用一些特定的Mixin类混入View类中,可以用Mixin类中的特定函数替换掉View默认的pass,这样就可以把相关代码加入到特定的位置来执行。
https://docs.djangoproject.com/zh-hans/4.1/topics/auth/default/#the-loginrequired-mixin
就对login_required装饰器的使用给出了Mixin方法。只要在构造CBV的类时
- from django.contrib.auth.mixins import LoginRequiredMixin
-
- class MyView(LoginRequiredMixin, View):
- login_url = '/login/'
- redirect_field_name = 'redirect_to'
在类的基类列表中增加一个LoginRequiredMixin类,就可以实现同样的功能,而且还可以在CBV中直接操作相关变量如login_url、redirect_field_name等。功能更加强大,当然,代码量也要多一些。
还有一个更多内容的例子
- from django.contrib.auth.mixins import UserPassesTestMixin
-
- class MyView(UserPassesTestMixin, View):
-
- def test_func(self):
- return self.request.user.email.endswith('@example.com')
采用UserPassesTextMixin基类混入的话,不仅可以定义个别变量,甚至对于请求执行时的特别函数都可以重载,在这里可以根据需要设定特殊的功能。
关于在View中混入类,更多的介绍参见
https://docs.djangoproject.com/zh-hans/4.1/topics/class-based-views/mixins/