2

Views.py

class templateList(PermissionRequiredMixin, TemplateView):
    permission_required = 'accounts.template_all'

    def get(self, request, *args, **kwargs):
        #view logic
        print(self.request.user.has_perms('accounts.template_all'))
        return render(request, template_name, context)

accounts/models.py

class User(AbstractBaseUser, PermissionsMixin):
    # some fields here
    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

        permissions = (
            ("template_all", "access to all templates"),
        )

ViewName.___mro____

(<class 'template.views.templateList'>, <class 'django.contrib.auth.mixins.PermissionRequiredMixin'>, <class 'django.contrib.auth.mixins.AccessMixin'>, <class 'django.views.generic.base.TemplateView'>, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.base.ContextMixin'>, <class 'django.views.generic.base.View'>, <class 'object'>)

self.request.user.has_perms('accounts.template_all') in views.py returns the correct boolean value, however, self.has_permission() returns True every time. permission_required has no effect, and the user is still able to see the page even when the print returns false. self.get_permission_required alos returns the correct value. Help appreciated.

Rudra Mutalik
  • 372
  • 4
  • 17
  • I think you should order the baseclasses differently: `class viewName(PermissionRequiredMixin, AccessMixin, generic.TemplateView)`. Right now the `TemplateView` overrides your `PermissionRequiredMixin`. – Willem Van Onsem Mar 02 '19 at 14:33
  • Your `print(..)` test does not pass a tuple to the `has_perms`, what happens if you call it like: `print(self.request.user.has_perms('accounts.action_all',))`? – Willem Van Onsem Mar 02 '19 at 14:55

1 Answers1

7

In short: the PermissionRequiredMixin baseclass should be placed before the TemplateView baseclass, such that the MRO (Method Resolution Order) is correct, and dispatch points to the override of the PermissionRequiredMixin.

A PermissionRequiredMixin patches the dispatch(..) method (well it adds an extra check that looks if the user has the proper permissions). Here however you have placed the subclasses in an order that results in the fact that the dispatch(..) function is the one from the View class.

Indeed, if we take a look at the MRO, we see:

>>> ViewName.__mro__
(<class 'ViewName'>, <class 'django.views.generic.base.TemplateView'>, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.base.ContextMixin'>, <class 'django.views.generic.base.View'>, <class 'django.contrib.auth.mixins.PermissionRequiredMixin'>, <class 'django.contrib.auth.mixins.AccessMixin'>, <class 'object'>)

and if we take a look at the method that is called when we call .dispatch(..), we see:

>>> ViewName.dispatch
<function View.dispatch at 0x7f169e8f6620>

In order to let the mixin override the original .dispatch(..) function, we need to put it first in the base classes, like:

# PermissionRequiredMixin is put before TemplateView

class ViewName(PermissionRequiredMixin, TemplateView):
    permission_required = 'accounts.action_all'
    # ...

We then see that:

>>> ViewName.__mro__
(<class 'ViewName'>, <class 'django.contrib.auth.mixins.PermissionRequiredMixin'>, <class 'django.contrib.auth.mixins.AccessMixin'>, <class 'django.views.generic.base.TemplateView'>, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.base.ContextMixin'>, <class 'django.views.generic.base.View'>, <class 'object'>)
>>> ViewName.dispatch
<function PermissionRequiredMixin.dispatch at 0x7f168b41d620>
djvg
  • 11,722
  • 5
  • 72
  • 103
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Thanks for the explanation, I didn't know that, however, this change has unfortunately not solved the issue. – Rudra Mutalik Mar 02 '19 at 14:47
  • Also, should the accessMixin be before templateView? – Rudra Mutalik Mar 02 '19 at 14:47
  • @RudraMutalik: no, since the accesview is already in the `PermissionRequiredView`. Django uses `self.request.user.has_perms(perms)`, with `perms` the `('accounts.action_all',)` here (so a tuple), are you sure that is working correctly? – Willem Van Onsem Mar 02 '19 at 14:50
  • @RudraMutalik: I've tested it locally, and it seems to work. Since you specified a `User` model yourself, perhaps there is something that is not working entirely correct? – Willem Van Onsem Mar 02 '19 at 14:53
  • `self.request.user.has_perms('accounts.action_all')` returns the correct value, so that seems to be working – Rudra Mutalik Mar 02 '19 at 14:55
  • @RudraMutalik: yes, but `has_perms` is given an iterable: https://docs.djangoproject.com/en/2.1/ref/contrib/auth/#django.contrib.auth.models.User.has_perms not a single list. – Willem Van Onsem Mar 02 '19 at 14:56
  • same result when it is a tuple – Rudra Mutalik Mar 02 '19 at 14:58
  • @RudraMutalik: what is the output of your `ViewName.__mro__`? – Willem Van Onsem Mar 02 '19 at 15:00
  • added to the question – Rudra Mutalik Mar 02 '19 at 15:07
  • @RudraMutalik: the mixin also offers a function `self.has_permission()`, perhaps it can be useful to print the response in your view and check if it generates the correct response. – Willem Van Onsem Mar 02 '19 at 15:08
  • It seems not, It is returning True when has_perms is false – Rudra Mutalik Mar 02 '19 at 15:13
  • @RudraMutalik: well `has_permission` is implemented like http://ccbv.co.uk/projects/Django/2.1/django.contrib.auth.mixins/PermissionRequiredMixin/ so that means that `self.get_permission_required()` is not working correctly, or `self.request.user.has_perms(perms)` is not. So I propose in order to debug this, that you try to "reproduce" this locally (as in printing the result of `self.get_permission_required()` etc. – Willem Van Onsem Mar 02 '19 at 15:15