Gobi Dasu
Gobi Dasu

Reputation: 469

How to customize DjangoObjectPermissions?

Hi I am following this example of using the DjangoObjectPermissionsFilter.

I want to create class SampleModelPermissions(permissions.DjangoObjectPermissions) so that it satisfies the following description in my "self-documented" DRF API:

enter image description here

This is my code:

in models.py:

class Sample(models.Model):
    created = models.DateTimeField(default=datetime.datetime.utcnow, blank=True, null=True)
    last_modified = models.DateTimeField(default=datetime.datetime.utcnow, blank=True, null=True)
    owner = models.ForeignKey(User, blank=True, null=True, related_name='sample_owner')
    text = models.TextField(default='', blank=True, null=True)

    class Meta:
        permissions = (
            ('view_sample', "can view sample"),
        )

    def __unicode__(self):
        return self.text

    def __str__(self):
        return self.text

in views.py:

class SampleViewSet(viewsets.ModelViewSet):
    '''
    * Model Description: Sample is a sample model.
    * CRUD on Sample model
    * C - CREATE - POST /sample/ - allowed as long as owner is the user creating the object 
    * R - READ - GET /sample/ (list) - user can see objects it owns
    * R - READ - GET /sample/[id]/ (detail) - user can see detail page of objects it owns
    * U - UPDATE - PATCH /sample/[id]/ - allowed for owner
    * D - DELETE - DELETE /sample/[id]/ - allowed for owner
    * Note in the case of a nested model A where a field f points to an instance of another model B, you can set f's value to an instance b of B by PATCHing or POSTing with f_id = [the id of b]. Yes, whenever f points to a foreign model, f is read only and f_id is write only.
    '''
    queryset = Sample.objects.all()
    filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter, DjangoObjectPermissionsFilter,)  
    permission_classes = (SampleModelPermissions,)
    filter_fields = '__all__'
    serializer_class = SampleSerializer

in permisions.py

class SampleModelPermissions(permissions.DjangoObjectPermissions):

    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

    logger.info('in SampleModelPermissions')

    def has_object_permission(self, request, view, obj):
        logger.info('in SampleModelPermissions has_object_permission')
        print 'permissions.SAFE_METHODS: ', permissions.SAFE_METHODS
        if request.method in permissions.SAFE_METHODS:
            return request.user == obj.owner or True # need to modify so can see own stuff
        elif request.method == 'POST':
            print 'checking if user has perm to create obj'
            return True # request.user == obj.owner
        elif request.method == 'PATCH': 
            return request.user == obj.owner
        elif request.method == 'DELETE':
            return request.user == obj.owner
        return False

But I get the following in POSTMAN:

enter image description here

enter image description here

Any tips on what I should be doing to get my API permissions to work as I describe they should in the self-documentation?

Upvotes: 1

Views: 3224

Answers (2)

Gobi Dasu
Gobi Dasu

Reputation: 469

The solution was to recognize that has_permission covers GET list and POST, and has_object_permissions covers GET detail, PATCH, and DELETE. I had to split my code into those 2 functions accordingly (bear in mind still working out other issues with the code though but the specific problem addressed in this question is covered):

class SampleModelPermissions(permissions.DjangoObjectPermissions):

    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

    logger.info('in SampleModelPermissions')

    def has_permission(self, request, view):
        logger.info('in SampleModelPermissions has_permission')
        if request.method in permissions.SAFE_METHODS:
            logger.info('SampleModelPermissions: has_permission: listing samples for user: ' + str(request.user.id))
            return True
        elif request.method == 'POST':
            suggested_owner = None
            try:
                logger.info('SampleModelPermissions: has_permission: request dict should have a suggested owner: ' + str(dict(request.data.iterlists())))
                suggested_owner = int(dict(request.data.iterlists())['owner_id'][0])
            except:
                logger.error('SampleModelPermissions: has_permission: request made without owner_id: ' + str(dict(request.data.iterlists())))
                return False
            return request.user.id == suggested_owner

    def has_object_permission(self, request, view, obj):
        logger.info('in SampleModelPermissions has_object_permission')
        print 'permissions.SAFE_METHODS: ', permissions.SAFE_METHODS
        if request.method in permissions.SAFE_METHODS:
            return request.user == obj.owner or True # need to modify so can see own stuff
        elif request.method == 'PATCH': 
            return request.user == obj.owner
        elif request.method == 'DELETE':
            return request.user == obj.owner
        return False

Upvotes: 0

Adam Dobrawy
Adam Dobrawy

Reputation: 1215

Is there a different way to manage permission and achieve results as you describe in documentation.

In the most way you have to assign permissions after create object. You can for example use signals for assign permissions.

Example DjangoObjectPermission:

class SampleModelPermissions(permissions.DjangoObjectPermissions):

    perms_map = {
        'GET': ['%(app_label)s.view_%(model_name)s'],
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
        'POST': ['%(app_label)s.add_%(model_name)s'],
        'PUT': ['%(app_label)s.change_%(model_name)s'],
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
    }

Example signals:

from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Sample

@receiver(post_save, sender=Sample, dispatch_uid="sample_assign_permission")
def permission_assign(sender, instance, created, **kwargs):
    if created:
        assign_perm('view_sample' self.request.user, instance)
        assign_perm('change_sample', self.request.user, instance)
        assign_perm('add_sample', self.request.user, instance)
        assign_perm('delete_sample', self.request.user, instance)

Upvotes: 1

Related Questions