Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

context in nested serializers django rest framework

If i have a nested serializer:

class ChildSerializer(ModelSerializer):     class Meta:         fields = ('c_name', )         model = Child   class ParentSerializer(ModelSerializer):      child = ChildSerializer(many=True, read_only=True)      class Meta:         model = Parent         fields = ('p_name', 'child') 

And i want to access the context in the nested serializer, how can i do that? As far as i can tell, context isn't passed to the Child.

I want to be able to implement a permission model per user on fields, for that i overridden the get_fields() method of the ModelSerializer:

def get_fields(self):     fields = super().get_fields()     ....     for f in fields:         if has_rights(self.context['request'].user, f, "read"):             ret_val[f] = fields[f]     ....     return ret_val 

Which works for regular serializers, but the context, and thus the request and user are not available when the nested child is passed to get_fields(). How do i access the context when the serializer is nested?

like image 656
Robin van Leeuwen Avatar asked May 31 '15 17:05

Robin van Leeuwen


People also ask

How do you pass extra context data to Serializers in Django REST framework?

In function based views we can pass extra context to serializer with "context" parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with "self. context". From example, to get "exclude_email_list" we just used code 'exclude_email_list = self.

What is context in Django REST framework?

Context are used in Django REST Framework when you want to add extra data to the serializer in addition to the object being serialized. views.py. class PostViewSet(ModelViewSet): queryset = Post.

What is Serializers in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

How do I pass Queryset to serializer?

To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.


2 Answers

Ok i found a working solution. I replaced the ChildSerializer assignment in the Parent class with a SerializerMethodField which adds the context. This is then passed to the get_fields method in my CustomModelSerializer:

class ChildSerializer(CustomModelSerializer):     class Meta:         fields = ('c_name', )         model = Child   class ParentSerializer(CustomModelSerializer):      child = serializers.SerializerMethodField('get_child_serializer')      class Meta:         model = Parent         fields = ('p_name', 'child')      def get_child_serializer(self, obj):         serializer_context = {'request': self.context.get('request') }         children = Child.objects.all().filter(parent=obj)         serializer = ChildSerializer(children, many=True, context=serializer_context)         return serializer.data 

and in my CustomModelSerializer:

class CustomModelSerializer(rest_serializer_classes.HyperlinkedModelSerializer):      def __init__(self, *args, **kwargs):         """             Make sure a user is coupled to the serializer (needed for permissions)         """         super().__init__(*args, **kwargs)         if not self.context:             self._context = getattr(self.Meta, 'context', {})         try:             self.user = self.context['request'].user         except KeyError:             self.user = None       def get_fields(self):         ret = OrderedDict()          if not self.user:             print("No user associated with object")             return ret          fields = super().get_fields()          # Bypass permission if superuser         if self.user.is_superuser:             return fields          for f in fields:             if has_right(self.user, self.Meta.model.__name__.lower(), f, "read"):                 ret[f] = fields[f]          return ret 

This seems to work fine, and fields of the child are discarded in the serializer when i either revoke read-rights on Child.c_name or on Parent.child

like image 115
Robin van Leeuwen Avatar answered Oct 05 '22 16:10

Robin van Leeuwen


If you can not change the nature of you child serializer, as in @Kirill Cherepanov and @Robin van Leeuwen answers, a light but not full-integrated solution would be to manually pass the context in __init__() function :

class ChildSerializer(CustomModelSerializer):     class Meta:         fields = ('c_name', )         model = Child   class ParentSerializer(CustomModelSerializer):      child = ChildSerializer(many=True, read_only=True)      class Meta:         model = Parent         fields = ('p_name', 'child')      def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)         # We pass the "upper serializer" context to the "nested one"         self.fields['child'].context.update(self.context) 
like image 41
Timothé Delion Avatar answered Oct 05 '22 14:10

Timothé Delion