summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Jerdonek <chris.jerdonek@gmail.com>2018-06-27 08:46:07 -0700
committerTim Graham <timograham@gmail.com>2018-06-27 11:46:24 -0400
commitda46599143408be58c1845b8c82cacbae0bb56c0 (patch)
tree7d3a2b8b8164e234ff8b6dbcbf74d2bcdce78364
parentd5482dfe20cfd4e244db600733d8a5cf58664f2c (diff)
[2.1.x] Refs #29253 -- Fixed method_decorator() crash if decorator sets a new attribute.
Regression in fdc936c9130cf4fb5d59869674b9a31cc79a7999. Backport of f434f5b84f7fcea9a76a551621ecce70786e2899 from master
-rw-r--r--django/utils/decorators.py8
-rw-r--r--tests/decorators/tests.py15
2 files changed, 20 insertions, 3 deletions
diff --git a/django/utils/decorators.py b/django/utils/decorators.py
index c3bf7bd49f..9f8b041e4b 100644
--- a/django/utils/decorators.py
+++ b/django/utils/decorators.py
@@ -2,7 +2,7 @@
# For backwards compatibility in Django 2.0.
from contextlib import ContextDecorator # noqa
-from functools import WRAPPER_ASSIGNMENTS, update_wrapper, wraps
+from functools import WRAPPER_ASSIGNMENTS, partial, update_wrapper, wraps
class classonlymethod(classmethod):
@@ -36,8 +36,10 @@ def _multi_decorate(decorators, method):
def _wrapper(self, *args, **kwargs):
# bound_method has the signature that 'decorator' expects i.e. no
- # 'self' argument.
- bound_method = method.__get__(self, type(self))
+ # '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)
diff --git a/tests/decorators/tests.py b/tests/decorators/tests.py
index e8f6b8d2d5..aaa09c0056 100644
--- a/tests/decorators/tests.py
+++ b/tests/decorators/tests.py
@@ -271,6 +271,21 @@ class MethodDecoratorTests(SimpleTestCase):
self.assertEqual(Test.method.__doc__, 'A method')
self.assertEqual(Test.method.__name__, 'method')
+ def test_new_attribute(self):
+ """A decorator that sets a new attribute on the method."""
+ def decorate(func):
+ func.x = 1
+ return func
+
+ class MyClass:
+ @method_decorator(decorate)
+ def method(self):
+ return True
+
+ obj = MyClass()
+ self.assertEqual(obj.method.x, 1)
+ self.assertIs(obj.method(), True)
+
def test_bad_iterable(self):
decorators = {myattr_dec_m, myattr2_dec_m}
msg = "'set' object is not subscriptable"