From 9137c54353f3356e0f65e0218016e92c5bc9cc43 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 12 Mar 2010 13:31:10 +0000 Subject: [PATCH] [1.1.X] Fixed #13093 -- Updated some decorators and the decorator_from_middleware function to allow callable classes to be decorated. Thanks to Brian Neal for the report. Backport of r12762 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@12763 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/decorators.py | 13 ++++++++++--- django/views/decorators/cache.py | 6 +++--- django/views/decorators/http.py | 4 ++-- django/views/decorators/vary.py | 5 +++-- tests/regressiontests/utils/decorators.py | 6 ++++++ tests/regressiontests/utils/urls.py | 1 + tests/regressiontests/utils/views.py | 7 +++++++ 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/django/utils/decorators.py b/django/utils/decorators.py index 8fc4c1d96a..683d4c6a7f 100644 --- a/django/utils/decorators.py +++ b/django/utils/decorators.py @@ -2,9 +2,16 @@ import types try: - from functools import wraps + from functools import wraps, WRAPPER_ASSIGNMENTS except ImportError: - from django.utils.functional import wraps # Python 2.3, 2.4 fallback. + from django.utils.functional import wraps, WRAPPER_ASSIGNMENTS # Python 2.3, 2.4 fallback. + +def available_attrs(fn): + """ + Return the list of functools-wrappable attributes on a callable. + This is required as a workaround for http://bugs.python.org/issue3445. + """ + return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a)) def decorator_from_middleware(middleware_class): """ @@ -57,5 +64,5 @@ def decorator_from_middleware(middleware_class): if result is not None: return result return response - return wraps(view_func)(_wrapped_view) + return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) return _decorator_from_middleware diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 8b620c1345..9abb8d44f1 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -16,7 +16,7 @@ try: except ImportError: from django.utils.functional import wraps # Python 2.3, 2.4 fallback. -from django.utils.decorators import decorator_from_middleware +from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.cache import patch_cache_control, add_never_cache_headers from django.middleware.cache import CacheMiddleware @@ -31,7 +31,7 @@ def cache_control(**kwargs): patch_cache_control(response, **kwargs) return response - return wraps(viewfunc)(_cache_controlled) + return wraps(viewfunc, assigned=available_attrs(viewfunc))(_cache_controlled) return _cache_controller @@ -44,4 +44,4 @@ def never_cache(view_func): response = view_func(request, *args, **kwargs) add_never_cache_headers(response) return response - return wraps(view_func)(_wrapped_view_func) + return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view_func) diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py index b51cf9a782..9a5a314f0c 100644 --- a/django/views/decorators/http.py +++ b/django/views/decorators/http.py @@ -11,7 +11,7 @@ from calendar import timegm from datetime import timedelta from email.Utils import formatdate -from django.utils.decorators import decorator_from_middleware +from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.http import parse_etags, quote_etag from django.middleware.http import ConditionalGetMiddleware from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse @@ -35,7 +35,7 @@ def require_http_methods(request_method_list): if request.method not in request_method_list: return HttpResponseNotAllowed(request_method_list) return func(request, *args, **kwargs) - return wraps(func)(inner) + return wraps(func, assigned=available_attrs(func))(inner) return decorator require_GET = require_http_methods(["GET"]) diff --git a/django/views/decorators/vary.py b/django/views/decorators/vary.py index ea1b8d307d..e2b87a8c71 100644 --- a/django/views/decorators/vary.py +++ b/django/views/decorators/vary.py @@ -4,6 +4,7 @@ except ImportError: from django.utils.functional import wraps # Python 2.3, 2.4 fallback. from django.utils.cache import patch_vary_headers +from django.utils.decorators import available_attrs def vary_on_headers(*headers): """ @@ -21,7 +22,7 @@ def vary_on_headers(*headers): response = func(*args, **kwargs) patch_vary_headers(response, headers) return response - return wraps(func)(inner_func) + return wraps(func, assigned=available_attrs(func))(inner_func) return decorator def vary_on_cookie(func): @@ -37,4 +38,4 @@ def vary_on_cookie(func): response = func(*args, **kwargs) patch_vary_headers(response, ('Cookie',)) return response - return wraps(func)(inner_func) + return wraps(func, assigned=available_attrs(func))(inner_func) diff --git a/tests/regressiontests/utils/decorators.py b/tests/regressiontests/utils/decorators.py index c6ae243c98..ca9214f787 100644 --- a/tests/regressiontests/utils/decorators.py +++ b/tests/regressiontests/utils/decorators.py @@ -11,3 +11,9 @@ class DecoratorFromMiddlewareTests(TestCase): Test a middleware that implements process_view. """ self.client.get('/utils/xview/') + + def test_callable_process_view_middleware(self): + """ + Test a middleware that implements process_view, operating on a callable class. + """ + self.client.get('/utils/class_xview/') diff --git a/tests/regressiontests/utils/urls.py b/tests/regressiontests/utils/urls.py index 7b60d0884c..ba09d14b3d 100644 --- a/tests/regressiontests/utils/urls.py +++ b/tests/regressiontests/utils/urls.py @@ -4,4 +4,5 @@ import views urlpatterns = patterns('', (r'^xview/$', views.xview), + (r'^class_xview/$', views.class_xview), ) diff --git a/tests/regressiontests/utils/views.py b/tests/regressiontests/utils/views.py index d22ae26641..ef97c6502d 100644 --- a/tests/regressiontests/utils/views.py +++ b/tests/regressiontests/utils/views.py @@ -8,3 +8,10 @@ xview_dec = decorator_from_middleware(XViewMiddleware) def xview(request): return HttpResponse() xview = xview_dec(xview) + + +class ClassXView(object): + def __call__(self, request): + return HttpResponse() + +class_xview = xview_dec(ClassXView())