I have internal account privacy permissions in my project(e.g. only friends can see profile page of user) and I want to have custom permission denied page for this case. Is there any way to return response from TemplateView with status code equals 403?
Something like this:
class PrivacyDeniedView(TempateView):
template_name = '...'
status_code = 403
I can do this by override dispatch() but maybe Django has out of the box solution
Answer: it looks like there no generic solution. The best way is proposed by #alecxe, but encapsulated in Mixin as #FoxMaSk proposed
One option is to override get() method of your TemplateView class:
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=403)
You can subclass TemplateResponse and set response_class in the view. For example:
from django.template.response import TemplateResponse
class TemplateResponseForbidden(TemplateResponse):
status_code = 403
class PrivacyDeniedView(TemplateView):
response_class = TemplateResponseForbidden
...
This approach is more DRY than the other suggestions because you don't need to copy and paste any code from TemplateView (e.g. the call to render_to_string()).
I tested this in Django 1.6.
While alecxe's answer works, I strongly suggest you to avoid overriding get; it's easy to forget that CBV's can have other methods like post, and if you're overriding one you should do the same for the others.
In fact, there is no need to create a separate view just to display a 403 error; Django already has django.http.HttpResponseForbidden. So instead of redirecting to your view, just do something along the lines of:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden()
Or, if you want to render a particular template:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden(loader.render_to_string("403.html"))
I just encountered this problem as well. My goal was to be able to specify the status code in urls.py, e.g.:
url(r'^login/error/?$', TemplateView.as_view(template_name='auth/login_error.html', status=503), name='login_error'),
So using the previous answers in this thread as idea starters, I came up with the following solution:
class TemplateView(django.views.generic.TemplateView):
status = 200
def render_to_response(self, context, **response_kwargs):
response_kwargs['status'] = self.status
return super(TemplateView, self).render_to_response(context, **response_kwargs)
This is an old question, but I came across it first when searching. I was using a different generic view (ListView) whose implementation for get is a bit more complex, and thus a bit of a mess to override. I went for this solution instead:
def get(self, request, *args, **kwargs):
response = super(ListView, self).get(request, *args, **kwargs)
response.status_code = 403
return response
This also allowed me to decide the status code based on some parameters of the request, which was necessary in my case to perform a 301 redirect for old-style URL patterns.
If you don't require the ability to change status code from within the method, then cjerdonek's answer is ideal.
Related
I am writing a class-based view to allow users to edit their profile. Since I want the users to access this view with a URL of the type my_profile/edit/ rather than something like profile/<int:pk>/edit/, using a view based on UpdateView is quite cumbersome since getting the user profile object requires to access the request object, and get_object does not directly have access to it.
My two questions:
Shall I use UpdateView in this case?
If so, what would be the best way to override get_object(self, queryset=None)? My best attempt so far is the following:
class EditProfileView(UpdateView):
model = UserProfile
_request = None
def get_object(self, queryset=None):
return get_user_profile(self._request)
def dispatch(self, request, *args, **kwargs): # can also override setup() in newer Django versions
self._request = request
return super().dispatch(request, *args, **kwargs)
This looks clean enough to me except that if one day the Django framework decides that get_object should be called early in the workflow of the view generation, this piece of code could break.
You don't need to do this. All class-based views make the request available as self.request.
Your entire code should just be:
class EditProfileView(UpdateView):
model = UserProfile
def get_object(self, queryset=None):
return get_user_profile(self.request)
Note, even if it didn't, you still wouldn't need to define _request at class level. That's just not necessary in Python.
Also, I don't know what your get_user_profile function does but it can probably be replaced with just self.request.user.profile.
I am implementing an API where I have nested structures.
Lets say it is a zoo and I can call GET /api/cage/ to get a list of cages GET /api/cage/1/ to get cage ID 1, but then I can GET /api/cage/1/animals/ to get a list of animals in that cage.
The problem I am having is with permissions. I should only be able to see animals in the cage if I can see the cage itself. I should be able to see the cage itself if has_object_permission() returns True in the relevant permission class.
For some reason, has_object_permission() gets called when I do GET /api/cage/1/, but has_permission() gets called when I call GET /api/cage/1/animals/. And with has_permission() I don't have access to the object to check the permissions. Am I missing something? How do I do this?
My cage viewset looks more or less like this
class CageViewSet(ModelViewSet):
queryset = Cage.objects.all()
serializer_class = CageSerializer
permission_classes = [GeneralZooPermissions, ]
authentication_classes = [ZooTicketCheck, ]
def get_queryset(self):
... code to only list cages you have permission to see ...
#detail_route(methods=['GET'])
def animals(self, request, pk=None):
return Request(AnimalSerializer(Animal.objects.filter(cage_id=pk), many=True).data)
My GeneralZooPermissions class looks like this (at the moment)
class GeneralZooPermissions(BasePermission):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return request.user.has_perm('view_cage', obj)
It seems like this is a bug in DRF. Detailed routes do not call the correct permission check. I have tried reporting this issue to DRF devs, but my report seems to have disappeared. Not sure what to do next. Ideas?
The issue I posted with DRF is back and I got a response. Seems like checking only has_permission() and not has_object_permission() is the intended behavior. This doesn't help me. At this point, something like this would have to be done:
class CustomPermission(BasePermission):
def has_permission(self, request, view):
"""we need to do all permission checking here, since has_object_permission() is not guaranteed to be called"""
if 'pk' in view.kwargs and view.kwargs['pk']:
obj = view.get_queryset()[0]
# check object permissions here
else:
# check model permissions here
def has_object_permission(self, request, view, obj):
""" nothing to do here, we already checked everything """
return True
OK, so after reading a bunch of DRF's code and posting an issue at the DRF GitHub page.
It seems that has_object_permission() only gets called if your view calls get_object() to retrieve the object to be operated on.
It makes some sense since you would need to retrieve the object to check permissions anyway and if they did it transparently it would add an extra database query.
The person who responded to my report said they need to update the docs to reflect this. So, the idea is that if you want to write a custom detail route and have it check permissions properly you need to do
class MyViewSet(ModelViewSet):
queryset = MyModel.objects.all()
....
permission_classes = (MyCustomPermissions, )
#detail_route(methods=['GET', ])
def custom(self, request, pk=None):
my_obj = self.get_object() # do this and your permissions shall be checked
return Response('whatever')
If you want to define permissions while doing another method that doesn't call the get_object() (e.g. a POST method), you can do overriding the has_permission method. Maybe this answer can help (https://stackoverflow.com/a/52783914/12737833)
Another thing you can do is use the check_object_permissions inside your POST method, that way you can call your has_object_permission method:
#action(detail=True, methods=["POST"])
def cool_post(self, request, pk=None, *args, **kwargs):
your_obj = self.get_object()
self.check_object_permissions(request, your_obj)
In my case I didn't address requests correctly so my URL was api/account/users and my mistake was that I set URL in frontend to api/account/ and thats not correct!
I have some users that are allowed to see a certain view.
To allow users to login and complain with a 403 Forbidden for those users that cannot see that login, I can use the following (as explained here):
#permission_required('polls.can_vote', raise_exception=True)
#login_required
def my_view(request):
...
This indeed works as expected. But all my views are class-based views. Since Django 1.9 (finally!) there are a bunch of pretty mixins for doing things that were only possible through the decorators. However...
class MyClassView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
raise_exception = <???>
permission_required = 'polls.can_vote'
template_name = 'poll_vote.html'
this doesn't work. Because the raise_exception flag is used by both LoginRequiredMixin and PermissionRequiredMixin, I cannot set it to anything.
if raise_exception is True, a user that is not logged in receives a 403 Forbidden (which I do not want).
if raise_exception is False, a user that is not allowed to see the view, will be redirected to the login page which, because the user is logged in, will redirect again to the page. Creating a not-at-all fancy redirect loop.
Of course I could implement my own mixin that behaves I expected, but is there any Django-way of doing this in the view itself? (not in the urls.py)
For many cases raising 403 for unauthenticated users is the expected behaviour. So yes, you need a custom mixin:
class LoggedInPermissionsMixin(PermissionRequiredMixin):
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated():
return redirect_to_login(self.request.get_full_path(),
self.get_login_url(), self.get_redirect_field_name())
if not self.has_permission():
# We could also use "return self.handle_no_permission()" here
raise PermissionDenied(self.get_permission_denied_message())
return super(LoggedInPermissionsMixin, self).dispatch(request, *args, **kwargs)
The desired behavior is the default since 2.1, so the other answers are obsolete:
Changed in 2.1: In older versions, authenticated users who lacked permissions were redirected to the login page (which resulted in a loop) instead of receiving an HTTP 403 Forbidden response. [src]
I wanted to add a comment, but my reputation does not allow. How about the following? I feel the below is more readable?
Updated after comments
My reasoning is: You basically write modified dispatch from LoginRequiredMixin and just set raise_exception = True. PermissionRequiredMixin will raise PermissionDenied when correct permissions are not met
class LoggedInPermissionsMixin(PermissionRequiredMixin):
raise_exception = True
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated():
return redirect_to_login(self.request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
return super(LoggedInPermissionsMixin, self).dispatch(request, *args, **kwargs)
Simplest solution seems to be a custom view mixin.
Something like that:
class PermissionsMixin(PermissionRequiredMixin):
def handle_no_permission(self):
self.raise_exception = self.request.user.is_authenticated()
return super(PermissionsMixin, self).handle_no_permission()
Or, just use PermissionRequiredMixin as usual and put this handle_no_premission to every CBV.
First of all I want both views use exact same URL because I don't want to make my URLConf more complicated. I want separate views for GET and POST to make my code cleaner. The code is something like this:
def view2 (request):
# handle POST request, possibly a ajax one
return HTTPRESPONSE(json_data, mimetype="Application/JSON")
def view1 (request):
if method == POST:
view2(request)
# What should I return here???
else:
# handle GET
return render(request, template, context)
My question is about the # What should I return here??? line. If I don't put a return there, error occurs:
not returning http response
But I already return an HTTP response in view2. How can I make this work?
Another, probably a bit cleaner way would be using class-based views
from django.views.generic import TemplateView
class View1(TemplateView):
def get(self, request, *args, **kwargs):
"""handle get request here"""
def post(self, request, *args, **kwargs):
"""handle post request here"""
def head(self, request, *args, **kwargs):
"""handle head request here. Yes, you can handle any kind of requests, not just get and post"""
Of course you can add common methods, __init__ (which is useless unless you are sure what you are doing), apply login_required (see this SO question) and pretty much everything you can do with django views (e.g. apply middleware, permissions, etc.) and python classes (e.g. inheritance, metaclasses/decorators, etc.)
Also, there's a whole bunch of generic class based view coming with Django to address common situations like list page, details page, edit page, etc.
You need to return the results of view2:
def view1 (request):
if request.method == 'POST':
return view2(request)
else:
# handle GET
return render(request, template, context)
For my site I created an abstract Model which implements model-level read permissions. That part of the system is completed and works correctly. One of the methods the permissioned model exposes is is_safe(user) which can manually test if a user is allowed to view that model or not.
What I would like to do is add a method to the effect of continue_if_safe which can be called on any model instance, and instead of returning a boolean value like is_safe it would first test if the model can be viewed or not, then in the case of False, it would redirect the user, either to the login page if they aren't already logged in or return a 403 error if they are logged in.
Ideal usage:
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
# ... remainder of code to be run if it's safe down here ...
I peeked at how the get_object_or_404 works, and it throws an Http404 error which seems to make sense. However, the problem is that there don't seem to be equivalent redirect or 403 errors. What's the best way to go about this?
(non-working) continue_if_safe method:
def continue_if_safe(self, user):
if not self.is_safe(user):
if user.is_authenticated():
raise HttpResponseForbidden()
else:
raise HttpResponseRedirect('/account/')
return
Edit -- The Solution
The code for the final solution, in case other "stackers" need some help with this:
In the Abstract Model:
def continue_if_safe(self, user):
if not self.is_safe(user):
raise PermissionDenied()
return
Views are caught by the middleware:
class PermissionDeniedToLoginMiddleware(object):
def process_exception(self, request, exception):
if type(exception) == PermissionDenied:
if not request.user.is_authenticated():
return HttpResponseRedirect('/account/?next=' + request.path)
return None
Usage in the view (very short and sweet):
model = get_object_or_404(Model, slug=slug)
model.continue_if_safe(request.user)
For the forbidden (403) error, you could raise a PermissionDenied exception (from django.core.exceptions).
For the redirecting behaviour, there's no built-in way to deal with it the way you describe in your question. You could write a custom middleware that will catch your exception and redirect in process_exception.
I've made a little middleware that return whatever your Exception class's render method returns. Now you can throw custom exception's (with render methods) in any of your views.
class ProductNotFound(Exception):
def render(self, request):
return HttpResponse("You could ofcourse use render_to_response or any response object")
pip install -e git+http://github.com/jonasgeiregat/django-excepted.git#egg=django_excepted
And add django_excepted.middleware.ExceptionHandlingMiddleware to your MIDDLEWARE_CLASSES.
Django annoying has a solution for this:
from annoying.exceptions import Redirect
...
raise Redirect('/') # or a url name, etc
You want to use decorators for this. Look up login_required for an example. Basically the decorator will allow you check check the safety and then return HttpResponseRedirect() if its not safe.
Your code will end up looking something like this:
#safety_check:
def some_view(request):
#Do Stuff