是否可以自动将请求注入模型方法?
Is it possible to automatically inject the request into model methods?
"Why do you need this for?",你可能会问。
嗯,在Django中是很常见的场景。
遵循规则 "thin views, fat models",非常鼓励将与模型相关的所有逻辑置于模型中。因此,在模型中放置所有这些方法以检查当前用户是否有权对模型的给定实例执行某些操作是很常见的。
这可能会给你敲响警钟:
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 违规,但非常接近。)
- 创建模板过滤器以将任意参数传递给函数。我认为它甚至不会起作用,因为过滤器将获得评估函数的结果(而不是函数本身),到那时就太晚了。
- 创建一个模板标签来调用一个向它传递任意参数的函数。这是可能的,但话又说回来,祝你实现 "as" 行为好运,这样你就可以在
if
模板标签中使用计算结果。
那么,如果可以在调用函数时自动将 request.user
注入到函数中会怎样呢?或更好!如果您可以注入整个 request
对象会怎样?这可能吗?
这是可能的,它以装饰器的形式出现在我面前。
这有点模糊,我知道,但我们在 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 %}
这行得通!
"Why do you need this for?",你可能会问。 嗯,在Django中是很常见的场景。
遵循规则 "thin views, fat models",非常鼓励将与模型相关的所有逻辑置于模型中。因此,在模型中放置所有这些方法以检查当前用户是否有权对模型的给定实例执行某些操作是很常见的。
这可能会给你敲响警钟:
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 违规,但非常接近。)
- 创建模板过滤器以将任意参数传递给函数。我认为它甚至不会起作用,因为过滤器将获得评估函数的结果(而不是函数本身),到那时就太晚了。
- 创建一个模板标签来调用一个向它传递任意参数的函数。这是可能的,但话又说回来,祝你实现 "as" 行为好运,这样你就可以在
if
模板标签中使用计算结果。
那么,如果可以在调用函数时自动将 request.user
注入到函数中会怎样呢?或更好!如果您可以注入整个 request
对象会怎样?这可能吗?
这是可能的,它以装饰器的形式出现在我面前。 这有点模糊,我知道,但我们在 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 %}
这行得通!