【问题标题】:Dynamically exclude or include a field in Django REST framework serializer在 Django REST 框架序列化程序中动态排除或包含字段
【发布时间】:2015-03-12 04:49:14
【问题描述】:

我在 Django REST 框架中有一个序列化器,定义如下:

class QuestionSerializer(serializers.Serializer):
    id = serializers.CharField()
    question_text = QuestionTextSerializer()
    topic = TopicSerializer()

现在我有两个使用上述序列化程序的 API 视图:

class QuestionWithTopicView(generics.RetrieveAPIView):
    # I wish to include all three fields - id, question_text
    # and topic in this API.
    serializer_class = QuestionSerializer

class QuestionWithoutTopicView(generics.RetrieveAPIView):
    # I want to exclude topic in this API.
    serializer_class = ExamHistorySerializer

一种解决方案是编写两个不同的序列化程序。但是必须有一个更简单的解决方案来有条件地从给定的序列化程序中排除一个字段。

【问题讨论】:

    标签: python django serialization django-rest-framework django-serializer


    【解决方案1】:

    你试过这个技巧吗

    class QuestionSerializer(serializers.Serializer):
        def __init__(self, *args, **kwargs):
            remove_fields = kwargs.pop('remove_fields', None)
            super(QuestionSerializer, self).__init__(*args, **kwargs)
    
            if remove_fields:
                # for multiple fields in a list
                for field_name in remove_fields:
                    self.fields.pop(field_name)
    
    class QuestionWithoutTopicView(generics.RetrieveAPIView):
            serializer_class = QuestionSerializer(remove_fields=['field_to_remove1' 'field_to_remove2'])
    

    如果没有,请尝试一下。

    【讨论】:

    • 这应该可以。但我期待在 Django 休息框架中内置一些东西。如果我找不到任何更清洁的解决方案,我会使用它。
    • 如果条件为if remove_fields and not getattr(self, 'many', False):,我建议您更正
    • 需要注意的是,这只适用于只读序列化器。这将在执行 CRUD 操作的序列化程序上创建或更新时中断。
    • @SudipKafle 在 DRF 的文档中也建议了类似的方法 django-rest-framework.org/api-guide/serializers/…
    • 小心这种方法,它不适用于many=True
    【解决方案2】:

    创建一个新的序列化程序是可行的方法。通过有条件地删除序列化程序中的字段,您会增加额外的复杂性并使您的代码更难快速诊断。您应该尽量避免混合单个类的职责。

    遵循基本的面向对象设计原则是必经之路。

    QuestionWithTopicView 是一个 QuestionWithoutTopicView 但有一个附加字段。

    class QuestionSerializer(serializers.Serializer):
            id = serializers.CharField()
            question_text = QuestionTextSerializer()
            topic = TopicSerializer()
    
    class TopicQuestionSerializer(QuestionSerializer):
           topic = TopicSerializer()
    

    【讨论】:

    • 有条件地删除字段并不总是不正确的情况。如果我有 100 个模型,每个模型有 10 个字段,我不想在序列化程序中显示,除非在某些情况下,我将不得不手动排除一千个字段。我宁愿自动排除它们并创建一个或两个单独的序列化程序,它们会为那些特殊情况添加这些字段。
    • 在您的示例中,您不必手动排除一千个字段。如果您有 100 个模型,每个模型有 10 个字段,那么您永远不想在序列化程序中显示,除非在某些情况下,然后创建一个包含所有字段的序列化程序。然后创建第二个序列化程序,扩展第一个序列化程序并使用 exclude_fields 元类选项。
    • 另外请注意,如果是这种情况,您可能应该使用 ModelSerializers 而不是构建自己的序列化程序。
    • 确实,你是对的。我在想自己。我的用例比仅具有排除字段的模型要简单一些。在我的项目中,我也有用户定义的动态模型。用户应该能够在其中向数据库添加字段,这些字段将被排除在外,而无需修改源代码。除了这些线,我还在想,把它们投射到这个案子上;)
    • 这个应该多投。有条件地排除字段是隐含的逻辑,因为这里建议覆盖get_serializer_class stackoverflow.com/a/22755648/5539589 对于显式调节序列化程序类更具pythonic,恕我直言,它更具可读性。
    【解决方案3】:

    将above answer 扩展为更通用的

    class QuestionSerializer(serializers.Serializer):
        def __init__(self, *args, **kwargs):
            fields = kwargs.pop('fields', None)
            super(QuestionSerializer, self).__init__(*args, **kwargs)
            if fields is not None:
                allowed = set(fields.split(','))
                existing = set(self.fields)
                for field_name in existing - allowed:
                    self.fields.pop(field_name)
    
    class QuestionWithoutTopicView(generics.RetrieveAPIView):
        def get_serializer(self, *args, **kwargs):
            kwargs['context'] = self.get_serializer_context()
            fields = self.request.GET.get('display')
            serializer_class = self.get_serializer_class()
            return serializer_class(fields=fields,*args, **kwargs)
        def get_serializer_class(self):
            return QuestionSerializer
        
    

    现在我们可以给出一个名为 display 的查询参数来输出任何自定义的显示格式http://localhost:8000/questions?display=param1,param2

    【讨论】:

      【解决方案4】:

      可以设置Meta的fields和exclude属性

      这是一个例子:

      class DynamicFieldsModelSerializer(serializers.ModelSerializer):
          class Meta:
              model = User
              exclude = ['id', 'email', 'mobile']
      
          def __init__(self, *args, **kwargs):
              super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
              #  @note: For example based on user,  we will send different fields
              if self.context['request'].user == self.instance.user:
                  # Or set self.Meta.fields = ['first_name', 'last_name', 'email', 'mobile',]
                  self.Meta.exclude = ['id']
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-16
        • 2016-05-04
        • 1970-01-01
        • 1970-01-01
        • 2013-02-07
        • 2015-07-20
        • 2019-05-20
        • 1970-01-01
        相关资源
        最近更新 更多