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')