Differentiating unauthenticated users in custom decorator - python

Django beginner here.
I have been using the inbuilt login_required decorator. I want to override it for certain users who's referral urls match a certain pattern (e.g. all users originating from /buy_and_sell/).
My purpose is to show a special login page to just these users, and a generic one to everyone else.
I've been looking at various examples of writing custom decorators (e.g. here, here, here and here). But I find the definitions hard to grasp for a beginner. Can someone give me a layman's understanding (and preferably illustrative example) of how I can solve my problem?

There is user_passes_test decorator included in Django.You do not have to make your own decorator.
from django.contrib.auth.decorators import user_passes_test
def check_special_user(user):
return user.filter(is_special=True)
# if not the special user it will redirect to another login url , otherwise process the view
#user_passes_test(check_special_user,login_url='/login/')
def my_view(request):
pass
...
Need Request in decorator
To do that make a clone version of user_passes_test in your project or app and make change as follow,
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): # change this line to request instead of 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
change test_func(request.user) to test_func(request) and you will get
whole request in your decorator function.
Edit: In url.py ,
url (
r'^your-url$',
user_passes_test(check_special_user, login_url='/login/')(
my_view
),
name='my_view'
)

Here the best answer to understand python decorators : How to make a chain of function decorators?
You can use login_url argument of login_required :
#login_required(login_url='some_url)
Another way is to create a custom decorator, an example from the documentation of Django :
from django.contrib.auth.decorators import user_passes_test
def email_check(user):
return user.email.endswith('#example.com')
#user_passes_test(email_check)
def my_view(request):
...

Related

How to test Django custom decorator with different values?

I've got a function based view like this:
#new_group_required("special_group", raise_exception=True)
def my_view(request):
<my code>
I'd like to test this as if the raise_exception was set to False but I can't seem to figure out through google how to do this.
Some people recommend using Mock but I'm pretty unfamiliar with Mock and how I can run my_view, but change just the attribute of that decorator.
Any help would be appreciated!
The decorator code looks like this (very similar to the built-in Permission decorator from Django):
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)
def _wrapper_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 _wrapper_view
return decorator
def new_group_required(group, login_url=None, raise_exception=False):
"""
Decorator for views that checks whether a user has a particular group
enabled, redirecting to the log-in page if necessary.
If the raise_exception parameter is given the PermissionDenied exception
is raised.
"""
def check_groups(user):
if isinstance(group, str):
groups = (group,)
else:
groups = group
# First check if the user has the group (even anon users)
if user.has_groups(groups):
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form
return False
return user_passes_test(check_groups, login_url=login_url)
I've currently got a test like this with a User that doesn't have a Group and it passes. I'm just trying to test the situation where a user doesn't have a Group and make sure it redirects them to the login_url appropriately when raise_exception is False.
def test_my_view_not_accessible_to_ungrouped_users(self):
self.client.force_login(user=self.user_without_group)
response = self.client.get("/my_app/my_view/")
self.assertEqual(response.status_code, 403)

How do Django authorization decorators (such as: login required) work?

I am trying to better understand "behind the scenes" of the Django authorization decorators.
Although I think I understand decorators in general, I find it difficult to understand the authorization decorators code.
Is there any "line by line" explanation of the code (https://docs.djangoproject.com/en/2.2/_modules/django/contrib/auth/decorators/)?
I don't know of anywhere that has a line-by-line documentation of these decorators, but here's my take on it.
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
This function serves as the basis for Django's decorator based authentication.
It accepts a test function which will have the user passed to it to determine
whether or not that user has access to the decorated view.
def decorator(view_func):
#wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
This chunk of code is just standard Python decorator stuff - if you understand
decorators there's not really anything to explain.
if test_func(request.user):
return view_func(request, *args, **kwargs)
Here the test function is called with the user. If the user passes the test
the original view function is immediately returned and no further action is
taken.
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
Retrieve the current request's url and the login url. The login url can be
passed to the user_passes_test decorator or the default value in the Django
settings can be used.
# 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]
Retrieve the HTTP scheme (http or https) and the netloc (www.example.com plus
the port if applicable) from both the current url and the login url.
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
If the HTTP scheme and the netloc for the two urls match then the path is set
to the relative url rather than the absolute url.
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
Redirect the request to the login page. redirect_to_login will send the user
to get logged in with a ?next= GET parameter equal to the current path.
return _wrapped_view
return decorator
Finishing decorator stuff.
login_required is just a shortcut for user_passes_test which already supplies the test_func - a simple function which checks the value of user.is_authenticated.
permission_required Does the same but takes the name of a permission or a list of permission names and checks that the user has those permissions. permission_required also has the added feature that you can pass raise_exception=True to raise a 403 instead of redirecting to the login url.

Django user_passes_test usage

I have a function-based view in Django:
#login_required
def bout_log_update(request, pk):
...
While it's protected from people who aren't logged in, I need to be able to restrict access to this view based on:
1. The user currently logged in
2. Which user created the object (referred to by pk)
It needs to be accessible only if the currently logged in user created the object being accessed, or is a superuser.
Can the standard #user_passes_test decorator accomplish this? Or a custom decorator? Or another method entirely?
I'd re-write it as a class-based view and use UserPassesTestMixin if I could, but I don't know that it's possible for this particular view.
You can achieve this quite easily with a custom decorator based on user_passes_test source:
def my_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):
# the following line is the only change with respect to
# user_passes_test:
if test_func(request.user, *args, **kwargs):
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
Note that just one line is changed from test_func(request.user) to test_func(request.user, *args, **kwargs) so that all arguments passed to the view are passed to the test function too.

Nested decorators, raising 404

Suppose I have view
def foo(request)
I'm also using custom user model as follow:
class MyUser(AbstractUser):
field = models.BooleanField()
I'd like to combine 2 django decorators: login_required and user_passed_test, so that anonymous user should be redirected to a login page and user who is not allowed to see the view (user.field == False) should see a proper message (say, something like 'you're not allowed to see this').
So I tried:
my_decor = user_passes_test(lambda user: user.field == True,
login_url='/bar')
def custom_decor(view_func):
decorated_view_func = login_required(my_decor(view_func), login_url='/login')
return decorated_view_func
And I also have to define view:
def bar(request):
return HttpResponse('you are not allowed to see this context.')
and hardcode it in my urls.
The question is: can I do this without creating an additional view and adding it to urls? Is there a way to make 'user_passed_test' decorator raising an 404 error instead of redirecting to a login view?
There is probably a very simple solution and surely I'm just missing it.
I dont remember very well the decorators syntax, but you can do:
def custom_decor(view_func):
def decorator(request, *args, **kwargs)
if request.user.field is True:
return view_func(request, *arg, **kwargs)
raise Http404
return decorator
so...
#login_required(login_url='/login')
#custom_decor
def foo(request)
# ...

Django: Tweaking #login_required decorator

I want to begin a private Beta for my website. I have a splash page where a user can enter a code to then access the rest of the site. Currently, all the other site pages (except the splash page) consist of a series of redirects set up by requiring user login (via #login_required decorator).
I want both logged in users and people who enter the Beta Tester code to be able to access the rest of the site. That means that I can't just use the decorator for all my views.
Should I alter the #login_required decorator itself? I'm more tempted to just do the following (I added a session variable if user enters correct code on splash page).
def view_name(request):
user=request.user
if not user.id or not request.session.get('code_success'):
return HttpResponseRedirect('/splash/')
Does this seem reasonable? I'd hate to have to repeat it for all my views
Brendan
Write your own decorator - it's fairly straight forward. In fact, if you look at the Django source for login_required, you should be able to fiddle around with a copy for your own purposes.
def my_login_required(function):
def wrapper(request, *args, **kw):
user=request.user
if not (user.id and request.session.get('code_success')):
return HttpResponseRedirect('/splash/')
else:
return function(request, *args, **kw)
return wrapper
I would recommend using a middleware instead. That will make it easier to drop once you move out of your private beta. There are a couple examples of login required middlewares on djangonsippets:
http://djangosnippets.org/snippets/1220/
http://djangosnippets.org/snippets/136/
I would recommend taking one of those and tweaking it to include you beta code logic.
HOW to re-use (tweak) internal Django login_required
For example, you need to allow access to page for only users who passed login_required checks and also are Coaches - and (save) pass coach instance to you view for further processing
decorators.py
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from profiles.models import CoachProfile
def coach_required(function):
def wrapper(request, *args, **kwargs):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated():
return decorated_view_func(request) # return redirect to signin
coach = CoachProfile.get_by_email(request.user.email)
if not coach: # if not coach redirect to home page
return HttpResponseRedirect(reverse('home', args=(), kwargs={}))
else:
return function(request, *args, coach=coach, **kwargs)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
views.py
#coach_required
def view_master_schedule(request, coach):
"""coach param is passed from decorator"""
context = {'schedule': coach.schedule()}
return render(request, 'template.html', context)
I would create a guest account and login people that enter the Beta Tester code to that account. Something along these lines:
def beta_code_accepted(request):
guest_user = User.objects.get(username='beta_guest')
login(request, guest_user)
Once your beta is complete, just disable the splash view.

Categories

Resources