Ticket #14512: ticket14512.2.diff
File ticket14512.2.diff, 7.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' 611 ProtectedView = view_decorator(login_required)(ProtectedView) 615 612 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 613 In these examples, every instance of ``ProtectedView`` will have 614 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): 117 return "decorator:" + func(request, arg) 118 wrapper = wraps(func)(wrapper) 119 wrapper.is_decorated = True 120 return wrapper 121 122 123 class ClassBasedViewDecorationTests(TestCase): 124 rf = RequestFactory() 125 126 def setUp(self): 127 class TextView(View): 128 attr = "OK" 129 def __init__(self, *args, **kwargs): 130 super(TextView, self).__init__(*args, **kwargs) 131 def dispatch(self, request, text): 132 return "view1:" + text 133 self.TextView = TextView 134 135 def test_decorate_view(self): 136 class TextView(View): 137 def get(self, request, text): 138 return "get:" + text 139 def post(self, request, text): 140 return "post:" + text 141 TextView = view_decorator(simple_dec)(TextView) 142 143 self.assertTrue(getattr(TextView.as_view(), "is_decorated", False), 144 "Class based view decorator didn't preserve attributes.") 145 self.assertEqual(TextView.as_view()(self.rf.get('/'), "hello"), "decorator:get:hello") 146 self.assertEqual(TextView.as_view()(self.rf.post('/'), "hello"), "decorator:post:hello") 147 148 def test_super_calls(self): 149 # NOTE: it's important for this test, that the definition 150 # and decoration of the class happens in the *same scope*. 151 class ViewWithSuper(self.TextView): 152 def __init__(self, **initargs): 153 self.recursion_count = 0 154 super(ViewWithSuper, self).__init__(**initargs) 155 156 def dispatch(self, *args, **kwargs): 157 self.recursion_count += 1 158 if self.recursion_count > 10: 159 raise Exception("Decoration caused recursive super() calls.") 160 return "view2:" + super(ViewWithSuper, self).dispatch(*args, **kwargs) 161 ViewWithSuper = view_decorator(simple_dec)(ViewWithSuper) 162 163 self.assertEqual(ViewWithSuper.as_view()(self.rf.get('/'), "A"), "decorator:view2:view1:A") 164 165 def test_base_unmodified(self): 166 DecoratedView = view_decorator(simple_dec)(self.TextView) 167 168 # since a super(TestView) is called in the __init__ method of the following 169 # assertion, and the DecorationView instance is no instance of TestView, 170 # a TypeError is raised. This is a Python subtlety, and therefore not the 171 # concern of view_decorator. 172 self.assertRaises(TypeError, DecoratedView.as_view(), (self.rf.get('/'), "A")) 173 174 self.assertEqual(self.TextView.as_view()(self.rf.get('/'), "A"), "view1:A") 175 self.assertFalse(DecoratedView is self.TextView) 176 self.assertEqual(DecoratedView.mro(), [DecoratedView] + self.TextView.mro()[1:]) 177 -
django/utils/decorators.py
43 43 return _dec 44 44 45 45 46 class view_decorator(object): 47 ''' 48 Used to convert a function based view decorator into a class based 49 view decorator. Use it like:: 50 51 class ProtectedView(View): 52 pass 53 ProtectedView = view_decorator(login_required)(ProtectedView) 54 55 or:: 56 57 @view_decorator(login_required) 58 class ProtectedView(View): 59 pass 60 ''' 61 def __init__(self, decorator): 62 super(view_decorator, self).__init__() 63 self.decorator = decorator 64 65 def __call__(self, cbv): 66 ''' 67 The result is a new class with the same base classes as the 68 original class, and the original class is left unchanged. 69 ''' 70 decorated_cbv = type(cbv.__name__, cbv.__bases__, dict(cbv.__dict__)) 71 @classonlymethod 72 def as_view(cls, decorator=self.decorator, original=decorated_cbv.as_view): 73 return decorator(original()) 74 decorated_cbv.as_view = as_view 75 return decorated_cbv 76 77 46 78 def decorator_from_middleware_with_args(middleware_class): 47 79 """ 48 80 Like decorator_from_middleware, but returns a function