Разрешение на поле в 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.
Ответы
Ответ 1
Как насчет переключения класса сериализатора на основе пользователя?
В документации:
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
Ответ 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'