Index: django/contrib/auth/decorators.py
===================================================================
--- django/contrib/auth/decorators.py	(revision 5312)
+++ django/contrib/auth/decorators.py	(working copy)
@@ -8,27 +8,16 @@
     redirecting to the log-in page if necessary. The test should be a callable
     that takes the user object and returns True if the user passes.
     """
-    if not login_url:
-        from django.conf import settings
-        login_url = settings.LOGIN_URL
-    def _dec(view_func):
-        def _checklogin(request, *args, **kwargs):
-            if test_func(request.user):
-                return view_func(request, *args, **kwargs)
-            return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
-        _checklogin.__doc__ = view_func.__doc__
-        _checklogin.__dict__ = view_func.__dict__
+    def decorate(view_func):
+        return _CheckLogin(view_func, test_func, login_url)
+    return decorate
 
-        return _checklogin
-    return _dec
-
-login_required = user_passes_test(lambda u: u.is_authenticated())
-login_required.__doc__ = (
+def login_required(view_func):
     """
     Decorator for views that checks that the user is logged in, redirecting
     to the log-in page if necessary.
     """
-    )
+    return _CheckLogin(view_func, lambda u: u.is_authenticated())
 
 def permission_required(perm, login_url=None):
     """
@@ -37,3 +26,34 @@
     """
     return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
 
+
+class _CheckLogin(object):
+    """
+    Class that checks that the user passes the given test, redirecting to
+    the log-in page if necessary. If the test is passed, the view function
+    is invoked. The test should be a callable that takes the user object
+    and returns True if the user passes.
+
+    We use a class here so that we can define __get__. This way, when a
+    _CheckLogin object is used as a method decorator, the view function
+    is properly bound to its instance.
+    """
+    def __init__(self, view_func, test_func, login_url=None):
+        if not login_url:
+            from django.conf import settings
+            login_url = settings.LOGIN_URL
+        self.view_func = view_func
+        self.test_func = test_func
+        self.login_url = login_url
+        
+    def __get__(self, obj, cls=None):
+        view_func = self.view_func.__get__(obj, cls)
+        return _CheckLogin(view_func, self.test_func, self.login_url)
+    
+    def __call__(self, request, *args, **kwargs):
+        if self.test_func(request.user):
+            return self.view_func(request, *args, **kwargs)
+        path = quote(request.get_full_path())
+        tup = self.login_url, REDIRECT_FIELD_NAME, path
+        return HttpResponseRedirect('%s?%s=%s' % tup)
+
Index: tests/modeltests/test_client/models.py
===================================================================
--- tests/modeltests/test_client/models.py	(revision 5312)
+++ tests/modeltests/test_client/models.py	(working copy)
@@ -218,6 +218,49 @@
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.context['user'].username, 'testclient')
 
+    def test_view_with_method_login(self):
+        "Request a page that is protected with a @login_required method"
+        
+        # Get the page without logging in. Should result in 302.
+        response = self.client.get('/test_client/login_protected_method_view/')
+        self.assertRedirects(response, '/accounts/login/')
+        
+        # Log in
+        self.client.login(username='testclient', password='password')
+
+        # Request a page that requires a login
+        response = self.client.get('/test_client/login_protected_method_view/')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['user'].username, 'testclient')
+
+    def test_view_with_permissions(self):
+        "Request a page that is protected with @permission_required"
+        
+        # Get the page without logging in. Should result in 302.
+        response = self.client.get('/test_client/permission_protected_view/')
+        self.assertRedirects(response, '/accounts/login/')
+        
+        # Log in with wrong permissions. Should result in 302.
+        self.client.login(username='testclient', password='password')
+        response = self.client.get('/test_client/permission_protected_view/')
+        self.assertRedirects(response, '/accounts/login/')
+
+        # TODO: Log in with right permissions and request the page again
+
+    def test_view_with_method_permissions(self):
+        "Request a page that is protected with a @permission_required method"
+        
+        # Get the page without logging in. Should result in 302.
+        response = self.client.get('/test_client/permission_protected_method_view/')
+        self.assertRedirects(response, '/accounts/login/')
+        
+        # Log in with wrong permissions. Should result in 302.
+        self.client.login(username='testclient', password='password')
+        response = self.client.get('/test_client/permission_protected_method_view/')
+        self.assertRedirects(response, '/accounts/login/')
+
+        # TODO: Log in with right permissions and request the page again
+
     def test_view_with_bad_login(self):
         "Request a page that is protected with @login, but use bad credentials"
 
Index: tests/modeltests/test_client/urls.py
===================================================================
--- tests/modeltests/test_client/urls.py	(revision 5312)
+++ tests/modeltests/test_client/urls.py	(working copy)
@@ -13,6 +13,9 @@
     (r'^form_view/$', views.form_view),
     (r'^form_view_with_template/$', views.form_view_with_template),
     (r'^login_protected_view/$', views.login_protected_view),
+    (r'^permission_protected_view/$', views.permission_protected_view),
+    (r'^login_protected_method_view/$', views.login_protected_method_view),
+    (r'^permission_protected_method_view/$', views.permission_protected_method_view),
     (r'^session_view/$', views.session_view),
     (r'^broken_view/$', views.broken_view),
     (r'^mail_sending_view/$', views.mail_sending_view),
Index: tests/modeltests/test_client/views.py
===================================================================
--- tests/modeltests/test_client/views.py	(revision 5312)
+++ tests/modeltests/test_client/views.py	(working copy)
@@ -2,7 +2,7 @@
 from django.core.mail import EmailMessage, SMTPConnection
 from django.template import Context, Template
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
-from django.contrib.auth.decorators import login_required
+from django.contrib.auth.decorators import login_required, permission_required
 from django.newforms.forms import Form
 from django.newforms import fields
 from django.shortcuts import render_to_response
@@ -112,10 +112,43 @@
 def login_protected_view(request):
     "A simple view that is login protected."
     t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
-    c = Context({'user': request.user})
+    c = Context({'user': request.user})
     
     return HttpResponse(t.render(c))
-login_protected_view = login_required(login_protected_view)
+login_protected_view = login_required(login_protected_view)
+
+def permission_protected_view(request):
+    "A simple view that is permission protected."
+    t = Template('This is a permission protected test. '
+                 'Username is {{ user.username }}. '
+                 'Permissions are {{ user.get_all_permissions }}.' ,
+                 name='Permissions Template')
+    c = Context({'user': request.user})
+    return HttpResponse(t.render(c))
+permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view)
+
+class _ViewManager(object):
+    def login_protected_view(self, request):
+        t = Template('This is a login protected test using a method. '
+                     'Username is {{ user.username }}.',
+                     name='Login Method Template')
+        c = Context({'user': request.user})
+        return HttpResponse(t.render(c))
+    login_protected_view = login_required(login_protected_view)
+
+    def permission_protected_view(self, request):
+        t = Template('This is a permission protected test using a method. '
+                     'Username is {{ user.username }}. '
+                     'Permissions are {{ user.get_all_permissions }}.' ,
+                     name='Permissions Template')
+        c = Context({'user': request.user})
+        return HttpResponse(t.render(c))
+    permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view)
+
+_view_manager = _ViewManager()
+login_protected_method_view = _view_manager.login_protected_view
+permission_protected_method_view = _view_manager.permission_protected_view
+
 
 def session_view(request):
     "A view that modifies the session"
