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,26 @@
 --------------------
 
 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'
 
-    ``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,80 @@
         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, func=func): 
+        return "decorator:" + func(request, arg) 
+    wrapper = wraps(func)(wrapper) 
+    wrapper.is_decorated = True 
+    return wrapper 
+
+
+class TextView(View):
+    def __init__(self, *args, **kwargs):
+        super(TextView, self).__init__(*args, **kwargs)
+    def get(self, request, text):
+        return "get1:" + text
+    def post(self, request, text):
+        return "post1:" + text
+TextView = view_decorator(simple_dec)(TextView)
+
+
+class BaseTextView(View):
+    def __init__(self, *args, **kwargs):
+        super(BaseTextView, self).__init__(*args, **kwargs)
+
+    def get(self, request, text):
+        return "get1:" + text
+
+
+class ExtendedTextView(BaseTextView):
+    def __init__(self, **initargs):
+        self.recursion_count = 0
+        super(ExtendedTextView, self).__init__(**initargs)
+
+    def get(self, *args, **kwargs):
+        self.recursion_count += 1
+        if self.recursion_count > 10:
+            raise Exception("Decoration caused recursive super() calls.")
+        return "get2:" + super(ExtendedTextView, self).get(*args, **kwargs)
+ExtendedTextView = view_decorator(simple_dec)(ExtendedTextView)
+
+
+class ClassBasedViewDecorationTests(TestCase): 
+    rf = RequestFactory()
+
+    def test_decorate_view(self):
+        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:get1:hello")
+        self.assertEqual(TextView.as_view()(self.rf.post('/'), "hello"),
+                         "decorator:post1:hello")
+
+    def test_decorate_derived_view(self):
+        # NOTE: it's important for this test, that the definition
+        # and decoration of the class happens in the *same scope*.
+        view = ExtendedTextView.as_view()
+        result = view(self.rf.get('/'), "A")
+        self.assertEqual(result, "decorator:get2:get1:A")
+
+    def test_base_unmodified(self):
+        DecoratedView = view_decorator(simple_dec)(BaseTextView)
+
+        # 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(BaseTextView.as_view()(self.rf.get('/'), "A"), "get1:A")
+        self.assertFalse(DecoratedView is BaseTextView)
+        self.assertEqual(DecoratedView.mro(), [DecoratedView] + BaseTextView.mro()[1:])
+
Index: django/utils/decorators.py
===================================================================
--- django/utils/decorators.py	(revision 16790)
+++ django/utils/decorators.py	(working copy)
@@ -43,6 +43,63 @@
     return _dec
 
 
+class as_view_wrapper(object):
+    'An instance of this class should be decorated with classonlymethod'
+    def __init__(self, decorator, original):
+        super(as_view_wrapper, self).__init__()
+        self.decorator = decorator
+        self.original = original
+
+    def __call__(self, *args, **kwargs):
+        view = self.original(*args, **kwargs)
+        return self.decorator(view)
+
+
+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, view):
+        '''
+        The result is a new class with the same base classes as the
+        original class, and the original class is left unchanged.
+        '''
+        # first make a (shallow) copy of the original view
+        decorated_view = type(view.__name__, view.__bases__, dict(view.__dict__))
+
+        # the original classonlymethod `as_view` is associated with the class
+        # it is defined in, so we first need to get the original function
+        # definition back
+        original_as_view_function = decorated_view.as_view.__func__
+
+        # make sure the decorated `as_view` classonlymethod uses the right
+        # decorator and original `as_view` function; we can't rely on the
+        # dynamic resolution that python uses, so we have to associate them
+        # with the callable object (see
+        # docs.python.org/tutorial/classes.html#python-scopes-and-namespaces)
+        as_view = as_view_wrapper(self.decorator, original_as_view_function)
+
+        # now, the decorated `as_view` classonlymethod can replace the
+        # original one
+        decorated_view.as_view = classonlymethod(as_view)
+        return decorated_view
+
+
 def decorator_from_middleware_with_args(middleware_class):
     """
     Like decorator_from_middleware, but returns a function
