Django:使用Mixin和dispatch方法的基于类的视图

28
通常,我使用类视图的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

它按预期工作:当匿名用户访问页面时,LoginRequiredMixindispatch方法被调用,然后将用户重定向到登录页面。

但是如果我想为此视图添加一些权限或设置一些初始变量,例如:

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)

在某些情况下,它可能无法正常工作,因为视图继承的 mixin 的 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 中会有一些更复杂的逻辑:检查权限、进行一些计算并将其存储在类属性中等等。

目前我通过从 mixin 中复制一些代码(就像上面的例子中那样)或者将视图的 dispatch 方法的代码复制到另一个 mixin 中,并在第一个 mixin 之后继承它以按顺序执行它们来解决这个问题(这不太好看,因为这个新的 mixin 只被一个视图使用)。

有没有什么正确的方法来解决这些问题?


首先调用 super(GenerateReportView, self).dispatch(*args, **kwargs),然后在你重写的 dispatch 方法内完成其余工作。 - Mihai Zamfir
2
@MihaiZamfir 这样做行不通,因为例如“LoginRequiredMixin”返回一个“HttpResponseRedirect”对象,所以如果我们将其存储在变量中,它不会重定向到登录视图,直到我们在“dispatch”方法的末尾返回此变量。 - cansadadeserfeliz
但您可以检查响应是否为重定向,然后继续派遣。 - Mihai Zamfir
2
问题在于混合处理的上游可能会导致有效的重定向。例如,存在继承层次结构,如(LoginRequiredMixin,CustomViewWhichRedirectsSometimes),现在,如果需要登录的调度返回重定向,则我们需要判断它是因为用户未经授权还是来自CustomViewWhichRedirectsSometimes的完全预期的重定向。 - okrutny
5个回答

6
我会编写自定义类来检查所有权限。
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)

实现GenerateReportView类(继承自SuperOrManagerPermissionsMixin和FormView)不需要重写dispatch方法。

如果您使用多重继承并且其中一个父类需要进行改进,最好先进行改进。这可以保持代码的清晰度。


4
对于你提供的例子,我会使用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

4
这是一篇旧帖子,但其他人可能也会遇到这个问题,以下是我的解决方案。
当你说
“[...]我想为这个视图添加一些权限或者设置一些初始变量,例如[...]”
而不是在你的视图的dispatch方法中设置那些初始变量,你可以编写一个单独的方法来设置这些变量,然后在你的get(和post如果需要)方法中调用该方法。它们在dispatch之后被调用,因此设置初始变量不会与您的mixin中的dispatch发生冲突。所以重写这个方法。
def set_initial_variables():
    self.hey = something
    return 

def get(blablabla):
    self.set_initial_variables()
    return super(blabla, self).get(blabla)

这样做比将你的mixin代码复制粘贴到视图的分发中更加干净。

3

您可以使用Django UserPassesTestMixin mixin或@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):
    ...

0

如果您重写了dispatch方法,就像您已经注意到的那样,您的LoginRequiredMixin将停止工作。虽然您可以添加一个检查来查看用户是否已经通过身份验证,但仅有这个检查并不足以处理重定向。

就像其他两个回答中提到的一样,我也更喜欢使用UserPassesTestMixin和包含逻辑的test_func()方法。我写这篇答案的原因是要展示它的用法:

  • 与LoginRequiredMixin一起使用
  • 针对更复杂的情况(例如带有try/excepts)

以下是我在您的情况下会怎么做:

from django.views.generic.edit import FormView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin

class GenerateReportView(LoginRequiredMixin, UserPassesTestMixin, FormView):
    template_name = 'reporting/reporting_form.html'
    form_class = ReportForm

    def test_func(self):
        if not (
            self.request.user.is_superuser or
            self.request.user.is_manager
        ):
            return False
        try:
            # some logic
            return True
        except:
            return False

请注意在UserPassesTestMixin之前使用LoginRequiredMixin的用法。顺序很重要。这意味着它将首先检查用户是否已通过身份验证,然后再检查test_func()。如果用户未经过身份验证,那么将被重定向到登录页面。

此外,我喜欢您如何使用if not (...),这是一种适应性强的方法。

if not (...):
    return False

此外,如果您正在寻找更多的逻辑,您可以使用try/except块来实现(甚至可以调用其他函数)。
try:
    # some logic
    return True
except:
    return False

return True的意思是该用户具有访问该视图的权限,而return False将向用户返回403状态码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接