10

For example, I have a Person model and its serializer

class Person(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    sex = models.IntegerField()
    phone = models.CharField(max_length=255)


class SimplePersonSerializer(serializer.ModelSerializer):
    class Meta:
        model = Person
        fields = ('first_name', 'last_name')

Then in my view function, I can:

@api_view(['GET'])
def people(request):
    people = Person.objects.all()
    data = SimplePersonSerializer(people, many=True).data
    return Response(data)

However, when I profiler it using django-debug-toolbar, it shows that the serializer ask SQL Server to select all field of Person model, despite I only need first_name and last_name.

I know I can change people = Person.objects.all() to people = Person.objects.all().only('first_name', 'last_name') to make it. But I wonder if I can do this inside the serializer.

Yriuns
  • 755
  • 1
  • 8
  • 22

4 Answers4

16

You can create dynamic field serializer for this and get the field data dynamically.

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # 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:
            # 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)

class SimplePersonSerializer(DynamicFieldsModelSerializer):
    class Meta:
        model = Person
        fields = '__all__' 

And then you can use it in your views like this.

@api_view(['GET'])
def people(request):
    fields = ('first_name', 'last_name')
    people = Person.objects.all().only(fields)
    data = SimplePersonSerializer(people, many=True, fields = fields).data
    return Response(data)

This helps to improve performance because it will fetch only the required data. (when using Person.objects.all().only('first_name', 'last_name') to fetch specific data)

  • But I need to write `fields` twice in every function calling `SimplePersonSerializer `. I prefer to write different `Serializer`s that including different fields for a certain model. – Yriuns Nov 15 '18 at 14:08
  • No, You can assign this in a variable and use it at two places( I have edited my answer) If you prefer to write different serializers for a single model then It will become very difficult to manage in a large application. Its always prefer to get fields value dynamically then explicitly define more than one serializer. – shivam bhatnagar Nov 15 '18 at 15:52
2

You get all the fields queried because that's the query that runs by default when you do .all etcetera. You only limit the fields (SELECT field1, field2, ...) when you do .only, .values, or .values_list.

You can you can define the fields inside the serializer or you can go further and do dynamic things like: https://github.com/wimglenn/djangorestframework-queryfields

Inside the serializer:

class Meta:
    fields = (*,...)

But, this is specific to the serializer. As the name implies this is just serializing the returned data into objects.

You can do queries in the serializer, but this typically for custom fields.

Pythonista
  • 11,377
  • 2
  • 31
  • 50
0

No you cannot achieve that by using builtin features of django and rest_framework.

Since serializer tries to access fields for model, you can describe properties by setting @property in your model or define custom SerializerMethodField, all this could possibly use all fields of your model.

vishes_shell
  • 22,409
  • 6
  • 71
  • 81
0

I add a class method setup_eager_loading for SimplePersonSerializer

class SimplePersonSerializer(serializer.ModelSerializer):

    @classmethod
    def setup_eager_loading(cls, queryset):
        queryset = queryset.only(*cls.Meta.fields)
    return queryset

    class Meta:
        model = Person
        fields = ('first_name', 'last_name')

And use it like this:

people = Person.objects.all()
people = SimplePersonSerializer.setup_eager_loading(people)
data = SimplePersonSerializer(people, many=True).data
Yriuns
  • 755
  • 1
  • 8
  • 22
  • This is nice, what about just setting the serializer queryset to use `.only`, directly so you don't need to call setup_eager_loading?? – user2085368 Aug 24 '19 at 18:18