Loading django/utils/decorators.py +18 −4 Original line number Diff line number Diff line Loading @@ -45,8 +45,20 @@ def method_decorator(decorator, name=''): else: func = obj def decorate(function): """ Apply a list/tuple of decorators if decorator is one. Decorator functions are applied so that the call order is the same as the order in which they appear in the iterable. """ if hasattr(decorator, '__iter__'): for dec in decorator[::-1]: function = dec(function) return function return decorator(function) def _wrapper(self, *args, **kwargs): @decorator @decorate def bound_func(*args2, **kwargs2): return func.__get__(self, type(self))(*args2, **kwargs2) # bound_func has the signature that 'decorator' expects i.e. no Loading @@ -57,7 +69,7 @@ def method_decorator(decorator, name=''): # want to copy those. We don't have access to bound_func in this scope, # but we can cheat by using it on a dummy function. @decorator @decorate def dummy(*args, **kwargs): pass update_wrapper(_wrapper, dummy) Loading @@ -69,7 +81,9 @@ def method_decorator(decorator, name=''): return obj return _wrapper # 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, assigned=available_attrs(decorator)) # Change the name to aid debugging. if hasattr(decorator, '__name__'): Loading docs/ref/utils.txt +11 −3 Original line number Diff line number Diff line Loading @@ -155,12 +155,20 @@ The functions defined in this module share the following properties: 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. of the method to be decorated and is required. ``decorator`` may also be a a list or tuple of functions. They are wrapped in reverse order so that the call order is the order in which the functions appear in the list/tuple. 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. The ability to decorate classes, the ``name`` parameter, and the ability for ``decorator`` to accept a list/tuple of decorator functions were added. .. function:: decorator_from_middleware(middleware_class) Loading docs/releases/1.9.txt +3 −2 Original line number Diff line number Diff line Loading @@ -378,8 +378,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>`. * :func:`~django.utils.decorators.method_decorator` can now be used with a list or tuple of decorators. It can also be used to :ref:`decorate classes instead of methods <decorating-class-based-views>`. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading docs/topics/class-based-views/intro.txt +21 −1 Original line number Diff line number Diff line Loading @@ -286,9 +286,29 @@ of the method to be decorated as the keyword argument ``name``:: class ProtectedView(TemplateView): template_name = 'secret.html' If you have a set of common decorators used in several places, you can define a list or tuple of decorators and use this instead of invoking ``method_decorator()`` multiple times. These two classes are equivalent:: decorators = [never_cache, login_required] @method_decorator(decorators, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(never_cache, name='dispatch') @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' The decorators will process a request in the order they are passed to the decorator. In the example, ``never_cache()`` will process the request before ``login_required()``. .. versionchanged:: 1.9 The ability to use ``method_decorator()`` on a class was added. The ability to use ``method_decorator()`` on a class and the ability for it to accept a list or tuple of decorators were added. In this example, every instance of ``ProtectedView`` will have login protection. Loading tests/decorators/tests.py +73 −8 Original line number Diff line number Diff line Loading @@ -212,14 +212,34 @@ class MethodDecoratorTests(SimpleTestCase): self.assertEqual(getattr(func, 'myattr', False), True) self.assertEqual(getattr(func, 'myattr2', False), True) # Now check method_decorator class Test(object): # Decorate using method_decorator() on the method. class TestPlain(object): @myattr_dec_m @myattr2_dec_m def method(self): "A method" pass # Decorate using method_decorator() on both the class and the method. # The decorators applied to the methods are applied before the ones # applied to the class. @method_decorator(myattr_dec_m, "method") class TestMethodAndClass(object): @method_decorator(myattr2_dec_m) def method(self): "A method" pass # Decorate using an iterable of decorators. decorators = (myattr_dec_m, myattr2_dec_m) @method_decorator(decorators, "method") class TestIterable(object): def method(self): "A method" pass for Test in (TestPlain, TestMethodAndClass, TestIterable): self.assertEqual(getattr(Test().method, 'myattr', False), True) self.assertEqual(getattr(Test().method, 'myattr2', False), True) Loading @@ -229,6 +249,16 @@ class MethodDecoratorTests(SimpleTestCase): self.assertEqual(Test.method.__doc__, 'A method') self.assertEqual(Test.method.__name__, 'method') def test_bad_iterable(self): decorators = {myattr_dec_m, myattr2_dec_m} # The rest of the exception message differs between Python 2 and 3. with self.assertRaisesMessage(TypeError, "'set' object"): @method_decorator(decorators, "method") class TestIterable(object): def method(self): "A method" pass # Test for argumented decorator def test_argumented(self): class Test(object): Loading Loading @@ -291,6 +321,41 @@ class MethodDecoratorTests(SimpleTestCase): self.assertTrue(Test().method()) def test_tuple_of_decorators(self): """ @method_decorator can accept a tuple of decorators. """ def add_question_mark(func): def _wrapper(*args, **kwargs): return func(*args, **kwargs) + "?" return _wrapper def add_exclamation_mark(func): def _wrapper(*args, **kwargs): return func(*args, **kwargs) + "!" return _wrapper # The order should be consistent with the usual order in which # decorators are applied, e.g. # @add_exclamation_mark # @add_question_mark # def func(): # ... decorators = (add_exclamation_mark, add_question_mark) @method_decorator(decorators, name="method") class TestFirst(object): def method(self): return "hello world" class TestSecond(object): @method_decorator(decorators) def method(self): return "hello world" self.assertEqual(TestFirst().method(), "hello world?!") self.assertEqual(TestSecond().method(), "hello world?!") def test_invalid_non_callable_attribute_decoration(self): """ @method_decorator on a non-callable attribute raises an error. Loading Loading
django/utils/decorators.py +18 −4 Original line number Diff line number Diff line Loading @@ -45,8 +45,20 @@ def method_decorator(decorator, name=''): else: func = obj def decorate(function): """ Apply a list/tuple of decorators if decorator is one. Decorator functions are applied so that the call order is the same as the order in which they appear in the iterable. """ if hasattr(decorator, '__iter__'): for dec in decorator[::-1]: function = dec(function) return function return decorator(function) def _wrapper(self, *args, **kwargs): @decorator @decorate def bound_func(*args2, **kwargs2): return func.__get__(self, type(self))(*args2, **kwargs2) # bound_func has the signature that 'decorator' expects i.e. no Loading @@ -57,7 +69,7 @@ def method_decorator(decorator, name=''): # want to copy those. We don't have access to bound_func in this scope, # but we can cheat by using it on a dummy function. @decorator @decorate def dummy(*args, **kwargs): pass update_wrapper(_wrapper, dummy) Loading @@ -69,7 +81,9 @@ def method_decorator(decorator, name=''): return obj return _wrapper # 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, assigned=available_attrs(decorator)) # Change the name to aid debugging. if hasattr(decorator, '__name__'): Loading
docs/ref/utils.txt +11 −3 Original line number Diff line number Diff line Loading @@ -155,12 +155,20 @@ The functions defined in this module share the following properties: 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. of the method to be decorated and is required. ``decorator`` may also be a a list or tuple of functions. They are wrapped in reverse order so that the call order is the order in which the functions appear in the list/tuple. 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. The ability to decorate classes, the ``name`` parameter, and the ability for ``decorator`` to accept a list/tuple of decorator functions were added. .. function:: decorator_from_middleware(middleware_class) Loading
docs/releases/1.9.txt +3 −2 Original line number Diff line number Diff line Loading @@ -378,8 +378,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>`. * :func:`~django.utils.decorators.method_decorator` can now be used with a list or tuple of decorators. It can also be used to :ref:`decorate classes instead of methods <decorating-class-based-views>`. Internationalization ^^^^^^^^^^^^^^^^^^^^ Loading
docs/topics/class-based-views/intro.txt +21 −1 Original line number Diff line number Diff line Loading @@ -286,9 +286,29 @@ of the method to be decorated as the keyword argument ``name``:: class ProtectedView(TemplateView): template_name = 'secret.html' If you have a set of common decorators used in several places, you can define a list or tuple of decorators and use this instead of invoking ``method_decorator()`` multiple times. These two classes are equivalent:: decorators = [never_cache, login_required] @method_decorator(decorators, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(never_cache, name='dispatch') @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html' The decorators will process a request in the order they are passed to the decorator. In the example, ``never_cache()`` will process the request before ``login_required()``. .. versionchanged:: 1.9 The ability to use ``method_decorator()`` on a class was added. The ability to use ``method_decorator()`` on a class and the ability for it to accept a list or tuple of decorators were added. In this example, every instance of ``ProtectedView`` will have login protection. Loading
tests/decorators/tests.py +73 −8 Original line number Diff line number Diff line Loading @@ -212,14 +212,34 @@ class MethodDecoratorTests(SimpleTestCase): self.assertEqual(getattr(func, 'myattr', False), True) self.assertEqual(getattr(func, 'myattr2', False), True) # Now check method_decorator class Test(object): # Decorate using method_decorator() on the method. class TestPlain(object): @myattr_dec_m @myattr2_dec_m def method(self): "A method" pass # Decorate using method_decorator() on both the class and the method. # The decorators applied to the methods are applied before the ones # applied to the class. @method_decorator(myattr_dec_m, "method") class TestMethodAndClass(object): @method_decorator(myattr2_dec_m) def method(self): "A method" pass # Decorate using an iterable of decorators. decorators = (myattr_dec_m, myattr2_dec_m) @method_decorator(decorators, "method") class TestIterable(object): def method(self): "A method" pass for Test in (TestPlain, TestMethodAndClass, TestIterable): self.assertEqual(getattr(Test().method, 'myattr', False), True) self.assertEqual(getattr(Test().method, 'myattr2', False), True) Loading @@ -229,6 +249,16 @@ class MethodDecoratorTests(SimpleTestCase): self.assertEqual(Test.method.__doc__, 'A method') self.assertEqual(Test.method.__name__, 'method') def test_bad_iterable(self): decorators = {myattr_dec_m, myattr2_dec_m} # The rest of the exception message differs between Python 2 and 3. with self.assertRaisesMessage(TypeError, "'set' object"): @method_decorator(decorators, "method") class TestIterable(object): def method(self): "A method" pass # Test for argumented decorator def test_argumented(self): class Test(object): Loading Loading @@ -291,6 +321,41 @@ class MethodDecoratorTests(SimpleTestCase): self.assertTrue(Test().method()) def test_tuple_of_decorators(self): """ @method_decorator can accept a tuple of decorators. """ def add_question_mark(func): def _wrapper(*args, **kwargs): return func(*args, **kwargs) + "?" return _wrapper def add_exclamation_mark(func): def _wrapper(*args, **kwargs): return func(*args, **kwargs) + "!" return _wrapper # The order should be consistent with the usual order in which # decorators are applied, e.g. # @add_exclamation_mark # @add_question_mark # def func(): # ... decorators = (add_exclamation_mark, add_question_mark) @method_decorator(decorators, name="method") class TestFirst(object): def method(self): return "hello world" class TestSecond(object): @method_decorator(decorators) def method(self): return "hello world" self.assertEqual(TestFirst().method(), "hello world?!") self.assertEqual(TestSecond().method(), "hello world?!") def test_invalid_non_callable_attribute_decoration(self): """ @method_decorator on a non-callable attribute raises an error. Loading