Per Field Permission in Django REST Framework

前端 未结 7 2258
既然无缘
既然无缘 2020-12-24 01:25

I am using Django REST Framework to serialize a Django model. I have a ListCreateAPIView view to list the objects and a RetrieveUpdateDestroyAPIView view to retrieve/update

相关标签:
7条回答
  • 2020-12-24 02:00

    In case you are performing only READ operations, you can just pop the fields in to_representation method of the serializer.

    def to_representation(self,instance):
        ret = super(YourSerializer,self).to_representation(instance)
        fields_to_pop = ['field1','field2','field3']
        if instance.created_by != self.context['request'].user.id:
            [ret.pop(field,'') for field in fields_to_pop]
        return ret
    

    This should be enough to hide sensitive fields.

    0 讨论(0)
  • 2020-12-24 02:04

    I had a similar problem the other day. Here is my approach:

    This is a DRF 2.4 solution.

    class PrivateField(serializers.Field):
        def field_to_native(self, obj, field_name):
            """
            Return null value if request has no access to that field
            """
            if obj.created_by == self.context.get('request').user:
                return super(PrivateField, self).field_to_native(obj, field_name)
            return None
    
    #Usage
    class UserInfoSerializer(serializers.ModelSerializer):
        private_field1 = PrivateField()
        private_field2 = PrivateField()
    
        class Meta:
            model = UserInfo
    

    And a DRF 3.x solution:

    class PrivateField(serializers.ReadOnlyField):
    
        def get_attribute(self, instance):
            """
            Given the *outgoing* object instance, return the primitive value
            that should be used for this field.
            """
            if instance.created_by == self.context['request'].user:
                return super(PrivateField, self).get_attribute(instance)
            return None
    

    This time we extend ReadOnlyField only because to_representation is not implemented in the serializers.Field class.

    0 讨论(0)
  • 2020-12-24 02:09

    For a solution that allows both reading and writing, do this:

    class PrivateField(serializers.Field):
        def get_attribute(self, obj):
            # We pass the object instance onto `to_representation`,
            # not just the field attribute.
            return obj
    
        def to_representation(self, obj):
            # for read functionality
            if obj.created_by != self.context['request'].user:
                return ""
            else:
                return obj.private_field1
    
        def to_internal_value(self, data):
            # for write functionality
            # check if data is valid and if not raise ValidationError
    
    
    class UserInfoSerializer(serializers.HyperlinkedModelSerializer):
        private_field1 = PrivateField()
        ...
    

    See the docs for an example.

    0 讨论(0)
  • 2020-12-24 02:18

    How about switching serializer class based on user?

    In documentation:

    http://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself

    def get_serializer_class(self):
        if self.request.user.is_staff:
            return FullAccountSerializer
        return BasicAccountSerializer
    
    0 讨论(0)
  • 2020-12-24 02:18

    I figured out a way to do it. In the serializer, I have access to both the object and the user making the API request. I can therefore check if the requestor is the owner of the object and return the private information. If they are not, the serializer will return an empty string.

    class UserInfoSerializer(serializers.HyperlinkedModelSerializer):
        private_field1 = serializers.SerializerMethodField('get_private_field1')
    
        class Meta:
            model = UserInfo
            fields = (
                'id',
                'public_field1',
                'public_field2',
                'private_field1',
            )
            read_only_fields = ('id')
    
        def get_private_field1(self, obj):
            # obj.created_by is the foreign key to the user model
            if obj.created_by != self.context['request'].user:
                return ""
            else:
                return obj.private_field1
    
    0 讨论(0)
  • 2020-12-24 02:20

    Here:

    -- models.py:

    class Article(models.Model):
        name = models.CharField(max_length=50, blank=False)
        author = models.CharField(max_length=50, blank=True)
    
        def __str__(self):
            return u"%s" % self.name
    
        class Meta:
            permissions = (
                # name
                ('read_name_article', "Read article's name"),
                ('change_name_article', "Change article's name"),
    
                # author
                ('read_author_article', "Read article's author"),
                ('change_author_article', "Change article's author"),
            )
    

    -- serializers.py:

    class ArticleSerializer(serializers.ModelSerializer):
    
        class Meta(object):
            model = Article
            fields = "__all__"
    
        def to_representation(self, request_data):
            # get the original representation
            ret = super(ArticleSerializer, self).to_representation(request_data)
            current_user = self.context['request'].user
            for field_name, field_value in sorted(ret.items()):
                if not current_user.has_perm(
                    'app_name.read_{}_article'.format(field_name)
                ):
                    ret.pop(field_name)  #  remove field if it's not permitted
    
            return ret
    
        def to_internal_value(self, request_data):
            errors = {}
            # get the original representation
            ret = super(ArticleSerializer, self).to_internal_value(request_data)
            current_user = self.context['request'].user
            for field_name, field_value in sorted(ret.items()):
                if field_value and not current_user.has_perm(
                    'app_name.change_{}_article'.format(field_name)
                ):
                    errors[field_name] = ["Field not allowed to change"]  # throw error if it's not permitted
    
            if errors:
                raise ValidationError(errors)
    
            return ret
    
    0 讨论(0)
提交回复
热议问题