Author: lukeplant
Date: 2010-10-21 09:56:49 -0500 (Thu, 21 Oct 2010)
New Revision: 14311

Modified:
   django/trunk/django/utils/decorators.py
   django/trunk/tests/regressiontests/decorators/tests.py
Log:
Fixed a bug with method_decorator not preserving the attributes of the wrapped 
method, which is important for decorators like csrf_exempt

Modified: django/trunk/django/utils/decorators.py
===================================================================
--- django/trunk/django/utils/decorators.py     2010-10-21 14:56:33 UTC (rev 
14310)
+++ django/trunk/django/utils/decorators.py     2010-10-21 14:56:49 UTC (rev 
14311)
@@ -11,15 +11,28 @@
     """
     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 it.
     def _dec(func):
         def _wrapper(self, *args, **kwargs):
+            @decorator
             def bound_func(*args2, **kwargs2):
                 return func(self, *args2, **kwargs2)
             # bound_func has the signature that 'decorator' expects i.e.  no
             # 'self' argument, but it is a closure over self so it can call
             # 'func' correctly.
-            return decorator(bound_func)(*args, **kwargs)
-        return wraps(func)(_wrapper)
+            return bound_func(*args, **kwargs)
+        # In case 'decorator' adds attributes to the function it decorates, we
+        # 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
+        def dummy(*args, **kwargs):
+            pass
+        update_wrapper(_wrapper, dummy)
+        # Need to preserve any existing attributes of 'func', including the 
name.
+        update_wrapper(_wrapper, func)
+
+        return _wrapper
     update_wrapper(_dec, decorator)
     # Change the name to aid debugging.
     _dec.__name__ = 'method_decorator(%s)' % decorator.__name__

Modified: django/trunk/tests/regressiontests/decorators/tests.py
===================================================================
--- django/trunk/tests/regressiontests/decorators/tests.py      2010-10-21 
14:56:33 UTC (rev 14310)
+++ django/trunk/tests/regressiontests/decorators/tests.py      2010-10-21 
14:56:49 UTC (rev 14311)
@@ -126,14 +126,60 @@
 simple_dec_m = method_decorator(simple_dec)
 
 
+# For testing method_decorator, two decorators that add an attribute to the 
function
+def myattr_dec(func):
+    def wrapper(*args, **kwargs):
+        return func(*args, **kwargs)
+    wrapper.myattr = True
+    return wraps(func)(wrapper)
+
+myattr_dec_m = method_decorator(myattr_dec)
+
+
+def myattr2_dec(func):
+    def wrapper(*args, **kwargs):
+        return func(*args, **kwargs)
+    wrapper.myattr2 = True
+    return wraps(func)(wrapper)
+
+myattr2_dec_m = method_decorator(myattr2_dec)
+
+
 class MethodDecoratorTests(TestCase):
     """
     Tests for method_decorator
     """
-    def test_method_decorator(self):
+    def test_preserve_signature(self):
         class Test(object):
             @simple_dec_m
             def say(self, arg):
                 return arg
 
         self.assertEqual("test:hello", Test().say("hello"))
+
+    def test_preserve_attributes(self):
+        # Sanity check myattr_dec and myattr2_dec
+        @myattr_dec
+        @myattr2_dec
+        def func():
+            pass
+
+        self.assertEqual(getattr(func, 'myattr', False), True)
+        self.assertEqual(getattr(func, 'myattr2', False), True)
+
+        # Now check method_decorator
+        class Test(object):
+            @myattr_dec_m
+            @myattr2_dec_m
+            def method(self):
+                "A method"
+                pass
+
+        self.assertEqual(getattr(Test().method, 'myattr', False), True)
+        self.assertEqual(getattr(Test().method, 'myattr2', False), True)
+
+        self.assertEqual(getattr(Test.method, 'myattr', False), True)
+        self.assertEqual(getattr(Test.method, 'myattr2', False), True)
+
+        self.assertEqual(Test.method.__doc__, 'A method')
+        self.assertEqual(Test.method.im_func.__name__, 'method')

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to