Ticket #14512: ticket14512.4.diff
File ticket14512.4.diff, 8.5 KB (added by , 13 years ago) |
---|
-
docs/topics/class-based-views.txt
588 588 -------------------- 589 589 590 590 To decorate every instance of a class-based view, you need to decorate 591 the class definition itself. To do this you apply the decorator to the592 :meth:`~django.views.generic.base.View.dispatch` method of the class. 591 the class definition itself. Django provides a way to turn function 592 based view decorators into class based view decorators:: 593 593 594 A method on a class isn't quite the same as a standalone function, so595 you can't just apply a function decorator to the method -- you need to596 transform it into a method decorator first. The ``method_decorator``597 decorator transforms a function decorator into a method decorator so598 that it can be used on an instance method. For example::599 600 594 from django.contrib.auth.decorators import login_required 601 from django.utils.decorators import method_decorator595 from django.utils.decorators import view_decorator 602 596 from django.views.generic import TemplateView 603 597 604 class ProtectedView(TemplateView): 598 class ProtectedView(TemplateView): 605 599 template_name = 'secret.html' 600 ProtectedView = view_decorator(login_required)(ProtectedView) 606 601 607 @method_decorator(login_required) 608 def dispatch(self, *args, **kwargs): 609 return super(ProtectedView, self).dispatch(*args, **kwargs) 602 In Python 2.6 and above you can also use class decorator syntax for this:: 610 603 611 In this example, every instance of ``ProtectedView`` will have 612 login protection. 604 from django.contrib.auth.decorators import login_required 605 from django.utils.decorators import view_decorator 606 from django.views.generic import TemplateView 613 607 614 .. note:: 608 @view_decorator(login_required) 609 class ProtectedView(TemplateView): 610 template_name = 'secret.html' 615 611 616 ``method_decorator`` passes ``*args`` and ``**kwargs`` 617 as parameters to the decorated method on the class. If your method 618 does not accept a compatible set of parameters it will raise a 619 ``TypeError`` exception. 620 No newline at end of file 612 In these examples, every instance of ``ProtectedView`` will have 613 login protection. -
tests/regressiontests/utils/decorators.py
3 3 from django.template import Template, Context 4 4 from django.template.response import TemplateResponse 5 5 from django.test import TestCase, RequestFactory 6 from django.utils.decorators import decorator_from_middleware 6 from django.utils.decorators import decorator_from_middleware, wraps, view_decorator 7 from django.views.generic import View 7 8 8 9 9 10 xview_dec = decorator_from_middleware(XViewMiddleware) … … 104 105 self.assertTrue(getattr(request, 'process_response_reached', False)) 105 106 # Check that process_response saw the rendered content 106 107 self.assertEqual(request.process_response_content, "Hello world") 108 109 110 def simple_dec(func): 111 """ 112 Simple decorator for testing view_decorator. It assumes a request 113 argument and one extra argument. Prepends string "decorator:" to the 114 result. 115 """ 116 def wrapper(request, arg, func=func): 117 return "decorator:" + func(request, arg) 118 wrapper = wraps(func)(wrapper) 119 wrapper.is_decorated = True 120 return wrapper 121 122 123 class TextView(View): 124 def __init__(self, *args, **kwargs): 125 super(TextView, self).__init__(*args, **kwargs) 126 def get(self, request, text): 127 return "get1:" + text 128 def post(self, request, text): 129 return "post1:" + text 130 TextView = view_decorator(simple_dec)(TextView) 131 132 133 class BaseTextView(View): 134 def __init__(self, *args, **kwargs): 135 super(BaseTextView, self).__init__(*args, **kwargs) 136 137 def get(self, request, text): 138 return "get1:" + text 139 140 141 class ExtendedTextView(BaseTextView): 142 def __init__(self, **initargs): 143 self.recursion_count = 0 144 super(ExtendedTextView, self).__init__(**initargs) 145 146 def get(self, *args, **kwargs): 147 self.recursion_count += 1 148 if self.recursion_count > 10: 149 raise Exception("Decoration caused recursive super() calls.") 150 return "get2:" + super(ExtendedTextView, self).get(*args, **kwargs) 151 ExtendedTextView = view_decorator(simple_dec)(ExtendedTextView) 152 153 154 class ClassBasedViewDecorationTests(TestCase): 155 rf = RequestFactory() 156 157 def test_decorate_view(self): 158 self.assertTrue(getattr(TextView.as_view(), "is_decorated", False), 159 "Class based view decorator didn't preserve attributes.") 160 self.assertEqual(TextView.as_view()(self.rf.get('/'), "hello"), 161 "decorator:get1:hello") 162 self.assertEqual(TextView.as_view()(self.rf.post('/'), "hello"), 163 "decorator:post1:hello") 164 165 def test_decorate_derived_view(self): 166 # NOTE: it's important for this test, that the definition 167 # and decoration of the class happens in the *same scope*. 168 view = ExtendedTextView.as_view() 169 result = view(self.rf.get('/'), "A") 170 self.assertEqual(result, "decorator:get2:get1:A") 171 172 def test_base_unmodified(self): 173 DecoratedView = view_decorator(simple_dec)(BaseTextView) 174 175 # since a super(TestView) is called in the __init__ method of the 176 # following assertion, and the DecorationView instance is no instance of 177 # TestView, a TypeError is raised. This is a Python subtlety, and 178 # therefore not the concern of view_decorator. 179 self.assertRaises(TypeError, DecoratedView.as_view(), (self.rf.get('/'), "A")) 180 181 self.assertEqual(BaseTextView.as_view()(self.rf.get('/'), "A"), "get1:A") 182 self.assertFalse(DecoratedView is BaseTextView) 183 self.assertEqual(DecoratedView.mro(), [DecoratedView] + BaseTextView.mro()[1:]) 184 -
django/utils/decorators.py
43 43 return _dec 44 44 45 45 46 class as_view_wrapper(object): 47 'An instance of this class should be decorated with classonlymethod' 48 def __init__(self, decorator, original): 49 super(as_view_wrapper, self).__init__() 50 self.decorator = decorator 51 self.original = original 52 53 def __call__(self, *args, **kwargs): 54 view = self.original(*args, **kwargs) 55 return self.decorator(view) 56 57 58 class view_decorator(object): 59 ''' 60 Used to convert a function based view decorator into a class based 61 view decorator. Use it like:: 62 63 class ProtectedView(View): 64 pass 65 ProtectedView = view_decorator(login_required)(ProtectedView) 66 67 or:: 68 69 @view_decorator(login_required) 70 class ProtectedView(View): 71 pass 72 ''' 73 def __init__(self, decorator): 74 super(view_decorator, self).__init__() 75 self.decorator = decorator 76 77 def __call__(self, view): 78 ''' 79 The result is a new class with the same base classes as the 80 original class, and the original class is left unchanged. 81 ''' 82 # first make a (shallow) copy of the original view 83 decorated_view = type(view.__name__, view.__bases__, dict(view.__dict__)) 84 85 # the original classonlymethod `as_view` is associated with the class 86 # it is defined in, so we first need to get the original function 87 # definition back 88 original_as_view_function = decorated_view.as_view.__func__ 89 90 # make sure the decorated `as_view` classonlymethod uses the right 91 # decorator and original `as_view` function; we can't rely on the 92 # dynamic resolution that python uses, so we have to associate them 93 # with the callable object (see 94 # docs.python.org/tutorial/classes.html#python-scopes-and-namespaces) 95 as_view = as_view_wrapper(self.decorator, original_as_view_function) 96 97 # now, the decorated `as_view` classonlymethod can replace the 98 # original one 99 decorated_view.as_view = classonlymethod(as_view) 100 return decorated_view 101 102 46 103 def decorator_from_middleware_with_args(middleware_class): 47 104 """ 48 105 Like decorator_from_middleware, but returns a function