【问题标题】:Django Rest Framework : Nested Serializer Dynamic Model FieldsDjango Rest 框架:嵌套序列化程序动态模型字段
【发布时间】:2016-05-04 08:09:04
【问题描述】:

我已经定义了如下的序列化程序。我正在使用mixin 动态更改显示字段。

class SerializerTwo(serializers.ModelSerializer):

    class Meta:
        model = Two
        fields = ('name', 'contact_number')

class SerializerOne(DynamicFieldsModelSerializer, serializers.ModelSerializer):
    another_field = SerializerTwo()

    class Meta:
        lookup_field = 'uuid'
        model = One
        fields = ('status', 'another_field',)

现在我想要做的是,动态传递(即时)将从 SerializerTwo 使用的所有字段,就像我为 SerializerOne 所做的那样。

我为 SerializerOne 做的方式是:

# where fields=('status')
SerializerOne(queryset, fields=fields)

有没有办法,我可以将 SerializerTwo 中的字段添加到上面的 Serializer 初始化中。

# where fields=('status', 'name') name from SerializerTwo
# the double underscore notation does not work here for fields, so another_field__name cannot be used as well
SerializerOne(queryset, fields=fields)

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    我使用下面的方式来实现所谓的Nested Serializer Dynamic Model Fields。

    class SerializerTwo(serializers.ModelSerializer):
        fields_filter_key = 'two_fields'
        class Meta:
            model = Two
            fields = ('name', 'contact_number')
    
    class SerializerOne(DynamicFieldsModelSerializer, serializers.ModelSerializer):
        fields_filter_key = 'one_fields'
        another_field = serializers.SerializerMethodField()
    
        class Meta:
            lookup_field = 'uuid'
            model = One
            fields = ('status', 'another_field',)
    
        def get_another_field(self, obj):
            another_filed_serializer = SerializerTwo(obj.another_field, 
                                                     context=self.context)
            return another_filed_serializer.data 
    

    我们对DynamicFieldsModelSerializer进行了一些修改

    class DynamicFieldsModelSerializer(serializers.ModelSerializer):
        def __init__(self, *args, **kwargs):
            super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
    
            if 'request' not in self.context or not self.fields_filter_key:
                return
            fields = self.context['request'].query_params.get(self.fields_filter_key)
            if fields:
                fields = fields.split(',')
                allowed = set(fields)
                existing = set(self.fields.keys())
                for field_name in existing - allowed:
                    self.fields.pop(field_name)
    

    所以最后一个问题是如何组织url,这样写GET url:

    domain/something?one_fields=name,contact_number&two_fields=another_field

    【讨论】:

      【解决方案2】:

      在遇到同样的问题后,我找到了解决方案,希望这对某些人有所帮助。我修改了 DynamicFieldsModelSerializer 定义为here

      def __init__(self, *args, **kwargs):
      # Don't pass the 'fields' arg up to the superclass
      fields = kwargs.pop('fields', None)
      nested = kwargs.pop('nested', None)
      # Instantiate the superclass normally
      super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
      
      if fields is not None:
          # Drop any fields that are not specified in the `fields` argument.
          allowed = set(fields)
          existing = set(self.fields.keys())
          for field_name in existing - allowed:
              self.fields.pop(field_name)
      
      if nested is not None:
          for serializer in nested:
              try:
                  nested_serializer = self.fields[serializer]
              except:
                  logger.warning("Wrong nested serializer name")
                  continue
      
              allowed = set(nested[serializer])
              existing = set(nested_serializer.fields.keys())
              for field_name in existing - allowed:
                  nested_serializer.fields.pop(field_name)
      

      之后,你可以这样使用它:

      SerializerOne(queryset, nested={"another_field": ["name"]})
      

      您可以修改我的解决方案以使用双下划线而不是另一个带有 dict 的 kewyord,但我想将常规字段与嵌套序列化程序分开。

      还可以改进为递归,这里我只处理一个嵌套序列化器的深度

      编辑 毕竟我修改了我的代码以使用双下划线语法:

      def __init__(self, *args, **kwargs):
      
          def parse_nested_fields(fields):
              field_object = {"fields": []}
              for f in fields:
                  obj = field_object
                  nested_fields = f.split("__")
                  for v in nested_fields:
                      if v not in obj["fields"]:
                          obj["fields"].append(v)
                      if nested_fields.index(v) < len(nested_fields) - 1:
                          obj[v] = obj.get(v, {"fields": []})
                          obj = obj[v]
              return field_object
      
          def select_nested_fields(serializer, fields):
              for k in fields:
                  if k == "fields":
                      fields_to_include(serializer, fields[k])
                  else:
                      select_nested_fields(serializer.fields[k], fields[k])
      
          def fields_to_include(serializer, fields):
              # Drop any fields that are not specified in the `fields` argument.
              allowed = set(fields)
              existing = set(serializer.fields.keys())
              for field_name in existing - allowed:
                  serializer.fields.pop(field_name)
      
          # Don't pass the 'fields' arg up to the superclass
          fields = kwargs.pop('fields', None)
          # Instantiate the superclass normally
          super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
      
          if fields is not None:
              fields = parse_nested_fields(fields)
              # Drop any fields that are not specified in the `fields` argument.
              select_nested_fields(self, fields)
      

      然后你可以像这样使用它:

      SerializerOne(instance, fields=["another_field__name"])
      

      【讨论】:

        【解决方案3】:

        @Lotram 的答案不适用于返回多个值的字段(通过many=True)。

        以下代码改进了 @Lotram 的解决方案,该解决方案适用于返回多个值的字段:

        class NestedDynamicFieldsModelSerializer(serializers.ModelSerializer):
        
        def __init__(self, *args, **kwargs):
        
            def parse_nested_fields(fields):
                field_object = {"fields": []}
                for f in fields:
                    obj = field_object
                    nested_fields = f.split("__")
                    for v in nested_fields:
                        if v not in obj["fields"]:
                            obj["fields"].append(v)
                        if nested_fields.index(v) < len(nested_fields) - 1:
                            obj[v] = obj.get(v, {"fields": []})
                            obj = obj[v]
                return field_object
        
            def select_nested_fields(serializer, fields):
                for k in fields:
                    if k == "fields":
                        fields_to_include(serializer, fields[k])
                    else:
                        select_nested_fields(serializer.fields[k], fields[k])
        
            def fields_to_include(serializer, fields):
                # Drop any fields that are not specified in the `fields` argument.
                allowed = set(fields)
                if isinstance(serializer, serializers.ListSerializer):
                    existing = set(serializer.child.fields.keys())
                    for field_name in existing - allowed:
                        serializer.child.fields.pop(field_name)
                else:
                    existing = set(serializer.fields.keys())
                    for field_name in existing - allowed:
                        serializer.fields.pop(field_name)
        
            # Don't pass the 'fields' arg up to the superclass
            fields = kwargs.pop('fields', None)
            # Instantiate the superclass normally
            super(NestedDynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
        
            if fields is not None:
                # import pdb; pdb.set_trace()
                fields = parse_nested_fields(fields)
                # Drop any fields that are not specified in the `fields` argument.
                select_nested_fields(self, fields)
        

        【讨论】:

        • 我在 if fields is not None: 块之后将 fields = kwargs.pop('fields', None) 替换为 kwargs.get('context').get('request').query_params.get('fields') if isinstance(kwargs.get('context'), dict) else None 和 fields = parse_nested_fields(fields) 替换为 fields = parse_nested_fields(fields.split(','))
        猜你喜欢
        • 2014-07-26
        • 2021-06-23
        • 2019-12-23
        • 2016-05-14
        • 2019-10-26
        • 1970-01-01
        • 2014-11-30
        • 2017-11-15
        • 2018-12-13
        相关资源
        最近更新 更多