Login

Decorating class-based views

Author:
lqc
Posted:
January 30, 2012
Language:
Python
Version:
1.3
Score:
0 (after 0 ratings)

This is a simplest approach possible. as_view() is replaced, so that it applies the given decorator before returning.

In this approach, decorators are always put on top - that means it's not possible to have functions called in this order:

B.dispatch, login_required, A.dispatch

NOTE: By default this modifies the given class, so be careful when doing this:

TemplateView = view_decorator(login_required)(TemplateView)

Because it will modify the TemplateView class. Instead create a fresh class first and apply the decorator there. A shortcut for this is specifying the subclass argument. But this is also dangerous. Consider:

@view_decorator(login_required, subclass=True)
class MyView(View):

    def get_context_data(self):
        data = super(MyView, self).get_context_data()
        data["foo"] = "bar"
        return data

This looks like a normal Python code, but there is a hidden infinite recursion, because of how super() works in Python 2.x; By the time get_context_data() is invoked, MyView refers to a subclass created in the decorator. super() looks at the next class in the MRO of MyView, which is the original MyView class we created, so it contains the get_context_data() method. Which is exactly the method that was just called. BOOM!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from functools import wraps
from django.utils.decorators import classonlymethod


def view_decorator(fdec, subclass=False):
    """
    Change a function decorator into a view decorator.

    https://github.com/lqc/django/tree/cbvdecoration_ticket14512
    """
    def decorator(cls):
        if not hasattr(cls, "as_view"):
            raise TypeError("You should only decorate subclasses of View, not mixins.")
        if subclass:
            cls = type("%sWithDecorator(%s)" % (cls.__name__, fdec.__name__), (cls,), {})
        original = cls.as_view.im_func
        @wraps(original)
        def as_view(current, **initkwargs):
            return fdec(original(current, **initkwargs))
        cls.as_view = classonlymethod(as_view)
        return cls
    return decorator

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.