Loading django/utils/decorators.py +29 −4 Original line number Diff line number Diff line Loading @@ -17,13 +17,34 @@ class classonlymethod(classmethod): return super(classonlymethod, self).__get__(instance, owner) def method_decorator(decorator): def method_decorator(decorator, name=''): """ Converts a function decorator into a method decorator """ # 'func' is a function at the time it is passed to _dec, but will eventually # be a method of the class it is defined on. def _dec(func): # '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): is_class = isinstance(obj, type) if is_class: if name and hasattr(obj, name): func = getattr(obj, name) if not callable(func): raise TypeError( "Cannot decorate '{0}' as it isn't a callable " "attribute of {1} ({2})".format(name, obj, func) ) else: raise ValueError( "The keyword argument `name` must be the name of a method " "of the decorated class: {0}. Got '{1}' instead".format( obj, name, ) ) else: func = obj def _wrapper(self, *args, **kwargs): @decorator def bound_func(*args2, **kwargs2): Loading @@ -43,6 +64,10 @@ def method_decorator(decorator): # Need to preserve any existing attributes of 'func', including the name. update_wrapper(_wrapper, func) if is_class: setattr(obj, name, _wrapper) return obj return _wrapper update_wrapper(_dec, decorator, assigned=available_attrs(decorator)) Loading docs/ref/utils.txt +8 −2 Original line number Diff line number Diff line Loading @@ -151,11 +151,17 @@ The functions defined in this module share the following properties: .. module:: django.utils.decorators :synopsis: Functions that help with creating decorators for views. .. function:: method_decorator(decorator) .. function:: method_decorator(decorator, name='') Converts a function decorator into a method decorator. See :ref:`decorating Converts a function decorator into a method decorator. It can be used to decorate methods or classes; in the latter case, ``name`` is the name of the method to be decorated and is required. See :ref:`decorating class based views<decorating-class-based-views>` for example usage. .. versionchanged:: 1.9 The ability to decorate classes and the ``name`` parameter were added. .. function:: decorator_from_middleware(middleware_class) Given a middleware class, returns a view decorator. This lets you use Loading docs/releases/1.9.txt +3 −0 Original line number Diff line number Diff line Loading @@ -338,6 +338,9 @@ Generic Views * Class based views generated using ``as_view()`` now have ``view_class`` and ``view_initkwargs`` attributes. * :func:`~django.utils.decorators.method_decorator` can now be used to :ref:`decorate classes instead of methods <decorating-class-based-views>`. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading docs/topics/class-based-views/intro.txt +12 −2 Original line number Diff line number Diff line Loading @@ -279,8 +279,18 @@ that it can be used on an instance method. For example:: def dispatch(self, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs) In this example, every instance of ``ProtectedView`` will have login protection. Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument ``name``:: @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' .. versionchanged:: 1.9 The ability to use ``method_decorator()`` on a class was added. In this example, every instance of ``ProtectedView`` will have login protection. .. note:: Loading tests/decorators/tests.py +50 −1 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from django.contrib.auth.decorators import ( ) from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed from django.middleware.clickjacking import XFrameOptionsMiddleware from django.test import SimpleTestCase from django.utils.decorators import method_decorator from django.utils.functional import allow_lazy, lazy from django.views.decorators.cache import ( Loading Loading @@ -189,7 +190,7 @@ class ClsDec(object): return update_wrapper(wrapped, f) class MethodDecoratorTests(TestCase): class MethodDecoratorTests(SimpleTestCase): """ Tests for method_decorator """ Loading Loading @@ -274,6 +275,54 @@ class MethodDecoratorTests(TestCase): self.assertEqual(Test().method(1), 1) def test_class_decoration(self): """ @method_decorator can be used to decorate a class and its methods. """ def deco(func): def _wrapper(*args, **kwargs): return True return _wrapper @method_decorator(deco, name="method") class Test(object): def method(self): return False self.assertTrue(Test().method()) def test_invalid_non_callable_attribute_decoration(self): """ @method_decorator on a non-callable attribute raises an error. """ msg = ( "Cannot decorate 'prop' as it isn't a callable attribute of " "<class 'Test'> (1)" ) with self.assertRaisesMessage(TypeError, msg): @method_decorator(lambda: None, name="prop") class Test(object): prop = 1 @classmethod def __module__(cls): return "tests" def test_invalid_method_name_to_decorate(self): """ @method_decorator on a nonexistent method raises an error. """ msg = ( "The keyword argument `name` must be the name of a method of the " "decorated class: <class 'Test'>. Got 'non_existing_method' instead" ) with self.assertRaisesMessage(ValueError, msg): @method_decorator(lambda: None, name="non_existing_method") class Test(object): @classmethod def __module__(cls): return "tests" class XFrameOptionsDecoratorsTests(TestCase): """ Loading Loading
django/utils/decorators.py +29 −4 Original line number Diff line number Diff line Loading @@ -17,13 +17,34 @@ class classonlymethod(classmethod): return super(classonlymethod, self).__get__(instance, owner) def method_decorator(decorator): def method_decorator(decorator, name=''): """ Converts a function decorator into a method decorator """ # 'func' is a function at the time it is passed to _dec, but will eventually # be a method of the class it is defined on. def _dec(func): # '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): is_class = isinstance(obj, type) if is_class: if name and hasattr(obj, name): func = getattr(obj, name) if not callable(func): raise TypeError( "Cannot decorate '{0}' as it isn't a callable " "attribute of {1} ({2})".format(name, obj, func) ) else: raise ValueError( "The keyword argument `name` must be the name of a method " "of the decorated class: {0}. Got '{1}' instead".format( obj, name, ) ) else: func = obj def _wrapper(self, *args, **kwargs): @decorator def bound_func(*args2, **kwargs2): Loading @@ -43,6 +64,10 @@ def method_decorator(decorator): # Need to preserve any existing attributes of 'func', including the name. update_wrapper(_wrapper, func) if is_class: setattr(obj, name, _wrapper) return obj return _wrapper update_wrapper(_dec, decorator, assigned=available_attrs(decorator)) Loading
docs/ref/utils.txt +8 −2 Original line number Diff line number Diff line Loading @@ -151,11 +151,17 @@ The functions defined in this module share the following properties: .. module:: django.utils.decorators :synopsis: Functions that help with creating decorators for views. .. function:: method_decorator(decorator) .. function:: method_decorator(decorator, name='') Converts a function decorator into a method decorator. See :ref:`decorating Converts a function decorator into a method decorator. It can be used to decorate methods or classes; in the latter case, ``name`` is the name of the method to be decorated and is required. See :ref:`decorating class based views<decorating-class-based-views>` for example usage. .. versionchanged:: 1.9 The ability to decorate classes and the ``name`` parameter were added. .. function:: decorator_from_middleware(middleware_class) Given a middleware class, returns a view decorator. This lets you use Loading
docs/releases/1.9.txt +3 −0 Original line number Diff line number Diff line Loading @@ -338,6 +338,9 @@ Generic Views * Class based views generated using ``as_view()`` now have ``view_class`` and ``view_initkwargs`` attributes. * :func:`~django.utils.decorators.method_decorator` can now be used to :ref:`decorate classes instead of methods <decorating-class-based-views>`. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading
docs/topics/class-based-views/intro.txt +12 −2 Original line number Diff line number Diff line Loading @@ -279,8 +279,18 @@ that it can be used on an instance method. For example:: def dispatch(self, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs) In this example, every instance of ``ProtectedView`` will have login protection. Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument ``name``:: @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' .. versionchanged:: 1.9 The ability to use ``method_decorator()`` on a class was added. In this example, every instance of ``ProtectedView`` will have login protection. .. note:: Loading
tests/decorators/tests.py +50 −1 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from django.contrib.auth.decorators import ( ) from django.http import HttpRequest, HttpResponse, HttpResponseNotAllowed from django.middleware.clickjacking import XFrameOptionsMiddleware from django.test import SimpleTestCase from django.utils.decorators import method_decorator from django.utils.functional import allow_lazy, lazy from django.views.decorators.cache import ( Loading Loading @@ -189,7 +190,7 @@ class ClsDec(object): return update_wrapper(wrapped, f) class MethodDecoratorTests(TestCase): class MethodDecoratorTests(SimpleTestCase): """ Tests for method_decorator """ Loading Loading @@ -274,6 +275,54 @@ class MethodDecoratorTests(TestCase): self.assertEqual(Test().method(1), 1) def test_class_decoration(self): """ @method_decorator can be used to decorate a class and its methods. """ def deco(func): def _wrapper(*args, **kwargs): return True return _wrapper @method_decorator(deco, name="method") class Test(object): def method(self): return False self.assertTrue(Test().method()) def test_invalid_non_callable_attribute_decoration(self): """ @method_decorator on a non-callable attribute raises an error. """ msg = ( "Cannot decorate 'prop' as it isn't a callable attribute of " "<class 'Test'> (1)" ) with self.assertRaisesMessage(TypeError, msg): @method_decorator(lambda: None, name="prop") class Test(object): prop = 1 @classmethod def __module__(cls): return "tests" def test_invalid_method_name_to_decorate(self): """ @method_decorator on a nonexistent method raises an error. """ msg = ( "The keyword argument `name` must be the name of a method of the " "decorated class: <class 'Test'>. Got 'non_existing_method' instead" ) with self.assertRaisesMessage(ValueError, msg): @method_decorator(lambda: None, name="non_existing_method") class Test(object): @classmethod def __module__(cls): return "tests" class XFrameOptionsDecoratorsTests(TestCase): """ Loading