“为什么你需要这个?”,你可能会问。 嗯,这是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不允许你这样做,你如何将当前用户传递给该方法调用?这段代码将无可救药地失败,并且最终将遵循其中一条备用路径:
if
templatetags中使用计算结果。那么,如果有可能在调用它时自动将request.user
注入函数中。或更好!如果你可以注入整个request
对象怎么办?这甚至可能吗?
答案 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 %}
这会有效!