有没有一种简单的方法可以只使用 Django Rest Framework 的 ModelSerializer 序列化非空字段?

Is there an easy way to only serialize non-empty fields with Django Rest Framework's ModelSerializer?

我正在开发一个 Django 项目,其中包含许多相当大的模型(大约 80 个字段)。我正在使用 Django Rest Framework 的 ModelSerializer 序列化模型,并使用 ViewSets 为我的前端提供 API。

效果很好,但我想减少服务器传输的数据量。我的大部分模型字段都是可选的,许多实例只有其中少数几个有值。在这些情况下 我只想序列化那些具有值(即真实值)的字段。

我想我可以在序列化器端或模型端做到这一点,但我不太明白这两者如何相互交流,可以这么说。

我目前的序列化器非常简单:

class OutfitSerializer(serializers.ModelSerializer):
  class Meta:
    model = Outfit
    fields = '__all__'

观点同样简单:

# Outfit views
class OutfitViewSet(viewsets.ViewSet):
  def list(self, request):
    queryset = Outfit.objects.all()
    serializer = OutfitSerializer(queryset, many=True)
    return Response(serializer.data)

我摆弄了序列化程序的子类并修改了 __init__ 函数(灵感来自 DRF 文档的 this 部分):

class NonEmptyFieldsModelSerializer(serializers.ModelSerializer):
  """
  ModelSerializer that allows fields to be set at runtime via the
  optional 'fields' argument

  Copied from https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
  """
  
  def __init__(self, *args, **kwargs):
    super(NonEmptyFieldsModelSerializer, self).__init__(*args, **kwargs)
    
    all_fields = set(self.fields)
    for field_name in all_fields:
      # IF THIS FIELD IS EMPTY IN THE OBJECT CURRENTLY BEING SERIALIZED:
        self.fields.pop(field_name)

但我不确定如何以及是否可以访问 __init__ 中的当前对象。我也不太明白序列化整个查询集是如何工作的:Would a new serializer instance be initialized for each model instance?

我可以简单地为模型本身编写一个序列化程序函数,但这有点违背了使用 Django Rest Framework 的目的,因为我必须单独配置每个字段。

那么,我怎样才能只序列化模型实例的非空字段?

编辑:我还想删除值为 0 的小数。但是,DRF 的 ModelSerializer 默认将小数转换为字符串以避免不准确。因此,我调整如下:

class NonEmptySerializer(serializers.ModelSerializer):
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        non_null_ret = copy.deepcopy(ret)
        for key in ret.keys():
            if not ret[key]:
                non_null_ret.pop(key)
            elif isinstance(ret[key], str) and re.fullmatch('[0.]+', ret[key]):
                non_null_ret.pop(key)
        return non_null_ret

您可以覆盖 ModelSerializer 的 to_representation 方法:

class NonEmptySerializer(ModelSerializer):
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        non_null_ret = copy.deepcopy(ret)
        for key in ret.keys():
            if not ret[key]:
                non_null_ret.pop(key)
        return non_null_ret

然后在需要的时候继承这个序列化器:

class OutfitSerializer(NonEmptySerializer):
    class Meta:
        model = Outfit
        fields = '__all__'

由于 to_representation 对单个序列化器和列表序列化器都被调用,因此它在这两种情况下都有效。