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