简体   繁体   中英

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

I am working on a Django project with a number of rather large models (around 80 fields). I am using Django Rest Framework's ModelSerializer to serialize the models, and ViewSets to provide an API for my frontend.

That works very well, but I would like to reduce the amount of data that is being transferred by the server. Most of my model fields are optional and many instances only have values for a few of them. In those cases I would like to serialize only those fields that have values (ie that are truthy).

I imagine I could do that either on the serializer side or on the model side, but I do not quite understand how these two talk to each other, so to speak.

My current serializer is very simple:

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

The view is equally simple:

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

I fiddled with sub-classing the serializer and modifying the __init__ function (inspired by this part of the DRF docs):

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)

but I am not sure how and whether I have access to the current object in the __init__. I also don't quite understand how that would work for serializing a whole queryset: Would a new serializer instance be initialized for each model instance?

I could simply write a serializer function for the model itself, but that would kind of defeat the purpose of using Django Rest Framework, as I would have to configure each field individually.

So, how can I serialize only non-empty fields of a model instance?

EDIT : I also wanted to remove decimal numbers with value 0. However, DRF's ModelSerializer converts decimals to strings by default in order to avoid inaccuracies. Therefore, I adjusted Igor's answer as follows:

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

You can override the to_representation method of ModelSerializer:

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

Then inherit from this serialiser when needed:

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

Since to_representation is called for both single and list serialisers, it works in both cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM