context in nested serializers django rest framework
Solution 1
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
Solution 2
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)
Solution 3
You can use serialziers.ListField
instead. ListField
automatically passes context to it's child. So, here's your code
class ChildSerializer(ModelSerializer):
class Meta:
fields = ('c_name', )
model = Child
class ParentSerializer(ModelSerializer):
child = serializers.ListField(read_only=True, child=ChildSerializer())
class Meta:
model = Parent
fields = ('p_name', 'child')
Solution 4
Ok, I have found an ultimate solution that will do exactly what was asked - pass context down to nested serializers.
To achieve that one need to override to_representation(self, instance)
of the nested serializer, so it looks like:
def to_representation(self, instance):
# here we update current serializer's context (access it as self._context)
# to access parent's context we use parent.context
# if there is no parent than it's the first serializer in the chain and it doesn't need any context except for itself's
# for example (after all the checks)
self._context["request"] = self.parent.context["request"]
# and that is it! The modified context will be used for serialization as if it was passed as usually
return super().to_representation(instance)
Solution 5
I know this is an old question, but I had the same question in 2019. Here is my solution:
class MyBaseSerializer(serializers.HyperlinkedModelSerializer):
def get_fields(self):
'''
Override get_fields() method to pass context to other serializers of this base class.
If the context contains query param "omit_data" as set to true, omit the "data" field
'''
fields = super().get_fields()
# Cause fields with this same base class to inherit self._context
for field_name in fields:
if isinstance(fields[field_name], serializers.ListSerializer):
if isinstance(fields[field_name].child, MyBaseSerializer):
fields[field_name].child._context = self._context
elif isinstance(fields[field_name], MyBaseSerializer):
fields[field_name]._context = self._context
# Check for "omit_data" in the query params and remove data field if true
if 'request' in self._context:
omit_data = self._context['request'].query_params.get('omit_data', False)
if omit_data and omit_data.lower() in ['true', '1']:
fields.pop('data')
return fields
In the above, I create a serializer base class that overrides get_fields()
and passes self._context
to any child serializer that has the same base class. For ListSerializers, I attach the context to the child of it.
Then, I check for a query param "omit_data" and remove the "data" field if it's requested.
I hope this is helpful for anybody still looking for answers for this.
Robin van Leeuwen
Updated on June 05, 2022Comments
-
Robin van Leeuwen almost 2 years
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?
-
Ariel about 5 yearsThis doesn't help if I want the nested serializer to be writeable.
-
David Schumann over 4 yearsThis seems to be a very verbose/complicated solution. What are its advantages over Kirill Cherepanovs solution?
-
Reuben almost 4 yearsI like this answer for it's reuse. In my case, I have a serializer with 36 fields that also go to different serializers. I'm going to extend on this idea and do 2 mixins. One to PassContext, which will do what this solution does, and one to InheritContext, which is the type check to see if the child should actually receive the context.
-
mynameistechno over 3 yearsThanks for the tip!
-
ExTexan over 3 years@Kirill, When I tried this, I got: "Child object is not iterable. In my case, my Child is not "many=True". Any thoughts?
-
George Thomas about 3 yearsI just tested this approach and it worked great for me, and feel like a far simpler setup that the other solution is given. I actually wanted to pull all of the context down, not just a single property. So I ended up making a generic Mixin that I could use in multiple serializer using this approach.
-
darkstar about 3 yearsAye bro this is really late but I love you. Been so annoyed for hours trying to figure this out. Thank you!
-
milosst over 2 yearsThat is what I was looking for. Accepted answer is how it normally works for read serializers. But this is better for writable ones.