Django:一个基于 class 的视图,带有 mixins 和 dispatch 方法
Django: a class based view with mixins and dispatch method
通常,我使用基于class 的视图的dispatch
方法来设置一些初始变量或添加一些基于用户权限的逻辑。
例如,
from django.views.generic import FormView
from braces.views import LoginRequiredMixin
class GenerateReportView(LoginRequiredMixin, FormView):
template_name = 'reporting/reporting_form.html'
form_class = ReportForm
def get_form(self, form_class):
form = form_class(**self.get_form_kwargs())
if not self.request.user.is_superuser:
form.fields['report_type'].choices = [
choice for choice in form.fields['report_type'].choices
if choice[0] != INVOICE_REPORT
]
return form
按预期工作:当匿名用户访问页面时,会调用LoginRequiredMixin的dispatch
方法,然后将用户重定向到登录页面。
但是如果我想为这个视图添加一些权限或者设置一些初始变量,例如
class GenerateReportView(LoginRequiredMixin, FormView):
def dispatch(self, *args, **kwargs):
if not (
self.request.user.is_superuser or
self.request.user.is_manager
):
raise Http404
return super(GenerateReportView, self).dispatch(*args, **kwargs)
在某些情况下它不起作用,因为 dispatch
视图继承的混入方法尚未被调用。因此,例如,为了能够请求用户的权限,我必须从 LoginRequiredMixin
:
重复验证
class GenerateReportView(LoginRequiredMixin, FormView):
def dispatch(self, *args, **kwargs):
if self.request.user.is_authenticated() and not (
self.request.user.is_superuser or
self.request.user.is_manager
):
raise Http404
return super(GenerateReportView, self).dispatch(*args, **kwargs)
这个例子很简单,但有时 mixin 中会有一些更复杂的逻辑:它检查权限,进行一些计算并将其存储在 class 属性中等。
现在我通过从 mixin 复制一些代码(如上面的示例)或将代码从视图的 dispatch
方法复制到另一个 mixin 并在第一个 mixin 之后继承它来解决它按顺序执行它们(这不是很漂亮,因为这个新的 mixin 只被一个视图使用)。
有什么好的方法可以解决这类问题吗?
对于您给出的示例,我将使用 django-braces 中的 UserPassesTestMixin
。
class GenerateReportView(UserPassesTestMixin, FormView):
def test_func(self, user):
return user.is_superuser or user.is_manager
如果这不适合您更复杂的逻辑,那么创建一个单独的 mixin 听起来是个不错的方法,因为它很好地封装了复杂的逻辑。
编辑
从 django 1.9 开始,UserPassesTestMixin 现在包含在 django 中:https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin
我会编写自定义 class,它会检查所有权限
from django.views.generic import FormView
from braces.views import AccessMixin
class SuperOrManagerPermissionsMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return self.handle_no_permission(request)
if self.user_has_permissions(request):
return super(SuperOrManagerPermissionsMixin, self).dispatch(
request, *args, **kwargs)
raise Http404 #or return self.handle_no_permission
def user_has_permissions(self, request):
return self.request.user.is_superuser or self.request.user.is_manager
# a bit simplyfied, but with the same redirect for anonymous and logged users
# without permissions
class SuperOrManagerPermissionsMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if self.user_has_permissions(request):
return super(SuperOrManagerPermissionsMixin, self).dispatch(
request, *args, **kwargs)
else:
return self.handle_no_permission(request)
def user_has_permissions(self, request):
return request.user.is_authenticated() and (self.request.user.is_superuser
or self.request.user.is_manager)
class GenerateReportView(SuperOrManagerPermissionsMixin, FormView):
#Remove next two lines, don't need it
def dispatch(self, *args, **kwargs):
#or put some logic here
return super(GenerateReportView, self).dispatch(*args, **kwargs)
并且 class GenerateReportView(SuperOrManagerPermissionsMixin, FormView) 的实现不需要覆盖调度方法
如果您使用多重继承并且其中一个父 class 需要一些改进,最好先改进它。它使代码更简洁。
这是一个旧的 post 但其他人可能会遇到,所以这是我提出的解决方案。
当你说
"[...]I want to add some permissions for this view or set some
initial variables, for example[...]"
您可以编写一个单独的方法来设置这些变量,然后在您的 get(和 post 如果需要)方法中调用该方法,而不是在视图的调度方法中设置这些初始变量.它们在调度后调用,因此设置初始变量不会与混合中的调度冲突。
所以重写方法
def set_initial_variables():
self.hey = something
return
def get(blablabla):
self.set_initial_variables()
return super(blabla, self).get(blabla)
这可能比在您的视图调度中复制和粘贴您的 mixin 代码更干净。
可以用 Django UserPassesTestMixin mixin or @user_passes_test 装饰器来完成。
UserPassesTestMixin 示例
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperUserOrManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
if self.request.user.is_superuser or self.request.user.is_manager:
return True
return False
class MyView(LoginRequiredMixin, SuperUserOrManagerRequiredMixin, View):
...
通常,我使用基于class 的视图的dispatch
方法来设置一些初始变量或添加一些基于用户权限的逻辑。
例如,
from django.views.generic import FormView
from braces.views import LoginRequiredMixin
class GenerateReportView(LoginRequiredMixin, FormView):
template_name = 'reporting/reporting_form.html'
form_class = ReportForm
def get_form(self, form_class):
form = form_class(**self.get_form_kwargs())
if not self.request.user.is_superuser:
form.fields['report_type'].choices = [
choice for choice in form.fields['report_type'].choices
if choice[0] != INVOICE_REPORT
]
return form
按预期工作:当匿名用户访问页面时,会调用LoginRequiredMixin的dispatch
方法,然后将用户重定向到登录页面。
但是如果我想为这个视图添加一些权限或者设置一些初始变量,例如
class GenerateReportView(LoginRequiredMixin, FormView):
def dispatch(self, *args, **kwargs):
if not (
self.request.user.is_superuser or
self.request.user.is_manager
):
raise Http404
return super(GenerateReportView, self).dispatch(*args, **kwargs)
在某些情况下它不起作用,因为 dispatch
视图继承的混入方法尚未被调用。因此,例如,为了能够请求用户的权限,我必须从 LoginRequiredMixin
:
class GenerateReportView(LoginRequiredMixin, FormView):
def dispatch(self, *args, **kwargs):
if self.request.user.is_authenticated() and not (
self.request.user.is_superuser or
self.request.user.is_manager
):
raise Http404
return super(GenerateReportView, self).dispatch(*args, **kwargs)
这个例子很简单,但有时 mixin 中会有一些更复杂的逻辑:它检查权限,进行一些计算并将其存储在 class 属性中等。
现在我通过从 mixin 复制一些代码(如上面的示例)或将代码从视图的 dispatch
方法复制到另一个 mixin 并在第一个 mixin 之后继承它来解决它按顺序执行它们(这不是很漂亮,因为这个新的 mixin 只被一个视图使用)。
有什么好的方法可以解决这类问题吗?
对于您给出的示例,我将使用 django-braces 中的 UserPassesTestMixin
。
class GenerateReportView(UserPassesTestMixin, FormView):
def test_func(self, user):
return user.is_superuser or user.is_manager
如果这不适合您更复杂的逻辑,那么创建一个单独的 mixin 听起来是个不错的方法,因为它很好地封装了复杂的逻辑。
编辑
从 django 1.9 开始,UserPassesTestMixin 现在包含在 django 中:https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin
我会编写自定义 class,它会检查所有权限
from django.views.generic import FormView
from braces.views import AccessMixin
class SuperOrManagerPermissionsMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated():
return self.handle_no_permission(request)
if self.user_has_permissions(request):
return super(SuperOrManagerPermissionsMixin, self).dispatch(
request, *args, **kwargs)
raise Http404 #or return self.handle_no_permission
def user_has_permissions(self, request):
return self.request.user.is_superuser or self.request.user.is_manager
# a bit simplyfied, but with the same redirect for anonymous and logged users
# without permissions
class SuperOrManagerPermissionsMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if self.user_has_permissions(request):
return super(SuperOrManagerPermissionsMixin, self).dispatch(
request, *args, **kwargs)
else:
return self.handle_no_permission(request)
def user_has_permissions(self, request):
return request.user.is_authenticated() and (self.request.user.is_superuser
or self.request.user.is_manager)
class GenerateReportView(SuperOrManagerPermissionsMixin, FormView):
#Remove next two lines, don't need it
def dispatch(self, *args, **kwargs):
#or put some logic here
return super(GenerateReportView, self).dispatch(*args, **kwargs)
并且 class GenerateReportView(SuperOrManagerPermissionsMixin, FormView) 的实现不需要覆盖调度方法
如果您使用多重继承并且其中一个父 class 需要一些改进,最好先改进它。它使代码更简洁。
这是一个旧的 post 但其他人可能会遇到,所以这是我提出的解决方案。
当你说
"[...]I want to add some permissions for this view or set some initial variables, for example[...]"
您可以编写一个单独的方法来设置这些变量,然后在您的 get(和 post 如果需要)方法中调用该方法,而不是在视图的调度方法中设置这些初始变量.它们在调度后调用,因此设置初始变量不会与混合中的调度冲突。 所以重写方法
def set_initial_variables():
self.hey = something
return
def get(blablabla):
self.set_initial_variables()
return super(blabla, self).get(blabla)
这可能比在您的视图调度中复制和粘贴您的 mixin 代码更干净。
可以用 Django UserPassesTestMixin mixin or @user_passes_test 装饰器来完成。
UserPassesTestMixin 示例
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperUserOrManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
if self.request.user.is_superuser or self.request.user.is_manager:
return True
return False
class MyView(LoginRequiredMixin, SuperUserOrManagerRequiredMixin, View):
...