Index: docs/topics/class-based-views.txt
===================================================================
--- docs/topics/class-based-views.txt	(revision 16790)
+++ docs/topics/class-based-views.txt	(working copy)
@@ -588,32 +588,27 @@
 --------------------
 
 To decorate every instance of a class-based view, you need to decorate
-the class definition itself. To do this you apply the decorator to the
-:meth:`~django.views.generic.base.View.dispatch` method of the class.
+the class definition itself. Django provides a way to turn function
+based view decorators into class based view decorators:: 
 
-A method on a class isn't quite the same as a standalone function, so
-you can't just apply a function decorator to the method -- you need to
-transform it into a method decorator first. The ``method_decorator``
-decorator transforms a function decorator into a method decorator so
-that it can be used on an instance method. For example::
-
     from django.contrib.auth.decorators import login_required
-    from django.utils.decorators import method_decorator
+    from django.utils.decorators import view_decorator 
     from django.views.generic import TemplateView
 
-    class ProtectedView(TemplateView):
+    class ProtectedView(TemplateView): 
         template_name = 'secret.html'
+    ProtectedView = view_decorator(login_required)(ProtectedView) 
 
-        @method_decorator(login_required)
-        def dispatch(self, *args, **kwargs):
-            return super(ProtectedView, self).dispatch(*args, **kwargs)
+In Python 2.6 and above you can also use class decorator syntax for this:: 
 
-In this example, every instance of ``ProtectedView`` will have
-login protection.
+    from django.contrib.auth.decorators import login_required
+    from django.utils.decorators import view_decorator 
+    from django.views.generic import TemplateView
 
-.. note::
+    @view_decorator(login_required) 
+    class ProtectedView(TemplateView): 
+        template_name = 'secret.html'
+    ProtectedView = view_decorator(login_required)(ProtectedView) 
 
-    ``method_decorator`` passes ``*args`` and ``**kwargs``
-    as parameters to the decorated method on the class. If your method
-    does not accept a compatible set of parameters it will raise a
-    ``TypeError`` exception.
\ No newline at end of file
+In these examples, every instance of ``ProtectedView`` will have
+login protection.
Index: tests/regressiontests/utils/decorators.py
===================================================================
--- tests/regressiontests/utils/decorators.py	(revision 16790)
+++ tests/regressiontests/utils/decorators.py	(working copy)
@@ -3,7 +3,8 @@
 from django.template import Template, Context
 from django.template.response import TemplateResponse
 from django.test import TestCase, RequestFactory
-from django.utils.decorators import decorator_from_middleware
+from django.utils.decorators import decorator_from_middleware, wraps, view_decorator
+from django.views.generic import View
 
 
 xview_dec = decorator_from_middleware(XViewMiddleware)
@@ -104,3 +105,73 @@
         self.assertTrue(getattr(request, 'process_response_reached', False))
         # Check that process_response saw the rendered content
         self.assertEqual(request.process_response_content, "Hello world")
+
+
+def simple_dec(func): 
+    """ 
+    Simple decorator for testing view_decorator. It assumes a request
+    argument and one extra argument. Prepends string "decorator:" to the
+    result.
+    """ 
+    def wrapper(request, arg): 
+        return "decorator:" + func(request, arg) 
+    wrapper = wraps(func)(wrapper) 
+    wrapper.is_decorated = True 
+    return wrapper 
+
+
+class ClassBasedViewDecorationTests(TestCase): 
+    rf = RequestFactory()
+
+    def setUp(self):
+        class TextView(View):
+            attr = "OK"
+            def __init__(self, *args, **kwargs):
+                super(TextView, self).__init__(*args, **kwargs)
+            def dispatch(self, request, text):
+                return "view1:" + text
+        self.TextView = TextView
+
+    def test_decorate_view(self):
+        class TextView(View):
+            def get(self, request, text):
+                return "get:" + text
+            def post(self, request, text):
+                return "post:" + text
+        TextView = view_decorator(simple_dec)(TextView)
+
+        self.assertTrue(getattr(TextView.as_view(), "is_decorated", False),
+                    "Class based view decorator didn't preserve attributes.")
+        self.assertEqual(TextView.as_view()(self.rf.get('/'), "hello"), "decorator:get:hello")
+        self.assertEqual(TextView.as_view()(self.rf.post('/'), "hello"), "decorator:post:hello")
+
+    def test_super_calls(self):
+        # NOTE: it's important for this test, that the definition
+        # and decoration of the class happens in the *same scope*.
+        class ViewWithSuper(self.TextView):
+            def __init__(self, **initargs):
+                self.recursion_count = 0
+                super(ViewWithSuper, self).__init__(**initargs)
+
+            def dispatch(self, *args, **kwargs):
+                self.recursion_count += 1
+                if self.recursion_count > 10:
+                    raise Exception("Decoration caused recursive super() calls.")
+                return "view2:" + super(ViewWithSuper, self).dispatch(*args, **kwargs)
+        ViewWithSuper = view_decorator(simple_dec)(ViewWithSuper)
+
+        self.assertEqual(ViewWithSuper.as_view()(self.rf.get('/'), "A"), "decorator:view2:view1:A")
+
+    def test_base_unmodified(self):
+        DecoratedView = view_decorator(simple_dec)(self.TextView)
+
+        # since a super(TestView) is called in the __init__ method of the following
+        # assertion, and the DecorationView instance is no instance of TestView,
+        # a TypeError is raised. This is a Python subtlety, and therefore not the
+        # concern of view_decorator.
+        self.assertRaises(TypeError, DecoratedView.as_view(), (self.rf.get('/'), "A"))
+
+        self.assertEqual(self.TextView.as_view()(self.rf.get('/'), "A"), "view1:A")
+        self.assertFalse(DecoratedView is self.TextView)
+        self.assertEqual(DecoratedView.mro(), [DecoratedView] + self.TextView.mro()[1:])
+
Index: django/utils/decorators.py
===================================================================
--- django/utils/decorators.py	(revision 16790)
+++ django/utils/decorators.py	(working copy)
@@ -43,6 +43,38 @@
     return _dec
 
 
+class view_decorator(object):
+    '''
+    Used to convert a function based view decorator into a class based
+    view decorator. Use it like::
+
+        class ProtectedView(View):
+            pass
+        ProtectedView = view_decorator(login_required)(ProtectedView)
+
+    or::
+
+        @view_decorator(login_required)
+        class ProtectedView(View):
+            pass
+    '''
+    def __init__(self, decorator):
+        super(view_decorator, self).__init__()
+        self.decorator = decorator
+
+    def __call__(self, cbv):
+        '''
+        The result is a new class with the same base classes as the
+        original class, and the original class is left unchanged.
+        '''
+        decorated_cbv = type(cbv.__name__, cbv.__bases__, dict(cbv.__dict__))
+        @classonlymethod
+        def as_view(cls, decorator=self.decorator, original=decorated_cbv.as_view):
+            return decorator(original())
+        decorated_cbv.as_view = as_view
+        return decorated_cbv
+
+
 def decorator_from_middleware_with_args(middleware_class):
     """
     Like decorator_from_middleware, but returns a function
