Fixed #25146 -- Allowed method_decorator() to decorate classes.
This commit is contained in:
parent
1a76257b1b
commit
3bdaaf6777
|
@ -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):
|
||||
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
@ -189,7 +190,7 @@ class ClsDec(object):
|
|||
return update_wrapper(wrapped, f)
|
||||
|
||||
|
||||
class MethodDecoratorTests(TestCase):
|
||||
class MethodDecoratorTests(SimpleTestCase):
|
||||
"""
|
||||
Tests for method_decorator
|
||||
"""
|
||||
|
@ -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…
Reference in New Issue