Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can PermissionRequiredMixin and LoginRequiredMixin be combined?

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)

like image 846
MariusSiuram Avatar asked Jan 08 '16 12:01

MariusSiuram


4 Answers

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]

like image 143
tobib Avatar answered Oct 24 '22 21:10

tobib


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.

like image 32
Nikolay Avatar answered Oct 24 '22 21:10

Nikolay


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)
like image 35
Sandeep Patil Avatar answered Oct 24 '22 20:10

Sandeep Patil


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)
like image 28
Alex Morozov Avatar answered Oct 24 '22 20:10

Alex Morozov