Ticket #14512: ticket14512.2.diff

File ticket14512.2.diff, 7.5 KB (added by Roald de Vries, 13 years ago)

patch based on release 1.3.1

  • docs/topics/class-based-views.txt

     
    588588--------------------
    589589
    590590To 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 the
    592 :meth:`~django.views.generic.base.View.dispatch` method of the class.
     591the class definition itself. Django provides a way to turn function
     592based view decorators into class based view decorators::
    593593
    594 A method on a class isn't quite the same as a standalone function, so
    595 you can't just apply a function decorator to the method -- you need to
    596 transform it into a method decorator first. The ``method_decorator``
    597 decorator transforms a function decorator into a method decorator so
    598 that it can be used on an instance method. For example::
    599 
    600594    from django.contrib.auth.decorators import login_required
    601     from django.utils.decorators import method_decorator
     595    from django.utils.decorators import view_decorator
    602596    from django.views.generic import TemplateView
    603597
    604     class ProtectedView(TemplateView):
     598    class ProtectedView(TemplateView): 
    605599        template_name = 'secret.html'
     600    ProtectedView = view_decorator(login_required)(ProtectedView)
    606601
    607         @method_decorator(login_required)
    608         def dispatch(self, *args, **kwargs):
    609             return super(ProtectedView, self).dispatch(*args, **kwargs)
     602In Python 2.6 and above you can also use class decorator syntax for this::
    610603
    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
    613607
    614 .. note::
     608    @view_decorator(login_required)
     609    class ProtectedView(TemplateView):
     610        template_name = 'secret.html'
     611    ProtectedView = view_decorator(login_required)(ProtectedView)
    615612
    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
     613In these examples, every instance of ``ProtectedView`` will have
     614login protection.
  • tests/regressiontests/utils/decorators.py

     
    33from django.template import Template, Context
    44from django.template.response import TemplateResponse
    55from django.test import TestCase, RequestFactory
    6 from django.utils.decorators import decorator_from_middleware
     6from django.utils.decorators import decorator_from_middleware, wraps, view_decorator
     7from django.views.generic import View
    78
    89
    910xview_dec = decorator_from_middleware(XViewMiddleware)
     
    104105        self.assertTrue(getattr(request, 'process_response_reached', False))
    105106        # Check that process_response saw the rendered content
    106107        self.assertEqual(request.process_response_content, "Hello world")
     108
     109
     110def 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
     123class 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

     
    4343    return _dec
    4444
    4545
     46class 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
    4678def decorator_from_middleware_with_args(middleware_class):
    4779    """
    4880    Like decorator_from_middleware, but returns a function
Back to Top