是否可以自动将请求注入模型方法?

时间:2015-07-21 18:43:18

标签: django django-models django-templates django-views

“为什么你需要这个?”,你可能会问。 嗯,这是Django中非常常见的场景。

遵循“瘦视图,胖模型”规则,我们非常鼓励人们在模型中放置与模型相关的所有逻辑。因此,在模型中放置所有这些方法以检查当前用户是否有权对给定模型实例执行某些操作是很常见的。

这可能会给你敲响声音:

class SomeModel(models.Model):
    ...
    ...
    def allow_editing(self, user):
        ...

    def allow_super_awkward_action(self, user):
        ...

所有这一切都希望能够在模板中做到这样的事情:

{% if object.allow_super_awkward_action %}
   place some link to go to the super awkward action
{% endif %}

问题是:如果Django不允许你这样做,你如何将当前用户传递给该方法调用?这段代码将无可救药地失败,并且最终将遵循其中一条备用路径:

  • 在视图中调用方法,将结果存储在变量中,将变量传递给模板上下文,然后检查变量。还有很长的路要走,想象你需要在几个模型方法中对几个视图执行此操作(这可能不是纯粹的DRY违规,但非常接近。)
  • 创建模板过滤器以将任意参数传递给函数。我认为它甚至不会起作用,因为过滤器会获得评估函数的结果(而不是函数itselt),而且此时为时已太晚。
  • 创建模板标记以调用向其传递任意参数的函数。这是可能的,但是再次,祝你好运实现“as”行为,这样你就可以在if templatetags中使用计算结果。

那么,如果有可能在调用它时自动将request.user注入函数中。或更好!如果你可以注入整个request对象怎么办?这甚至可能吗?

1 个答案:

答案 0 :(得分:1)

这是可能的,它以装饰者的形式来到我身边。 我知道,它有点模糊,但我们使用的是Python,Python允许一点点黑暗;)

这里搜索堆栈帧中的HttpRequest实例,如果找到它,它会将它注入到修饰函数中。 (如果您想知道,断言可以帮助您进行防御性编码:可能找不到请求对象,或者如果额外的参数没有默认值,则函数调用仍然可能在模板中失败等。)

import inspect
from django.http import HttpRequest
from django.utils import six

def inject_request(func):
    argspec = inspect.getargspec(func)
    assert 'request' in argspec.args, \
        "There must be a 'request' argument in the function."
    index = argspec.args.index('request')
    assert index in [0, 1], \
        "The 'request' argument must be, at most, the second positional argument."
    assert len(argspec.args) - len(argspec.defaults or []) == index, \
        "All arguments after (and including) 'request' must have default values."
    def wrapper(*args, **kwargs):
        if (index < len(args) and args[index]) or kwargs.get('request', None):
            return func(*args, **kwargs)
        request = None
        frame = inspect.currentframe().f_back
        while frame and not request:
            for v_name, v_object in six.iteritems(frame.f_locals):
                if isinstance(v_object, HttpRequest):
                    request = v_object
                    break
            frame = frame.f_back
        if request:
            kwargs.setdefault('request', request)
        return func(*args, **kwargs)
    return wrapper

在你的模特中,你可以这样做:

class SomeModel(models.Model):
    ...
    ...
    @inject_request
    def allow_editing(self, request=None):
        ...
    @inject_request
    def allow_super_awkward_action(self, request=None):
        ...

我们使用模板中的普通方法调用恢复了幸福:

{% if object.allow_super_awkward_action %}
    place link to the action
{% endif %}

这会有效!