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 (i.e. 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
CodePudding user response:
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.