【问题标题】:context in nested serializers django rest framework嵌套序列化程序 django rest 框架中的上下文
【发布时间】:2015-08-14 03:26:45
【问题描述】:

如果我有一个嵌套的序列化器:

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')

我想访问嵌套序列化程序中的上下文,我该怎么做?据我所知,上下文没有传递给孩子。

我希望能够在字段上为每个用户实现一个权限模型,为此我重写了 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

这适用于常规序列化程序,但是当嵌套子级传递给 get_fields() 时,上下文以及请求和用户不可用。嵌套序列化程序时如何访问上下文?

【问题讨论】:

    标签: django python-3.x serialization django-rest-framework


    【解决方案1】:

    如果你想限制子序列化字段的查询集,那么继续使用

    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 引导我通过调试并查看child 的get_fields 函数中的可用变量来找到它。

    【讨论】:

      【解决方案2】:

      好的,我找到了一个最终解决方案,可以完全按照要求执行 - 将上下文传递给嵌套的序列化程序。 为了实现这一点,需要覆盖嵌套序列化程序的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)
      

      【讨论】:

      • 我刚刚测试了这种方法,它对我来说效果很好,并且感觉就像是提供了其他解决方案的更简单的设置。我实际上想把所有的上下文都拉下来,而不仅仅是一个属性。所以我最终制作了一个通用的 Mixin,我可以使用这种方法在多个序列化程序中使用。
      【解决方案3】:

      如果您无法更改子序列化程序的性质,如@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)
      

      【讨论】:

      • 这就是我想要的。接受的答案是它通常如何用于读取序列化程序。但这更适合可写的。
      【解决方案4】:

      我知道这是一个老问题,但我在 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
      

      在上面,我创建了一个序列化器基类,它覆盖get_fields() 并将self._context 传递给具有相同基类的任何子序列化器。对于 ListSerializer,我将上下文附加到它的子级。

      然后,我检查查询参数“omit_data”,如果需要,删除“数据”字段。

      我希望这对仍在寻找答案的人有所帮助。

      【讨论】:

      • 这似乎是一个非常冗长/复杂的解决方案。与 Kirill Cherepanovs 解决方案相比,它有哪些优势?
      • 我喜欢这个答案,因为它可以重复使用。就我而言,我有一个带有 36 个字段的序列化器,它们也用于不同的序列化器。我将扩展这个想法并做 2 个 mixin。一个传递给 PassContext,它将执行此解决方案的工作,另一个传递给 InheritContext,它是类型检查,以查看孩子是否应该实际接收上下文。
      【解决方案5】:

      您可以改用serialziers.ListFieldListField 自动将上下文传递给它的孩子。所以,这是你的代码

      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')
      

      【讨论】:

      • 感谢您的提示!
      • @Kirill,当我尝试这个时,我得到:“子对象不可迭代。就我而言,我的子对象不是“many=True”。有什么想法吗?
      【解决方案6】:

      好的,我找到了一个可行的解决方案。我用添加上下文的 SerializerMethodField 替换了 Parent 类中的 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_nameParent.child

      【讨论】:

      • 如果我希望嵌套序列化程序可写,这将无济于事。
      • 是的,兄弟,这真的很晚了,但我爱你。好几个小时试图弄清楚这一点。谢谢!
      猜你喜欢
      • 2019-12-23
      • 2021-06-23
      • 2016-05-14
      • 2018-12-13
      • 2015-05-11
      • 2016-05-31
      • 2015-02-17
      • 2020-07-07
      相关资源
      最近更新 更多