在 Django 中间件中访问当前视图类实例

Posted

技术标签:

【中文标题】在 Django 中间件中访问当前视图类实例【英文标题】:Accessing the current view class instance in Django middleware 【发布时间】:2014-01-10 00:28:43 【问题描述】:

问题:

我正在尝试访问中间件层中视图实例的属性。

例如,给定一个这样的基于类的视图:

# views.py
class MyView(View):
    my_attribute = 'something'

我希望能够通过执行以下操作来处理中间件中的 my_attribute

# middleware.py
def process_view(self, request, view_func, view_args, view_kwargs):
    my_attribute = request.view.my_attribute

当然,这是行不通的,因为 Django 不会通过请求对象公开视图实例。有没有办法做到这一点?

谢谢!


我的第一次尝试:

我最初认为process_view() 方法可能是执行此操作的好地方。不幸的是,它接收到的view_func 参数包含一个函数——MyView.as_view() 的输出——而不是视图实例本身。来自Django docs:

process_view(self, request, view_func, view_args, view_kwargs)

...view_func 是 Django 即将使用的 Python 函数。 (这是实际功能 对象,而不是字符串形式的函数名称。)...


我的第二次尝试:

视图实例的句柄在process_template_response() 方法中可用,但它非常笨拙,而且无论如何,我希望能够在中间件堆栈的较早位置使用my_attribute。但这确实有效:

def process_template_response(self, request, response):
    my_attribute = response.context_data['view'].my_attribute

【问题讨论】:

您要解决的问题是什么? @burhan-khalid:目标是根据视图属性的值在模板上下文中放置一些数据。我会使用上下文处理器来执行此操作,但也没有找到在上下文处理器中访问视图实例的方法。我目前的方法是使用覆盖get_context_data() 的mixin。这样就完成了工作,但是每个请求都需要此功能,因此我想避免在应用程序的每个视图中都必须从 mixin 继承。 我还可以想到许多其他应用程序。只是一个例子:一种处理视图访问控制的简单方法。我知道还有其他处理访问控制的方法,但这似乎是一种特别简单的方法,我很想知道它是否可能。 【参考方案1】:

没有内置的方法可以做到这一点,但这是 django-users 邮件列表中一位好心的用户给我的解决方案。我在这里转发他的建议,以防其他人尝试做同样的事情。

这在以下情况下很有用:

    您希望在中间件中识别当前视图的属性并相应地执行处理,并且; 出于各种原因,您不想使用 mixin 或装饰器来完成类似的结果。

这会检查传递给process_view() 中间件挂钩的view_func 对象,并确定并导入适当的视图类。

# middleware.py
from myutils import get_class

def process_view(self, request, view_func, view_args, view_kwargs):
        view = get_class(view_func.__module__, view_func.__name__)
        view.my_attribute

然后你的get_class() 定义:

# myutils.py
from django.utils import importlib

def get_class(module_name, cls_name):
    try:
        module = importlib.import_module(module_name)
    except ImportError:
        raise ImportError('Invalid class path: '.format(module_name))
    try:
        cls = getattr(module, cls_name)
    except AttributeError:
        raise ImportError('Invalid class name: '.format(cls_name))
    else:
        return cls

【讨论】:

【参考方案2】:

使用装饰器,有很多方法可以实现所需的行为。

1.如果你只想标记一个类让中间件做某事

from django.utils.decorators import classonlymethod

def special_marker(class_view):
    def as_view(cls, **initkwargs):
        view = super(cls, cls).as_view(**initkwargs)
        view.special_marker = True
        return view
    return type(class_view.__name__, (class_view,), 
        'as_view': classonlymethod(as_view),
    )


@special_marker
class MyView(View):
    pass


class MyMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        return self.get_response(request)

    def process_view(self, request, view_func, view_args, view_kwargs):
        special_marker = getattr(view_func, 'special_marker', False)
        if special_marker:
            # Do something

2。如果你想将一些视图中不需要的数据传递给中间件

from django.utils.decorators import classonlymethod

def tell_middleware(**kwargs):
    def wrapper(class_view):
        def as_view(cls, **initkwargs):
            view = super(cls, cls).as_view(**initkwargs)
            for k, v in kwargs.items():
                setattr(view, k, v)
            return view
        return type(class_view.__name__, (class_view,), 
            'as_view': classonlymethod(as_view),
        )
    return wrapper


@tell_middleware(my_attribute='something')
class MyView(View):
    pass


class MyMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        return self.get_response(request)

    def process_view(self, request, view_func, view_args, view_kwargs):
        my_attribute = getattr(view_func, 'my_attribute', 'default value')
        if my_attribute == 'something':
            # Do something

3.如果你想将一些视图属性暴露给中间件

from django.utils.decorators import classonlymethod

def expose_to_middleware(*args):
    def wrapper(class_view):
        def as_view(cls, **initkwargs):
            view = super(cls, cls).as_view(**initkwargs)
            for attr in args:
                setattr(view, attr, getattr(class_view, attr)
            return view
        return type(class_view.__name__, (class_view,), 
            'as_view': classonlymethod(as_view),
        )
    return wrapper


@expose_to_middleware('my_attribute', 'my_other_attribute')
class MyView(View):
    my_attribute = 'something'
    my_other_attribute = 'else'
    unexposed_attribute = 'foobar'


class MyMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        return self.get_response(request)

    def process_view(self, request, view_func, view_args, view_kwargs):
        my_attribute = getattr(view_func, 'my_attribute', 'default value')
        if my_attribute == 'something':
            # Do something

【讨论】:

【参考方案3】:

另一种解决方案是创建一个新的 View 类:

from django.views.generic.base import View
class AddClassView(View):
    @classonlymethod
    def as_view(cls, **initkwargs):
        view = super(AddClassView, cls).as_view(**initkwargs)
        view.cls = cls
        return view

并在基于类的视图中使用它:

# views.py
class MyView(AddClassView):
    my_attribute = 'something'

然后你在中间件中进行如下操作:

# middleware.py
def process_view(self, request, view_func, view_args, view_kwargs):
    view_func.cls.my_attribute  # 'something'

这个方法用在Django REST Framework(https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L94-L104)

【讨论】:

【参考方案4】:

如果它依赖于视图,它可能应该是该视图的混合。如果您正在执行依赖于活动视图的菜单之类的操作,我将对当前 URL 名称进行反向查找:

see a previous answer about using URL name lookup of the current URL

【讨论】:

在某些情况下,这些绝对是潜在的解决方法。但是必须在每个视图的继承层次结构中声明 Mixin,如果您基本上在任何地方都需要 mixin,这可能会成为一个问题。中间件不是更好地处理这种情况吗?如果路径可以通过request.path_info 获得,为什么不在request.view 提供视图实例本身呢?更令人惊讶的是:视图实例在 模板上下文 中可用:我可以简单地使用 view.my_attribute ... 获取视图属性,但要处理它,我需要使用模板标签。奇数。 对切线感到抱歉,但是能够直接使用视图实例不是对基于类的视图的一个很好的扩展吗?它可以让你全面完成一些目前只能根据具体情况使用 mixins 才能完成的事情。或者使用装饰器,对于初学者来说这是一个相当难以访问的功能。无论如何,在模板中公开视图实例而不是通过请求对象似乎有点愚蠢。但我也可能在这里遗漏了一些完全基本的东西。 :)

以上是关于在 Django 中间件中访问当前视图类实例的主要内容,如果未能解决你的问题,请参考以下文章

Django类视图中间件

Django:(05)类视图,装饰器和中间件

django中间件

Django----中间件详解

使用中间件添加参数以查看 django 中的函数

Django----中间件详解