On Thu, 2010-10-21 at 01:40 +0200, Łukasz Rekucki wrote:

> On a related note, while writing a class_decorator() I noticed that
> method_decorator() doesn't work with decorators that set attributes on
> the view (like csrf_exempt). This is because the decorator is invoked
> on a closure inside _wrapper() during method call (i.e. during method
> call, not class creation), while as_view() only sees _wrapper - which
> has no attributes.

Good catch!  The attached patch should fix it, but it feels a bit icky,
due to the double application of the decorator. Let me know if it solves
this problem for you, or if you can think of a better way to fix it.

Luke

-- 
If you can't answer a man's arguments, all is not lost; you can 
still call him vile names. (Elbert Hubbard)

Luke Plant || http://lukeplant.me.uk/

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

diff -r e0bdd0d406e9 django/utils/decorators.py
--- a/django/utils/decorators.py	Thu Oct 21 06:01:02 2010 +0000
+++ b/django/utils/decorators.py	Thu Oct 21 13:26:46 2010 +0100
@@ -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__
diff -r e0bdd0d406e9 tests/regressiontests/decorators/tests.py
--- a/tests/regressiontests/decorators/tests.py	Thu Oct 21 06:01:02 2010 +0000
+++ b/tests/regressiontests/decorators/tests.py	Thu Oct 21 13:26:46 2010 +0100
@@ -129,14 +129,58 @@
 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 foo():
+            pass
+
+        self.assertEqual(getattr(foo, 'myattr', False), True)
+        self.assertEqual(getattr(foo, 'myattr2', False), True)
+
+        # Now check method_decorator
+        class Test(object):
+            @myattr_dec_m
+            @myattr2_dec_m
+            def method(self):
+                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.__func__.__name__, 'method')

Reply via email to