Подтвердить что ты не робот

Разрешение на поле в Django REST Framework

Я использую Django REST Framework для сериализации модели Django. У меня есть представление ListCreateAPIView, чтобы отображать объекты и представление RetrieveUpdateDestroyAPIView для извлечения/обновления/удаления отдельных объектов. Модель хранит информацию, которую пользователи представляют сами. Информация, которую они представляют, содержит некоторую конфиденциальную информацию и некоторую общедоступную информацию. Я хочу, чтобы все пользователи могли перечислить и получить общедоступную информацию, но я хочу, чтобы только владелец отображал/извлекал/обновлял/удалял личную информацию. Поэтому мне нужны разрешения для каждого поля, а не разрешения для объектов.

Самое близкое предложение, которое я нашел, было https://groups.google.com/forum/#!topic/django-rest-framework/FUd27n_k3U0, которое меняет сериализатор на основе типа запроса. Это не будет работать для моей ситуации, потому что у меня нет набора запросов или объекта в этот момент, чтобы определить, принадлежит ли он пользователю или нет.

Конечно, у меня есть интерфейс, который скрывает личную информацию, но умные люди все еще могут отследить запросы API, чтобы получить полные объекты. Если код необходим, я могу его предоставить, но мой запрос применяется к проектам Vanilla Django REST Framework.

4b9b3361

Ответ 2

У меня была аналогичная проблема на днях. Вот мой подход:

Это решение DRF 2.4.

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

И решение DRF 3.x:

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

На этот раз мы расширяем ReadOnlyField только потому, что to_representation не реализован в классе serializers.Field.

Ответ 3

Я понял способ сделать это. В сериализаторе у меня есть доступ как к объекту, так и к пользователю, выполняющему запрос API. Поэтому я могу проверить, является ли запросчиком владельцем объекта и возвращает личную информацию. Если это не так, сериализатор вернет пустую строку.

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

Ответ 4

Вот:

- 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 name"),
            ('change_name_article', "Change article name"),

            # author
            ('read_author_article', "Read article author"),
            ('change_author_article', "Change article 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 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 not permitted

        if errors:
            raise ValidationError(errors)

        return ret

Ответ 5

Для решения, которое позволяет читать и писать, сделайте следующее:

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()
    ...

В качестве примера см. docs.

Ответ 6

Если вы выполняете только операции READ, вы можете просто поместить поля в метод to_representation для сериализатора.

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

Этого должно быть достаточно, чтобы скрыть чувствительные поля.

Ответ 7

Просто поделитесь другим возможным решением

Например, чтобы электронную почту показывали только для себя.

На UserSerializer добавьте:

email = serializers.SerializerMethodField('get_user_email')

Затем реализуйте get_user_email так:

def get_user_email(self, obj):
    user = None
    request = self.context.get("request")
    if request and hasattr(request, "user"):
        user = request.user
    return obj.email if user.id == obj.pk else 'HIDDEN'