-
Notifications
You must be signed in to change notification settings - Fork 300
[Question] dynamic included_serializers #1007
-
Hi,
I am reading the current documentation but I didn't find the way to included a serializer dynamically with the included_serializers attribute.
I think is a common feature to use X or B serializer by N attribute of the request method, request user,... For example, the user type. I add this logic to the ViewSet overriding the get_serializer_class method but now I need to add an included with this behavior.
Following, you can see a simple example of this scenario:
# models.py
class Author(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=300)
class Comment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
text = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# serializers.py
class CommentPublicSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("text",)
class CommentPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("id", "text")
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Author
fields = ("id", "name", "last_comment")
last_comment = SerializerMethodResourceRelatedField(
model=Comment,
method_name="get_last_comment",
related_link_view_name="api:comment-list",
related_link_url_kwarg="author_pk",
)
def get_last_comment(self, instance: Author) -> Comment:
return Comment.objects.filter(author=instance).order_by("created_at").last()
# views.py
class CommentViewSet(ModelViewSet):
default_serializer_class = CommentSerializer
serializer_classes = {
"private" : CommentPrivateSerializer,
}
queryset = Comment.objects.all()
def get_serializer_class(self) -> Serializer:
return self.serializer_classes.get(self.request.user.type, self.default_serializer_class)
I would like to add in the current AuthorSerializer an included_serializer -> last_comment with the CommentPrivateSerializer or the CommentPublicSerializer depending of the request user type. Something like:
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
included_serializers = {
"last_comment": ... # CommentPrivateSerializer or CommentPublicSerializer
}
....
Is there any way to do this? If not, have you any idea to do it? It's possible you have in mind something so I don't mind to create a PR to add this feature.
IMHO, we could move the included serializer to the ResourceRelatedField adding a boolean attribute "includable" and use the ViewSet to get the serializer?
Thanks in advance!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 3 replies
-
I think what you are trying to do is not REST conform as you will end up with two instances of the same resource with different representation depending on user (see #901 (comment) for a similar discussion).
To be REST conform you would need to have two different APIs one private and one public returning different resources. The model can be the same simply the resource name of the serializer needs to be set differently. (by setting resource_name in serializer Meta class). Note thought in your example you are trying to hide the id of the comment, in JSON:API spec the id of a resource cannot be hidden
If you really wanna move forward with your current idea one resource different representation you could overwrite get_fields in a CommentSerializer and return fields according to the rights of a user.
Hope this is helpful.
Beta Was this translation helpful? Give feedback.
All reactions
-
Thank @sliverc for a quick reaction!
Sorry, I've noticed the example I added is not very clear. I know the id cannot be hidden in JSON:API so you can change the id by other
"sensitive field", i.e the vat number, the bank account. Then you don't want that all the users know you have this information so if you are a user type A you can see this information but if you are a user of type B you cannot see them.
You have a Team manager and members (employees) of the team. All of them can access to all the members of the same team, the manager can see the salary but the rest of them cannot see. IMHO it's overkill to have a private / public API for this. How you deal with this kind of features?
About the get_fields it was my first option but we will have many types of users and it will be very complex to maintain. I am trying to keep simple avoiding have everything mixed.
Beta Was this translation helpful? Give feedback.
All reactions
-
You could extract confidential employee data onto a different resource and protect that resource with permission_classes. You could either do this on the database level with OneToOneField so you have EmployeeModel and EmployeeConfidentialModel. It could also be done on on the serializer level like you have a EmployeeSerializer and EmployeeConfidentialSerializer both based on the same model but exposing different fields. After that you connect the two serializers with SerializerMethodResourceRelatedField.
Beta Was this translation helpful? Give feedback.
All reactions
-
I see your point and I agree with you the best way to handle this is split the fields in groups but, we have a complex case use. we have a model with N fields, we can called field_1, field_2, field_n with N types of users where the user_1 can access from field_1 to field_5, users_2 can access from field_2 to field_7, user_3 can access from field_1 to field_9. So I am thinking is crazy to have as many endpoints as cases uses we have due to there is not a easy way to split them.
It means you have more than one field confidential, for example, vat_number and you only want the admin user has access to this field but not for the TeamManagerUser. Furthermore, you want other user_type have access to the vat_number and the salary and other user_type has access to salary. All of them can see the first_name but only the admin user has access to the full name. We'll have a endpoint by field ->
- vat_number
- salary
- first_name
- last_name
I know the salary could be part of other model / endpoint (maybe Contracts?) but you can change it by security_social_number or field_x related directly with the Employee.
Thank you very much for your answer!
Beta Was this translation helpful? Give feedback.