70
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
15
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.
- [Django]-Django Password Generator
- [Django]-Aggregating save()s in Django?
- [Django]-How to query as GROUP BY in Django?
6
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
- [Django]-How to get a favicon to show up in my django app?
- [Django]-Django: Calculate the Sum of the column values through query
- [Django]-Django settings per application โ best practice?
6
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
- [Django]-Django: Make certain fields in a ModelForm required=False
- [Django]-Django FileField: How to return filename only (in template)
- [Django]-Django 1.8 migrate is not creating tables
3
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.
- [Django]-How to mock users and requests in django
- [Django]-Django filter vs exclude
- [Django]-Django simple_tag and setting context variables
3
This is an old question, but the topic is still relevant.
DRF recommends to create different serializers for different permission. But this approach only works, if you have only a few permissions or groups.
restframework-serializer-permissions is a drop in replacement for drf serializers.
Instead of importing the serializers and fields from drf, you are importing them from serializer_permissions.
Installation:
$ pip install restframework-serializer-permissions
Example Serializers:
# import permissions from rest_framework
from rest_framework.permissions import AllowAny, IsAuthenticated
# import serializers from serializer_permissions instead of rest_framework
from serializer_permissions import serializers
# import you models
from myproject.models import ShoppingItem, ShoppingList
class ShoppingItemSerializer(serializers.ModelSerializer):
item_name = serializers.CharField()
class Meta:
# metaclass as described in drf docs
model = ShoppingItem
fields = ('item_name', )
class ShoppingListSerializer(serializers.ModelSerializer):
# Allow all users to list name
list_name = serializers.CharField(permission_classes=(AllowAny, ))
# Only allow authenticated users to retrieve the comment
list_comment = serializers.CharField(permissions=(IsAuthenticated, ))
# show owner only, when the current user has 'auth.view_user' permission
owner = serializers.CharField(permissions=('auth.view_user', ), hide=True)
# serializer which is only available, when the user is authenticated
items = ShoppingItemSerializer(many=True, permissions=(IsAuthenticated, ), hide=True)
class Meta:
# metaclass as described in drf docs
model = ShoppingItem
fields = ('list_name', 'list_comment', 'owner', 'items', )
Disclosure: Iโm the author of this extension
- [Django]-Django Model Field Default Based Off Another Field in Same Model
- [Django]-Get the name of a decorated function?
- [Django]-Django migration strategy for renaming a model and relationship fields
2
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.
- [Django]-How to see which tests were run during Django's manage.py test command
- [Django]-Serializer call is showing an TypeError: Object of type 'ListSerializer' is not JSON serializable?
- [Django]-Any way to make {% extends 'โฆ' %} conditional? โ Django
2
Just share another possible solution
For example, to make email only show for oneself.
On UserSerializer, add:
email = serializers.SerializerMethodField('get_user_email')
Then implement get_user_email like this:
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'
- [Django]-Django queryset filter โ Q() | VS __in
- [Django]-Python 3 list(dictionary.keys()) raises error. What am I doing wrong?
- [Django]-What is "load url from future" in Django
0
I solved it using a serializer Mixin:
class FieldPermissionModelSerializerMixin(serializers.ModelSerializer):
"""
A mixin that allows you to specify what fields will be returned based on field level permissions
"""
permission_fields = []
def get_field_names(self, declared_fields, info) -> List:
"""Determine the fields to apply."""
fields = getattr(self.Meta, "fields", [])
for permission_field in self.permission_fields:
app_name = getattr(self.Meta, "model", None)._meta.app_label
permission_name = f"can_view_field_{permission_field}"
full_permission_name = f"{app_name}.{permission_name}"
if self.context["request"].user.has_perm(full_permission_name):
fields.append(permission_field)
return fields
Then you can use this serializer with base fields and permissionable fields.
POSITION_BASE_FIELDS = [
"id",
"name",
"level",
"role",
"sort",
]
POSITION_PERMISSION_FIELDS = ["market_salary", "recommended_rate_per_hour"]
class PositionListSerializer(FieldPermissionModelSerializerMixin):
permission_fields = POSITION_PERMISSION_FIELDS
class Meta:
model = Position
fields = POSITION_BASE_FIELDS + []
This is then based on field level permissions defined on the model.
class Position(models.Model):
name = models.CharField(max_length=255, db_index=True)
level = models.CharField(max_length=255, null=True, blank=True)
sort = models.IntegerField(blank=True, default=0)
market_salary = models.DecimalField(max_digits=19, decimal_places=2, default=0.00)
recommended_rate_per_hour = models.DecimalField(
max_digits=7, decimal_places=2, null=True, blank=True
)
class Meta:
ordering = ["name", "sort"]
unique_together = ("name", "level")
permissions = (
("can_view_field_market_salary", "Can view field: market_salary"),
(
"can_view_field_recommended_rate_per_hour",
"Can view field: recommended_rate_per_hour",
),
)
- [Django]-Django migration fails with "__fake__.DoesNotExist: Permission matching query does not exist."
- [Django]-Django Admin Show Image from Imagefield
- [Django]-How to get GET request values in Django Templates?