I am trying to understand python decorators so I decided to check the inner workings of django @login_required decorator. After looking at the source code, I got here:
def user_passes_test(test_func, login_url=None,
redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
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.
"""
From what I understand, the above function is supposed to use the return value of test_func to determine whether to redirect user to login page or allow user to continue. My problem is that i can't find anywhere this test_func is called. How does the above function user_passes_test work exactly. Any help will be appreciated.
edit:
I have realized that I am the problem since I was looking at the source code on django documentation and I have just noticed that there is an indentation error. Am all good now.
It's called in this line .
To explain what's going on, let's use a simpler example. Assume you have various functions (corresponding to the views) that need to have their arguments checked (corresponding to the user check) before being executed; if the arguments fail the check, the result should be None
(corresponding to the login redirect).
Here's example code, the structure of which matches Django's user_passes_test
:
def arguments_pass_test(test_func):
def decorator(func):
def _wrapped_func(*args, **kwargs):
if test_func(*args, **kwargs):
return func(*args, **kwargs)
else:
return None
return _wrapped_func
return decorator
@arguments_pass_test(lambda x: x != 0)
def inverse(x):
return 1.0 / x
print(inverse(2)) # 0.5
print(inverse(0)) # None
So let's look at how the decorator magic happens, that is this part:
@arguments_pass_test(lambda x: x != 0)
def inverse(x):
return 1.0 / x
First off, arguments_pass_test(lambda x: x != 0)
is just a function call; the @
doesn't come into play yet. The return value from that function call to arguments_pass_test
is the inner function called decorator
, and the naming makes it clear that this function is the actual decorator.
So now we have this:
@decorator
def inverse(x):
return 1.0 / x
The decorator syntax gets translated by Python into something roughly equivalent to this:
def inverse(x):
return 1.0 / x
inverse = decorator(inverse)
So inverse
gets replaced with the the result of calling the decorator
with the original function inverse
as its argument func
. The result of calling the decorator is what's called _wrapped_func
. So what's happening is similar to this:
def _original_inverse(x):
return 1.0 / x
def _wrapped_func(x):
if test_func(x): # this doesn't exist here, but bear with me
return _original_inverse(x)
else
return None
inverse = _wrapped_func
which is finally equivalent(ish) to
def inverse(x):
if x != 0:
return 1.0 / x
else
return None
which is exactly what we were going for.
I'm not sure where you got that function, but I just freshly install django
through pip
on my Python 3.6, and here is what I found in django.contrib.auth.decorators
. The following is accessible here as well.
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
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.
"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return _wrapped_view
return decorator
This makes much more sense, since test_func
is effectively called inside of the decorator.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.