framework - Permiso por campo en el marco REST de Django
django rest framework serializer (6)
¿Qué hay de cambiar la clase serializador basado en el usuario?
En la documentación:
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
Estoy usando Django REST Framework para serializar un modelo de Django. Tengo una vista ListCreateAPIView para enumerar los objetos y una vista RetrieveUpdateDestroyAPIView para recuperar / actualizar / eliminar objetos individuales. El modelo almacena información que los usuarios envían ellos mismos. La información que envían contiene información privada y información pública. Quiero que todos los usuarios puedan enumerar y recuperar la información pública, pero solo quiero que el propietario enumere / recupere / actualice / elimine la información privada. Por lo tanto, necesito permisos por campo y no permisos de objeto.
La sugerencia más cercana que encontré fue https://groups.google.com/forum/#!topic/django-rest-framework/FUd27n_k3U0 que cambia el serializador según el tipo de solicitud. Esto no funcionará para mi situación porque no tengo el queryset u objeto en ese punto para determinar si es propiedad del usuario o no.
Por supuesto, tengo mi interfaz ocultando la información privada, pero las personas inteligentes todavía pueden rastrear las solicitudes de API para obtener los objetos completos. Si es necesario el código, puedo proporcionarlo, pero mi solicitud se aplica a los diseños de VT DANGO Framework de vainilla.
Aquí:
- 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
Descubrí una manera de hacerlo. En el serializador, tengo acceso tanto al objeto como al usuario que realiza la solicitud de API. Por lo tanto, puedo verificar si el solicitante es el propietario del objeto y devolver la información privada. Si no lo son, el serializador devolverá una cadena vacía.
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
En caso de que solo esté realizando operaciones de LECTURA, simplemente puede abrir los campos en el método de representación del serializador.
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
Esto debería ser suficiente para ocultar campos sensibles.
Para una solución que permita leer y escribir, haga esto:
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()
...
Ver la docs para un ejemplo.
Tuve un problema similar el otro día. Aquí está mi enfoque:
Esta es una solución 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
Y una solución 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
Esta vez extendemos ReadOnlyField
solo porque to_representation
no está implementado en la clase serializers.Field
.