Nested decorators, raising 404 - python

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

Related

user_passes_test: raise exception if user does not pass

I am trying to define a decorator to check if the user has admin certain privileges:
def admin_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda u: u.is_staff and u.is_authenticated() and not Association.objects.filter(admins=u).exists(),
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
The aim is to use this throught the views. Particularly, I am using it in a class-based view:
class MyCBV(CreateView):
#method_decorator(admin_required)
def dispatch(self, request, *args, **kwargs):
return super(MyCBV, self).dispatch(request, *args, **kwargs)
The problem is that this view is loaded via AJAX, so the redirect doesn't happen. Also, the HTTP status the view returns is success even when the user authentication fails, so the client (JS) has no way of telling when the action really succeeded or not.
I usually have trouble understanding decorators and Django authentication, so my question is: how can I raise an exception (preferably the PermissionDenied exception) when the authentication decoration function fails?
In Django 1.9+, you can use the UserPassesTestMixin instead of a decorator, and set raise_exception to True.
Since you are using Django 1.4, which is insecure and obsolete, you won't be able to do this. There isn't an option to make user_passes_test raise PermissionDenied rather than redirect. You could either try to detect the redirect in your JavaScript, or look at the user_passes_test source code and implement something similar that returns the response you want.

Django login page with parameters

In Django application I am working on right now, user session is marked as multiple after same user login on another device, so after page reloading first user logs out and is redirected to admin/login page.
Need to show first user warning about this on admin/login page. The only thing, that came up on my mind for now is to add GET parameter to this page url, check it in view, and show this warning. But I can't find the class/method to override, where I can check if session is expired or not and add GET parameter, say multiple_sessions=1 url.
I assume, it would look something like this:
def logout(self, request, *args, **kwargs):
if request.session['multiple']:
super(CustomLogout, self).logout(request, redirect_to='/admin/login?multiple_session=1', *args, **kwargs)
else:
super(CustomLogout, self).logout(request, *args, **kwargs)
So the question is, what exactly method is responsible for redirecting user to logout?
Any other ideas of achieving this behaviour are greatly appreciated!
Not sure if I understood correctly. Your question is, how to show a message to the user after they logged out, is that correct?
Django has the messages framework to do just that, showing messages to a user.
from django.contrib import messages
def logout(self, request, *args, **kwargs):
if request.session['multiple']:
messages.info(request, 'Something multiple sessions.')
super(CustomLogout, self).logout(request, *args, **kwargs)
Used this custom middleware class to achieve needed behavior:
class LogoutMiddleware(object):
def process_response(self, request, response):
if hasattr(request, 'user'):
if request.user.is_authenticated() and request.session.exists(request.session.session_key):
current_session = Session.objects.get(pk=request.session.session_key)
if current_session.old == True:
response_redirect = HttpResponseRedirect(reverse('logout'))
response_redirect.set_cookie('multiple-sessions', True)
return response_redirect
return response
Then, if the login view finds this cookie, it renders warning in template.

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

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.

access django session from a decorator

I have a decorator that I use for my views #valid_session
from django.http import Http404
def valid_session(the_func):
"""
function to check if the user has a valid session
"""
def _decorated(*args, **kwargs):
if ## check if username is in the request.session:
raise Http404('not logged in.')
else:
return the_func(*args, **kwargs)
return _decorated
I would like to access my session in my decoartor. When user is logged in, I put the username in my session.
Will something like the following solve your problem:
def valid_session(func):
def decorated(request, *args, **kwargs):
print request.session
return func(request, *args, **kwargs)
return decorated
The view function takes the request as the first parameter, so the decorator will receive it as its first parameter as well. You can pull the session out of it with just request.session.
You could pass the request (or just the session) in as a parameter to the decorator. I just don't know how to get at it to pass it in. I was trying to figure out something similar last night.

Categories

Resources