Here i have got a Django view that should normally redirect me to the Settings.LOGIN_URL if user has not the required permission but the issue is it's always displaying the 403 FORBIDDEN page.
the url defined in Settings.LOGIN_URL is 'accounts/login/'.
class LibrairianListView(PermissionRequiredMixin, View):
model = BookInstance
template_name = 'catalog/librairian_list_borrowed.html'
permission_required = 'catalog.can_mark_returned'
def get_queryset(self):
return BookInstance.objects.filter(status__exact='o').order_by('due_back')
def get_context_data(self, **kwargs):
print(self.raise_exception)
context = {}
context['bookinstance_list'] = self.get_queryset()
return context
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.get_context_data())
And i change this to a function based view and everything is working fine, but i wanted to use class base view since this should do the same work
This happened because the user is logged-in but he does not have 'catalog.can_mark_returned' permission. This is indented behavior and no need to redirect the user to login page since he is already logged-in.
If you still wish to modify the way the permission error handled, override the handle_no_permission()--(Django doc) method
Related
I have created a decorator 'unauthenticated_user(view_func)' in my 'decorators.py' file which restricts a user from accessing a view if the current user is not authenticated.
In the 'unauthenticated_user()' function, there is a conditional 'request.user.is_authenticated' which should return true if a user attached to the token is authenticated. However, upon testing this function, I have noticed that 'request.user' in 'decorators.py' is not always equal to the correct value returned by 'self.request.user' in 'views.py'. Instead, it often returns previously logged in users, or 'AnonymousUser' which ruins functionality.
decorators.py:
def unauthenticated_user(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
return view_func(request, *args, **kwargs)
else:
return HttpResponse('No user logged in')
return wrapper_func
views.py:
#method_decorator(unauthenticated_user, name = 'dispatch')
class UserLogout(APIView):
...
This is the views.py function I have been using to test that there is in fact an authenticated user when the decorators.py function returns 'AnonymousUser':
class Current(APIView):
def get(self, request, format = None):
try:
return Response(self.request.user.is_authenticated)
except:
return Response("no user currently logged in")
How to I ensure that 'decorators.py' 'unauthenticated_user' function has access to the user in the current request like the 'Current' view does in 'views.py'?
Any help would be much appreciated. To my knowledge there is no way to call 'self' in the 'decorators.py' function.
Thanks, Grae
Since you are using the DRF, you should make use of the IsAuthenticated permission class of the view
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class MyAPIView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response({"msg": "Some message"})
I'm making an user authentication app using Django authentication framework. I'm using provided views like LoginView and other views (from django.contrib.auth). Problem is when users (authenticated or anonymous) access urls:
path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
they see the these pages. for example the page that confirms that a link to reset their password has been sent to their email. while the user is already signed in and they didn't ask for password reset and there is no email send to their email because they never asked to reset their password. they just manually accessed email has been sent confirmation page 127.0.0.1:8000/account/password_reset/done/.
How to prevent them from accessing these urls or show them appropriate message?
For such cases, you need to generate a unique request no and send it through query params e.g. 127.0.0.1:8000/account/password_reset/done?req_no=XXXXXXXX or just add a lookup field in URL e.g.
path('password_reset/done/<int:req_no> or <str:req_no>', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
now you can get request no. in view, check if such request no exist if so send the templet for success message else redirect to home page or send 404 or anything you like,
to store the request no. you should create a new model and after sending the success templet, delete that req no from the database.
You can use the UserPassesTestMixin which can be imported from
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
then you can inherit the method you want to override in views,
for ex.
from django.contrib.auth.views.PasswordResetView
after inheriting the view you can perform the thing you want to do in test function
class ClassName(UserPassesTestMixin, PasswordResetView):
def test_func(self):
# do your thing
Thanks to answers i came up with this solution. (you can use value of was_sent to decide whatever). But as i'm new and have low experience there are major doubts about the practicality of this solution. comments are appreciated. (most of the code is copied from the source. added lines are marked with #)
class CustomPasswordResetDoneView(PasswordResetDoneView):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
self.request.session['was_sent'] = False #
return self.render_to_response(context)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session.get('was_sent', False) #
return context
class CustomPasswordResetView(PasswordResetView):
def get(self, request, *args, **kwargs):
self.request.session.get('was_sent', False) #
self.request.session['was_sent'] = False #
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
self.request.session['was_sent'] = True #
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session['was_sent'] #
return context
Hi so I have a working view. I wanted to know if this is the best or a good way of doing it. I want to have a delete view that deletes a Photo object but only if the logged in user is the on associated with the object.
Here is my views.py
class PhotoDelete(DeleteView):
model = Photo
template_name = 'otologue/photo_delete.html'
success_url = reverse_lazy('otologue:photos')
def get(self, request, *args, **kwargs):
object_instance = self.get_object() # Get the object
object_user = object_instance.photoextended.user # Get the user who owns the object
user = get_object_or_404(User, username=self.request.user) # Get the user in the view
if object_user != user: # See if the object_user is the same as the user
return HttpResponseForbidden('Permission Error')
else:
return render(request, self.template_name, {'object': object_instance})
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
If you need more info like the models and such, please request it.
You don't have to call get_object_or_404(), since self.request.user already contains an instance of User, so you can compare it directly.
You should call (and return the result) DeleteView's get() method instead of rendering the template yourself.
I would also use LoginRequiredMixin instead of a decorator, since your dispatch() now does nothing apart from calling super's method.
Be aware, that the actual deletion is done in the post() method. You should move your user check to the dispatch() method, otherwise it will be possible to bypass the check with a forged POST request.
To have better idea what DeleteView does in its methods, check it's source code (for example at ccbv.co.uk)
Lets just suppose that we have following url:
example.com/users/1
if user with ID=1 opens it, user receive info about his account, but if user switch 1 with 2, then he can view other user details as well, and we of course do not want that, so I have following solutions:
1) just pass currently logged in user id threw request.user.id inside a template
2) add permission, but I did not find type of permissions that would allow me to do that. Of course I could create dozens of permissions each for each user, but of course that is very nasty way.
Any other ideas how to cope with that in Django?
You can either fill context with the request which makes sure the user will never see another user's data, e.g. (using CBVs):
class AccountView(TemplateView):
"""
Generic account view
"""
template_name = "users/account.html"
def get_context_data(self, **kwargs):
context = super(AccountView, self).get_context_data(**kwargs)
context['user'] = User.objects.get(id=self.request.user.id)
return context
#method_decorator(login_required(login_url=reverse('login')))
def dispatch(self, *args, **kwargs):
return super(AccountView, self).dispatch(*args, **kwargs)
Another approach, to make sure 'fake' urls render 404's is to write an owner_required decorator, e.g.:
def owner_required(function):
#wraps(function)
def decorator(*args, **kwargs):
request = args[1]
user = get_object_or_404(User, username=request.user.username)
if user.is_authenticated() and user.username == kwargs.get('slug'):
return function(*args, **kwargs)
raise Http404
return decorator
You don't need permission to do this.
In your views.py
from django.http import Http404
def myview(request, user_id):
user = request.user
if user_id != request.user.id:
raise Http404
#all your logic here
But if you want user profile to be private, you don't need to use user_id in your url pattern. just use the user object stored in the request variable.
How can make Django permission_required decorator not to redirect already logged-in users to login page, but display some message like Insufficient permissions?
Thank you.
A quick and dirty solution would be to write your own decorator to do this. Something like this:
decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
#decorator_with_arguments
def custom_permission_required(function, perm):
def _function(request, *args, **kwargs):
if request.user.has_perm(perm):
return function(request, *args, **kwargs)
else:
request.user.message_set.create(message = "What are you doing here?!")
# Return a response or redirect to referrer or some page of your choice
return _function
You can then decorate your view thus:
#custom_permission_required('my_perm')
def my_view(request, *args, **kwargs):
#Do stuff
Since django 1.4 permission_required has a raise_exception parameter that you can set to True to have an unauthorized PermissionDenied exception raised
Eg. to give an exemple on a Class Based View:
from django.contrib.auth.decorators import permission_required
...
class MyView(TemplateView):
#method_decorator(permission_required('can_do_something', raise_exception=True))
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
Ref:permission_required decorator doc
I'm assuming this question requires two pieces
Users that are already logged in do not get redirected to the login page
Users that are not logged in do not get redirected
#Manoj Govindan's answer nor #Stefano's answer will not work. #Lidor's answer will work, but he has fully re-implemented the permission_required function.
Here is a simpler way:
#login_required
#permission_required('hi there', raise_exception=True)
def blah(request):
pass
With this, if the user is not logged in, they will be redirected. If they are but they don't have permissions, they will be down an error.
I had the same problem but learned about raise_exception parameter at the #permission_required decorator!
this parameter is False by default, but once you pass it the True value, it will automatically redirect the without permission user to the 403.html page! (if there was any 403.html page in the root of your project, otherwise shows the server 403 forbidden page!
read more in Django docs here
#permission_required('app_name.view_model', raise_exception=True)
def some_model_view(request):
...
You can write your own decorator to replace django's permission_required decorator:
from django.utils import six
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import user_passes_test
def permission_required(perm, login_url=None, raise_exception=True):
def check_perms(user):
if isinstance(perm, six.string_types):
perms = (perm, )
else:
perms = perm
if user.has_perms(perms):
return True
if raise_exception and user.pk:
raise PermissionDenied
return False
return user_passes_test(check_perms, login_url=login_url)
And use it the same way:
#permission_required('app.permission')
def view_page(request):
# your view function
Logged in users with no permission will get a 403 forbidden error. Those who are not logged in will be redirected to the login page.