Django Rest Framework use DjangoModelPermissions on ListAPIView

Viewed 6981

I am playing around with the djangorestframework and my goal is to use DjangoModelPermissions on my view which reacts to a GET request. The official documentation says:

The default behavior can also be overridden to support custom model permissions. For example, you might want to include a view model permission for GET requests.

Source

So I modified my model like the following:

class User(AbstractUser):
    display_name = models.CharField(_('Display Name'), blank=True, max_length=255)

    class Meta:
        permissions = (
            ("view_user", "Can view users"),
        )

    def __str__(self):
        return self.username

And the view:

class UserListAPIView(ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (permissions.DjangoModelPermissions,)

Settings:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissions'
    ]
}

The problem is that my implemented UserListAPIView successfully returns a list of all objects to a user who doesn't belong to a Group neither has any custom User Permission. It seems to me that the DjangoModelPermissions takes no effect.

3 Answers

Ouch, that was easier than I thought:

class CustomDjangoModelPermission(permissions.DjangoModelPermissions):

    def __init__(self):
        self.perms_map = copy.deepcopy(self.perms_map)  # from EunChong's answer
        self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s']

@Yannic Hamann's solution has a small bug. It overwrites parent's perms_map['GET'].

As follows, A dictionary overring need deepcopy.

class CustomDjangoModelPermission(permissions.DjangoModelPermissions):

    def __init__(self):
        self.perms_map = copy.deepcopy(self.perms_map) # you need deepcopy when you inherit a dictionary type 
        self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s']

dictionary overring test

class Parent:
    perms = {'GET':'I am a Parent !'}

class Child(Parent):
    def __init__(self):
        self.perms['GET'] = 'I am a Child !'

dictionary overring result

>>> print(Parent().perms['GET'])
I am a Parent !

>>> print(Child().perms['GET'])
I am a Child !

>>> print(Parent().perms['GET'])
I am a Child ! # Parent's perms is overwritten by Child.
       ^^^^^  

You have to override the custome DjangoModelPermissions.

class BaseModelPerm(permissions.DjangoModelPermissions):

     def get_custom_perms(self, method, view):
          app_name = view.model._meta.app_label
          return [app_name+"."+perms for perms in view.extra_perms_map.get(method, [])]

    def has_permission(self, request, view):
       perms = self.get_required_permissions(request.method, view.model)
       perms.extend(self.get_custom_perms(request.method, view))
       return (
          request.user and
          (request.user.is_authenticated() or not self.authenticated_users_only) and
        request.user.has_perms(perms)
    )

in the view you can use like below

class ViewName(generic.ListApiView):

      """ Trip listing view """

     model = model_name
     serializer_class = serializer_class
     permission_classes = (permissions.IsAuthenticated,BaseModelPerm)
     queryset = model.objects.all()
     extra_perms_map = {
      'GET': ["can_view_trip"],
     }

add the whatever extra permissions you want to add.

Related