Source code for django.utils.decorators

"Functions that help with dynamically creating decorators for views."

# For backwards compatibility in Django 2.0.
from contextlib import ContextDecorator  # noqa
from functools import WRAPPER_ASSIGNMENTS, partial, update_wrapper, wraps


class classonlymethod(classmethod):
    def __get__(self, instance, cls=None):
        if instance is not None:
            raise AttributeError("This method is available only on the class, not on instances.")
        return super().__get__(instance, cls)


def _update_method_wrapper(_wrapper, decorator):
    # _multi_decorate()'s bound_method isn't available in this scope. Cheat by
    # using it on a dummy function.
    @decorator
    def dummy(*args, **kwargs):
        pass
    update_wrapper(_wrapper, dummy)


def _multi_decorate(decorators, method):
    """
    Decorate `method` with one or more function decorators. `decorators` can be
    a single decorator or an iterable of decorators.
    """
    if hasattr(decorators, '__iter__'):
        # Apply a list/tuple of decorators if 'decorators' is one. Decorator
        # functions are applied so that the call order is the same as the
        # order in which they appear in the iterable.
        decorators = decorators[::-1]
    else:
        decorators = [decorators]

    def _wrapper(self, *args, **kwargs):
        # bound_method has the signature that 'decorator' expects i.e. no
        # 'self' argument, but it's a closure over self so it can call
        # 'func'. Also, wrap method.__get__() in a function because new
        # attributes can't be set on bound method objects, only on functions.
        bound_method = partial(method.__get__(self, type(self)))
        for dec in decorators:
            bound_method = dec(bound_method)
        return bound_method(*args, **kwargs)

    # Copy any attributes that a decorator adds to the function it decorates.
    for dec in decorators:
        _update_method_wrapper(_wrapper, dec)
    # Preserve any existing attributes of 'method', including the name.
    update_wrapper(_wrapper, method)
    return _wrapper


[docs]def method_decorator(decorator, name=''): """ Convert a function decorator into a method decorator """ # 'obj' can be a class or a function. If 'obj' is a function at the time it # is passed to _dec, it will eventually be a method of the class it is # defined on. If 'obj' is a class, the 'name' is required to be the name # of the method that will be decorated. def _dec(obj): if not isinstance(obj, type): return _multi_decorate(decorator, obj) if not (name and hasattr(obj, name)): raise ValueError( "The keyword argument `name` must be the name of a method " "of the decorated class: %s. Got '%s' instead." % (obj, name) ) method = getattr(obj, name) if not callable(method): raise TypeError( "Cannot decorate '%s' as it isn't a callable attribute of " "%s (%s)." % (name, obj, method) ) _wrapper = _multi_decorate(decorator, method) setattr(obj, name, _wrapper) return obj # Don't worry about making _dec look similar to a list/tuple as it's rather # meaningless. if not hasattr(decorator, '__iter__'): update_wrapper(_dec, decorator) # Change the name to aid debugging. obj = decorator if hasattr(decorator, '__name__') else decorator.__class__ _dec.__name__ = 'method_decorator(%s)' % obj.__name__ return _dec
[docs]def decorator_from_middleware_with_args(middleware_class): """ Like decorator_from_middleware, but return a function that accepts the arguments to be passed to the middleware_class. Use like:: cache_page = decorator_from_middleware_with_args(CacheMiddleware) # ... @cache_page(3600) def my_view(request): # ... """ return make_middleware_decorator(middleware_class)
[docs]def decorator_from_middleware(middleware_class): """ Given a middleware class (not an instance), return a view decorator. This lets you use middleware functionality on a per-view basis. The middleware is created with no params passed. """ return make_middleware_decorator(middleware_class)()
# Unused, for backwards compatibility in Django 2.0. def available_attrs(fn): """ Return the list of functools-wrappable attributes on a callable. This was required as a workaround for https://bugs.python.org/issue3445 under Python 2. """ return WRAPPER_ASSIGNMENTS def make_middleware_decorator(middleware_class): def _make_decorator(*m_args, **m_kwargs): middleware = middleware_class(*m_args, **m_kwargs) def _decorator(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): if hasattr(middleware, 'process_request'): result = middleware.process_request(request) if result is not None: return result if hasattr(middleware, 'process_view'): result = middleware.process_view(request, view_func, args, kwargs) if result is not None: return result try: response = view_func(request, *args, **kwargs) except Exception as e: if hasattr(middleware, 'process_exception'): result = middleware.process_exception(request, e) if result is not None: return result raise if hasattr(response, 'render') and callable(response.render): if hasattr(middleware, 'process_template_response'): response = middleware.process_template_response(request, response) # Defer running of process_response until after the template # has been rendered: if hasattr(middleware, 'process_response'): def callback(response): return middleware.process_response(request, response) response.add_post_render_callback(callback) else: if hasattr(middleware, 'process_response'): return middleware.process_response(request, response) return response return _wrapped_view return _decorator return _make_decorator class classproperty: def __init__(self, method=None): self.fget = method def __get__(self, instance, cls=None): return self.fget(cls) def getter(self, method): self.fget = method return self