Serializers Prefetch in View

In Django / DRF, we can provide Prefetch, but is it the role of the serializer to have "good" datas. For example I have this basic code:

from rest_framework import generics, serializers
from app.models import Client


class ClientListCreate(generics.ListCreateAPIView):
    queryset = Client.objects.all()
    serializer = ClientSerializer()

class ClientSerializer(serializers.ModelSerializer):
    amount_users = serializers.SerializerMethodField()
    
    def get_amount_users(self, obj: Client):
        return obj.users.filter(is_active=True).count() # N+1 Queries

This is something to prevent N+1 queries (but the serializer is not stand-alone) :

from rest_framework import generics, serializers
from app.models import Client


class ClientListCreate(generics.ListCreateAPIView):
    queryset = Client.objects.all().prefetch_related(
        Prefetch(
            "users",
            queryset=User.objects.filter(is_active=True)
        )
    )
    serializer = ClientSerializer()

class ClientSerializer(serializers.ModelSerializer):
    amount_users = serializers.SerializerMethodField()
    
    def get_amount_users(self, obj: Client):
        return obj.users.count()

This is something to prevent N+1 queries and be standalone :

from rest_framework import generics, serializers

class PrefetchedDataMixin:
    def has_prefetch(self, to_attr: str = "", related_name: str = ""):
        ...

    def get_prefetch(self, fct, to_attr: str = "", related_name: str = ""):
        ...

class Client(models.Model, PrefetchedDataMixin):
    ...


class ClientListCreate(generics.ListCreateAPIView):
    queryset = Client.objects.all().prefetch_related(
        Prefetch(
            "users",
            queryset=User.objects.filter(is_active=True),
            to_attr="active_users"
        )
    )
    serializer = ClientSerializer()

class ClientSerializer(serializers.ModelSerializer):
    amount_users = serializers.SerializerMethodField()
    
    def get_amount_users(self, obj: Client):
        users = obj.get_prefetch(
            lambda client: client.users.filter(is_active=True),
            to_attr="active_users",
        )
        return users.count()

What would be the best practice for a large scallable app ?

Does it just depend on the company practices ?

I think you might be better off with adding annotation in this case, as the users.count() call in the serializer might still do a separate DB request:

class ClientListCreate(generics.ListCreateAPIView):
    queryset = Client.objects.annotate(
        active_users_count=Count(
            "users", 
            filter=Q(is_active=True),
        ),
    )
    serializer = ClientSerializer()


class ClientSerializer(serializers.ModelSerializer):
    users_count = serializers.SerializerIntegerField(
        source="active_users_count", 
        read_only=True,
    )

    class Meta:
        model = Client

The view is responsible for fetching the data effectively and the serializer is just converting Python -> JSON. There is coupling between the view and the serializer but there is no a good way around it

Вернуться на верх