Django user_passes_test usage - python

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.

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)

Differentiating unauthenticated users in custom decorator

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):
...

How to test user ownership of object using Django decorators

I'm working on a Django project and trying to figure out how I can test for user ownership and allow editing or redirect based on the result.
I have a model Scene. Scene is linked to User to track which user created a particular Scene:
class Scene(models.Model):
user = models.ForeignKey(User)
[rest of Scene model]
I have a URL pattern to edit a particular Scene object like this:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', SceneUpdateView.as_view(), name='scene-edit'),
I have a logged in user via django-allauth. I want only Scene owners to be able to edit Scenes.
I'm trying to figure out how to use a decorator to test if scene.user.id == self.request.user.id for the particular scene called by the URL.
Do I need to send URL information into permission_required or user_passes_test decorators (is this possible)?
How can I make this happen?
You can use a custom decorator for your specefic need.
Note: I'm using function based view, you will have to modify the code to class based view if you want:
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
from django.contrib.auth.models import User
from yourmodels.models import Scene
#Custom decorator
def must_be_yours(func):
def check_and_call(request, *args, **kwargs):
#user = request.user
#print user.id
pk = kwargs["pk"]
scene = Scene.objects.get(pk=pk)
if not (scene.user.id == request.user.id):
return HttpResponse("It is not yours ! You are not permitted !",
content_type="application/json", status=403)
return func(request, *args, **kwargs)
return check_and_call
#View Function
#must_be_yours
#csrf_protect
def update_scene(request, pk=None):
print pk
if request.method == 'PUT':
#modify merely
pass
Urls:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', 'update_scene'),
In Function Based Views it's common to see decorators. Yet, in Class Based Views (CBV) it's more common to use Mixins or QuerySet.
Adapted from this answer, one can create the following custom Mixin that overrides the dispatch method
class UserOwnerMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.object.user != self.request.user:
return HttpResponseForbidden()
return super(UserOwnerMixin, self).dispatch(request, *args, **kwargs)
This is a generalized way across multiple model class, as long as one is using user = models.ForeignKey(User).
Then use it in the CBV in a similar fashion to
class MyCustomView(UserOwnerMixin, View):

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