嵌套序列化程序中的上下文 Django 休息框架
context in nested serializers django rest framework
如果我有一个嵌套的序列化器:
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')
我想访问嵌套序列化程序中的上下文,我该怎么做?据我所知,上下文没有传递给 Child。
我希望能够在字段上实现每个用户的权限模型,为此我覆盖了 ModelSerializer 的 get_fields() 方法:
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
这适用于常规序列化程序,但是当嵌套的 child 传递给 get_fields() 时,上下文以及请求和用户不可用。嵌套序列化程序时如何访问上下文?
好的,我找到了可行的解决方案。我用添加上下文的 SerializerMethodField 替换了 Parent class 中的 ChildSerializer 赋值。然后将其传递到我的 CustomModelSerializer 中的 get_fields 方法:
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
在我的 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
这似乎工作正常,当我撤销对 Child.c_name 或 [=18= 的读取权限时,子项的字段在序列化程序中被丢弃]Parent.child
您可以改用serialziers.ListField
。 ListField
自动将上下文传递给它的 child。所以,这是你的代码
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')
我知道这是一个老问题,但我在 2019 年遇到了同样的问题。这是我的解决方案:
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
在上面,我创建了一个序列化器基 class 来覆盖 get_fields()
并将 self._context
传递给任何具有相同基 class 的子序列化器。对于 ListSerializers,我将上下文附加到它的子项。
然后,我检查查询参数 "omit_data" 并在需要时删除 "data" 字段。
我希望这对仍在为此寻找答案的任何人有所帮助。
如果您不能像@Kirill Cherepanov 和@Robin van Leeuwen 的回答那样改变您的子序列化程序的性质,那么轻量级但不是完全集成的 解决方案将是在 __init__()
函数中手动传递上下文:
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)
好的,我已经找到了一个最终的解决方案,它可以完全满足要求——将上下文传递给嵌套的序列化程序。
要实现这一点,需要覆盖嵌套序列化程序的 to_representation(self, instance)
,所以它看起来像:
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)
如果您试图限制子序列化程序字段的查询集,请继续使用
self.parent.context
在子序列化程序中访问父上下文。
像这样:
def get_fields(self):
fields = super().get_fields()
fields['product'].queryset = Product.objects.filter(company=self.parent.context['company'])
return fields
这个 answer 让我通过调试和查看子 get_fields 函数中的可用变量找到了这个。
我正在使用 djangorestframework
3.12.xx 并且上下文会自动传播到嵌套的序列化程序。
如果我有一个嵌套的序列化器:
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')
我想访问嵌套序列化程序中的上下文,我该怎么做?据我所知,上下文没有传递给 Child。
我希望能够在字段上实现每个用户的权限模型,为此我覆盖了 ModelSerializer 的 get_fields() 方法:
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
这适用于常规序列化程序,但是当嵌套的 child 传递给 get_fields() 时,上下文以及请求和用户不可用。嵌套序列化程序时如何访问上下文?
好的,我找到了可行的解决方案。我用添加上下文的 SerializerMethodField 替换了 Parent class 中的 ChildSerializer 赋值。然后将其传递到我的 CustomModelSerializer 中的 get_fields 方法:
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
在我的 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
这似乎工作正常,当我撤销对 Child.c_name 或 [=18= 的读取权限时,子项的字段在序列化程序中被丢弃]Parent.child
您可以改用serialziers.ListField
。 ListField
自动将上下文传递给它的 child。所以,这是你的代码
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')
我知道这是一个老问题,但我在 2019 年遇到了同样的问题。这是我的解决方案:
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
在上面,我创建了一个序列化器基 class 来覆盖 get_fields()
并将 self._context
传递给任何具有相同基 class 的子序列化器。对于 ListSerializers,我将上下文附加到它的子项。
然后,我检查查询参数 "omit_data" 并在需要时删除 "data" 字段。
我希望这对仍在为此寻找答案的任何人有所帮助。
如果您不能像@Kirill Cherepanov 和@Robin van Leeuwen 的回答那样改变您的子序列化程序的性质,那么轻量级但不是完全集成的 解决方案将是在 __init__()
函数中手动传递上下文:
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)
好的,我已经找到了一个最终的解决方案,它可以完全满足要求——将上下文传递给嵌套的序列化程序。
要实现这一点,需要覆盖嵌套序列化程序的 to_representation(self, instance)
,所以它看起来像:
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)
如果您试图限制子序列化程序字段的查询集,请继续使用
self.parent.context
在子序列化程序中访问父上下文。
像这样:
def get_fields(self):
fields = super().get_fields()
fields['product'].queryset = Product.objects.filter(company=self.parent.context['company'])
return fields
这个 answer 让我通过调试和查看子 get_fields 函数中的可用变量找到了这个。
我正在使用 djangorestframework
3.12.xx 并且上下文会自动传播到嵌套的序列化程序。