Question

Question:

I'm trying to access an attribute of the view instance in the middleware layer.

For example, given a class-based view like this:

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

I'd love to be able to get a handle on my_attribute in the middleware by doing something like this:

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

Of course, this does not work because Django doesn't expose the view instance through the request object. Is there a way to get this accomplished?

Thanks!


My first attempt:

I initially figured that the process_view() method might be a good place to do this. Unfortunately, the view_func argument it receives contains a function -- the output of MyView.as_view() -- rather than the view instance itself. From the Django docs:

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

...view_func is the Python function that Django is about to use. (It’s the actual function object, not the name of the function as a string.)...


My second attempt:

A handle to the view instance is available in process_template_response() method, but it's pretty awkward, and, in any case, I'd like to be able to work with my_attribute at an earlier point in the middleware stack. But this does work:

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

Solution

There is no built-in way to do this, but here is a solution given to me by a kindly user on the django-users mailing list. I'm reposting his suggestion here in case anyone else is trying to do the same thing.

This is useful if:

  1. you want to identify properties of the current view in your middleware and perform processing accordingly, and;
  2. for various reasons you don't want to use mixins or decorators to accomplish similar results.

This inspects the view_func object passed to the process_view() middleware hook and determines and imports the the appropriate view class.

# 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

Then your get_class() definition:

# 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

OTHER TIPS

Using decorators, there are quite some ways to achieve the desired behavior.

1. If you only want to mark a class for the middleware to do something

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. If you want to pass some data to the middleware that you don't need in the view

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. If you want to expose some view attributes to the middleware

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

Another solution could be to create a new View class:

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

And use this in your class based view:

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

Then you do the following in the middleware:

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

This method is used in the Django REST Framework(https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L94-L104)

If it depends on the view, it should probably be a mixin of that view. If you're doing something like a menu that depends on the active view, I'd do a reverse lookup of the current URL name:

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top