【问题标题】:Cleanest way to decorate Django's dispatch method装饰 Django 调度方法的最干净的方法
【发布时间】:2018-11-07 05:51:38
【问题描述】:

我偶然发现了一个用于为请求方法提供一些参数的代码。问题是我不确定这是否是处理这种情况的最干净的方式

def check_permissions(check_mixins):
"""
:param check_mixins: is given to the inner decorator
Decorator that will automatically populate some parameters when
using dispatch() toward the right method (get(), post())

"""
  def _decorator(_dispatch):
    def wrapper(request, *args, **kwargs):

如果这里的方法定义中没有传入“self”会不会有问题...

        for mixin in check_mixins:
            kwargs = mixin.check(request, *args, **kwargs)
            if isinstance(kwargs, HttpResponseRedirect):
                return kwargs

        return _dispatch(request, *args, **kwargs)

    return wrapper

  return _decorator


class UserLoginMixin(object):

def check(request, *args, **kwargs):

...这里呢?在我的 IDE 中看起来很丑

    user = request.user
    if user.is_authenticated() and not user.is_anonymous():
        kwargs['user'] = user
        return kwargs
    return redirect('user_login')

class AppoExistMixin(object):

def check(request, *args, **kwargs):

这里也是……

    appo_id = kwargs['appo_id']
    try:
        appoff = IdAppoff.objects.get(id=appo_id)
        kwargs['appoff'] = appoff
        del kwargs['appo_id']
        return kwargs
    except IdAppoff.DoesNotExist:
        pass
    messages.add_message(request, messages.ERROR,
                         "Item doesn't exist!")
    return redirect('home')



class SecurityMixin(View):
"""
    Mixin that dispatch() to the right method with augmented kwargs.
    kwargs are added if they match to specific treatment.
"""

data = []

def __init__(self, authenticators):
    super(SecurityMixin, self).__init__()
    # Clearing data in order to not add useless param to kwargs
    self.data.clear()
    # Build the list that contain each authenticator providing
    # context increase
    for auth in authenticators:
        self.data.append(auth)

@method_decorator(check_permissions(data))

为什么是 data 而不是 self.data ?怎么可能?

def dispatch(self, request, *args, **kwargs):
    return super(SecurityMixin, self).dispatch(request, *args, **kwargs)

然后每个视图都继承自 SecurityMixin 并获得authenticators = [UserLoginMixin, ...] 作为类属性。

有时的问题(我无法重现错误...)是在正确设置 URL 定义时,我在增强的 kwargs 上遇到了 KeyError。例如:

appo_id = kwargs['appo_id']
KeyError: 'appo_id'Exception

我已经找了好几个小时了,但似乎我永远也找不到解决方案……这有点令人沮丧。

如果有人可以提供帮助,将不胜感激。

【问题讨论】:

    标签: django python-decorators


    【解决方案1】:

    我有一种预感,对类属性的不当处理是错误的。


    类与实例

    每次调用SecurityMixin.__init__ 时,都会覆盖class 属性data

    class A:
        data = []
    
        def __init__(self, *args):
            self.data.clear() # self.data references the class attribute
            for x in args:
                self.data.append(x)
    
        x = A('foo')
        # A.data = ['foo']
        # x.data = ['foo']
        y = A('bar')
        # A.data = ['bar']
        # y.data = ['bar']
        # x.data = ['bar'] !!
    

    但是:

    class A:
        data = ['I am empty']
    
        def __init__(self, *args):
            self.data = [] # redeclaring data as an instance attribute
            for x in args:
                self.data.append(x)
    
        x = A('foo')
        # A.data = ['I am empty']
        # x.data = ['foo']
        y = A('bar')
        # A.data = ['I am empty']
        # y.data = ['bar']
        # x.data = ['foo'] 
    

    class 属性data 被传递给装饰器(你不能将实例属性传递给方法装饰器,即 self.data,因为在装饰器声明期间该实例还不存在)。 但是,如果传入('self' 参数),包装函数确实可以访问实例。

    Django 的 method_decorator 删除了这个 self 参数;该装饰器用于将 function 装饰器(不会隐式获取 self 参数)转换为 method 装饰器(隐式获取 self 参数)。这就是为什么您不必将self 包含在各种混合检查方法的参数列表中,因为它已被method_decorator 删除。简单来说:使用method_decorator 用函数装饰器来装饰方法。在这里阅读它decorating CBVs

    知道了这一点,我不太确定为什么 check_permissions 应该是一个 function 装饰器,因为它现在只用于装饰方法。 你可以用check_permissions 本身来装饰调度:

    def check_permissions(_dispatch):
        def _decorator(self, request, *args, **kwargs): # adding self
            for mixin in self.data: # referencing the INSTANCE data
                kwargs = mixin.check(request, *args, **kwargs) 
                if isinstance(kwargs, HttpResponseRedirect):
                    return kwargs
            return _dispatch(self, request, *args, **kwargs) # don't forget self here
        return _decorator
    
    
    @check_permissions
    def dispatch(self, request, *args, **kwargs):
        ...
    

    也许某些视图正在尝试检查 AppoExistMixin,因为它在该视图的数据列表中,尽管它不应该是 - 并且视图的 kwargs 不包含“appo_id”。您也可以通过将所需的检查混合直接传递给装饰器来尝试显式:@method_decorator(check_permissions([UserLoginMixin, ...]))。这样您就不必搞乱类与实例属性。

    另外...您应该将data 重命名为您不太可能用您自己的变量覆盖的名称。

    如果你想变得超级懒惰,你可以这样做:

    appo_id = kwargs.get('appo_id',False)
    if not appo_id: return kwargs
    

    但这只会修复该视图中的特定错误。忽视症状而不是治愈疾病。


    更多解释:

    功能与方法。 check_permissions 是一个函数,而dispatch() 是一个方法。您不能简单地在方法上使用函数装饰器:一方面,因为隐式参数 selfmethod 所属的实例)也被传递给装饰器,尽管它可能不会期望它.
    这就是 django 的 method_decorator 通过在装饰器中删除和存储 self 来发挥作用的地方。比较两个签名:wrapper(request, *args, **kwargs)_decorator(self, request, *args, **kwargs)。在前者中,method_decorator'吸收'self 在调用函数装饰器之前。 把它想象成一个适配器,一个装饰器的装饰器,它“弥合了函数和方法之间的鸿沟”。如果您不想/不能更改装饰器,请使用它。
    但是,在您的情况下,您可以更改装饰器以使其与方法一起使用 - 因此您不需要 django 的 method_decorator

    【讨论】:

    • 非常感谢您的回答!我也许能解决这个问题!
    • 让我知道发生了什么。混淆类实例和可变方法参数click 确实会掩盖真正的问题。
    • 我在大约 2 周前修改了代码,将数据类属性转换为实例属性。我用你的代码修改了装饰器的定义。它真的很好用。不再有 KeySurpriseError :)
    • 但我不确定我是否真正理解了你带来的前装修师和新装修师之间的真正区别
    • @Rick Star:更新了我的帖子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-15
    • 1970-01-01
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多