Skip to content

Instantly share code, notes, and snippets.

@jacobian
Created September 15, 2011 20:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobian/1220375 to your computer and use it in GitHub Desktop.
Save jacobian/1220375 to your computer and use it in GitHub Desktop.
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 5805a31..a07a4d9 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -1,10 +1,11 @@
+import inspect
import urlparse
from functools import wraps
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs
-
+from django.http import HttpRequest
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
@@ -13,11 +14,46 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
that takes the user object and returns True if the user passes.
"""
- def decorator(view_func):
- @wraps(view_func, assigned=available_attrs(view_func))
- def _wrapped_view(request, *args, **kwargs):
+ def decorator(obj):
+ # Case 1: decorated class.
+ if inspect.isclass(obj):
+ # FIXME 1: this should be done in a way that mimics @wraps: the
+ # class name should stay the same (or similar), and
+ # docstrings should be preserved.
+ # FIXME 2: This shouldn't just assume that the object is a CBV and
+ # wrap dispatch; it should inspect and figure it out and
+ # raise an error if it's the wrong type.
+ class LoginProtectedSubclass(obj):
+ @user_passes_test(test_func, login_url, redirect_field_name)
+ def dispatch(self, *args, **kwargs):
+ return super(LoginProtectedSubclass, self).dispatch(*args, **kwargs)
+ return LoginProtectedSubclass
+
+ # Case 2: decorated function or method.
+ # These two case differ: one will have a signature like (request, ...),
+ # and the other will be (self, request, ...). Since we need to inspect
+ # request this poses a bit of a problem. Now, We don't know which one's
+ # which just yet, so we have to wait until the decorator is called to
+ # figure it out.
+ @wraps(obj, assigned=available_attrs(obj))
+ def _wrapped_view(*args, **kwargs):
+ # If this is a function, args[0] will be the request object. If
+ # this is a method, args[0] will be self and args[1] will be the
+ # request object. We'll avoid just naively looking at args[0]
+ # and args[1], though: that'll raise an IndexError if the decorator
+ # is used on a non-view-function. That's a useless error message,
+ # so we'll raise something a bit nicer instead.
+ request = None
+ for arg in args[0:2]:
+ if isinstance(arg, HttpRequest):
+ request = arg
+ break
+ if request is None:
+ # FIXME: better error message, please.
+ raise TypeError("View wasn't called with a HttpRequest object.")
+
if test_func(request.user):
- return view_func(request, *args, **kwargs)
+ return obj(*args, **kwargs)
path = request.build_absolute_uri()
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment