Django:一个基于类的视图,带有mixin和dispatch方法

93ze6v8z  于 2022-12-27  发布在  Go
关注(0)|答案(5)|浏览(147)

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

在某些情况下,它不起作用,因为视图继承的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只被一个视图使用)来解决这个问题。
有什么合适的方法来解决这类问题吗?

cnjp1d6j

cnjp1d6j1#

我会编写自定义类,检查所有权限

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)的实现不需要重写调度方法
如果你使用了多重继承,并且其中一个父类需要改进,那么最好先改进它,这样可以保持代码的简洁。

o7jaxewo

o7jaxewo2#

这是一个老职位,但其他人可能会遇到,所以这里是我提出的解决方案。
当你说
"[...]我想为此视图添加一些权限或 * 设置一些初始变量 *,例如[...]"
除了在视图的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的代码要干净。

68de4m5k

68de4m5k3#

对于您给出的示例,我将使用django-braces中的UserPassesTestMixin

class GenerateReportView(UserPassesTestMixin, FormView):
    def test_func(self, user):
        return user.is_superuser or user.is_manager

如果这不适合您更复杂的逻辑,那么创建一个单独的mixin听起来是一个不错的方法,因为它很好地封装了复杂的逻辑。

    • 编辑**

As of django 1.9, the UserPassesTestMixin is now included in django: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin

72qzrwbm

72qzrwbm4#

这可以通过Django UserPassesTestMixin mixin或@user_passes_test装饰器来完成。

用户通过测试混合示例

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):
    ...
uurv41yg

uurv41yg5#

如果你覆盖了dispatch,正如你所注意到的,你的LoginRequiredMixin将停止工作。虽然你可以添加一个检查来查看用户是否通过了身份验证,但是仅仅这个检查还不足以处理重定向。
正如在另外两个答案中提到的,我也更喜欢将UserPassesTestMixin与包含逻辑的test_func()方法一起使用。

  • 以及登录所需的混合
  • 对于更复杂的情况(例如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

注意LoginRequiredMixinUserPassesTestMixin之前的用法。顺序是相关的。这意味着在检查test_func()之前,它将首先检查用户是否通过身份验证。如果用户未通过身份验证,则将被重定向到登录页面。
另外,我喜欢您使用if not (...)的方式,它经过了调整,

if not (...):
    return False

除此之外,由于您正在寻找更多的逻辑,您可以使用try/except块(它甚至可以调用其他函数)来实现这一点

try:
    # some logic
    return True
except:
    return False

return True意味着用户随后具有访问视图的许可,而return False将向用户给予403状态代码。

相关问题