We are having too many models which are related, While returning queryset serializing the data is too slow(serializer.data). Below are our models and serializer.
Why django nested serializer is taking too long to return JSON response. What are we doing wrong here?
models.py
class Doctor(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
first_name = models.CharField(_("first_name"), max_length=255, null=False)
last_name = models.CharField(_("last_name"), max_length=255, null=False)
full_name = property(lambda self: "{} {}".format(self.first_name, self.last_name))
email = models.EmailField(_("email"), max_length=255, unique=True, null=False)
password = models.CharField(_("password"), max_length=128, null=False)
country_code = models.CharField(
_("country_code"), max_length=10, null=False, blank=False
)
phone_number = models.CharField(
_("phone_number"), max_length=50, null=False, blank=False
)
full_phone_number = models.CharField(
_("full_phone_number"), max_length=60, unique=True, null=False
)
is_otp_verified = models.BooleanField(_("is_otp_verified"), default=False)
gender = models.CharField(
_("gender"), max_length=50, choices=choices.gender_choices, blank=True, null=False
)
image = encrypt(models.ImageField(upload_to=userProfilePictureDirectoryPath, default=None, null=True, blank=True))
is_active = models.BooleanField(_("is_active"), default=True)
is_verified = models.BooleanField(_("is_verified"), default=False)
national_medical_id = models.CharField(max_length=100, null=True, unique=True)
numeric_id = models.IntegerField(blank=True, null=True)
created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated_at"), auto_now=True, blank=True, null=True)
username = None
USERNAME_FIELD = "full_phone_number"
REQUIRED_FIELDS = []
objects = CustomUserManager()
class Meta:
unique_together = (
"country_code",
"phone_number",
)
def __str__(self) -> str:
return self.phone_number
class Specialties(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name_en = models.CharField(_("name_en"), max_length=255, blank=True, null=True)
name_ro = models.CharField(_("name_ro"), max_length=255, blank=True, null=True)
name_ar = models.CharField(_("name_ar"), max_length=255, blank=True, null=True)
priority = models.IntegerField(_("priority"), default=settings.DEFAULT_SPECIALITY_PRIORTY)
icon = encrypt(models.ImageField(upload_to=userSpecialtiesDirectoryPath, blank=True))
is_active = models.BooleanField(_("is_active"), default=False)
created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated_at"), auto_now=True, blank=True, null=True)
class Meta:
ordering = ['priority']
class Profile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
doctor = models.OneToOneField(to=Doctor, on_delete=models.CASCADE)
bio = models.TextField(max_length=5000, blank=True)
spoken_languages = models.ManyToManyField(to=Language, name=_("language"))
graduation_year = models.CharField(max_length=4, blank=True)
app_language = models.CharField(max_length=255, choices=choices.language_choices, default=choices.language_choices[0][0])
date_of_birth = models.DateField(blank=True, null=True)
reviews_average = models.FloatField(default=0.0)
number_of_bookings = models.IntegerField(default=0)
number_of_reviews = models.IntegerField(default=0)
number_of_visitors = models.IntegerField(default=0)
image = encrypt(models.ImageField(upload_to=userProfilePictureDirectoryPath, default=None, null=True, blank=True))
lowest_price = models.FloatField(null=True, blank=True)
specialties = models.ManyToManyField(to=Specialties, name="specialties")
created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated_at"), auto_now=True, blank=True, null=True)
class Achievements(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
description = models.TextField(
_("description"), max_length=1024, blank=True, null=True
)
name = models.CharField(blank=True, null=True, max_length=255)
image = encrypt(models.ImageField(upload_to=userAchivementsDirectoryPath, blank=True))
is_active = models.BooleanField(_("is_active"), default=False)
issued_at = models.DateTimeField(_("issued_at"), blank=True, null=True)
created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated_at"), auto_now=True, blank=True, null=True)
class PlaceOfWork(models.Model):
id = models.UUIDField(
primary_key=True, default=uuid.uuid4, unique=True, editable=False
)
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
title = models.CharField(max_length=255, blank=False, null=False)
name = models.CharField(max_length=255, blank=False, null=False)
still_working = models.BooleanField(default=True)
started_at = models.DateField(blank=False, null=False)
finished_at = models.DateField(blank=True, null=True)
created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated_at"), auto_now=True, blank=True, null=True)
views.py
class DoctorBySpecialityView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
doctor = request.user
specialties = Specialties.objects
active_doctor = get_all_active_doctors().count()
search = request.query_params.get(constants.SEARCH, None)
context = {"me": doctor, "search": search}
serialized_specialties = SpecialitiesWithListOfDoctors(
specialties, many=True, context=context
)
data = {
"specialties": serialized_specialties.data,
"active_doctor": active_doctor,
}
response = get_response(
status=status.HTTP_200_OK,
status_code=ResponseStatus.success.value,
data=data,
)
return Response(response, status=status.HTTP_200_OK)
serializers.py
class SpecialtiesBaseSerializer(serializers.ModelSerializer):
class Meta:
model = Specialties
read_only_fields = ("id",)
class SpecialtiesSerializer(SpecialtiesBaseSerializer):
class Meta(SpecialtiesBaseSerializer.Meta):
fields = "__all__"
class SpecialitiesWithListOfDoctors(SpecialtiesSerializer):
doctors = serializers.SerializerMethodField()
doctor_count = serializers.SerializerMethodField()
def get_doctors(self, speciality):
from doctor.serializers import DoctorProfileSerializer
me = self.context["me"]
search = self.context["search"]
query = speciality.profile_set.filter(~Q(doctor=me)).all()
if not search:
query = query.filter(
Q(doctor__first_name__icontains=search)
| Q(doctor__last_name__icontains=search)
)
serialized_doctor_profile = DoctorProfileSerializer(
query, many=True, context=self.context
)
self.context["doctor_count"] = query.count()
return serialized_doctor_profile.data
def get_doctor_count(self, speciality):
return self.context["doctor_count"]
class DoctorProfileSerializer(ProfileSerializer):
doctor = serializers.SerializerMethodField()
place_of_work = serializers.SerializerMethodField()
achievements = serializers.SerializerMethodField()
chat_access = serializers.SerializerMethodField()
def get_doctor(self, profile):
doctor = profile.doctor
return DoctorSerializer(doctor).data
def get_place_of_work(self, profile):
place_of_work = profile.doctor.placeofwork_set.all()
return PlaceOfWorkSerializer(place_of_work, many=True).data
def get_achievements(self, profile):
achievements = profile.doctor.achievements_set.all()
return AchievementsSerializer(achievements, many=True).data
class DoctorBaseSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor
class DoctorSerializer(DoctorBaseSerializer):
need_support_help = serializers.SerializerMethodField()
def create(self, validated_data):
validated_data["password"] = make_password(validated_data["password"])
validated_data["numeric_id"] = get_numeric_id()
return super(DoctorSerializer, self).create(validated_data)
class Meta(DoctorBaseSerializer.Meta):
read_only_fields = ("uuid",)
exclude = [
"numeric_id",
]
extra_kwargs = {
"password": {"write_only": True},
"is_superuser": {"write_only": True},
"is_staff": {"write_only": True},
"date_joined": {"write_only": True},
"groups": {"write_only": True},
"user_permissions": {"write_only": True},
"last_login": {"write_only": True},
}
def get_need_support_help(self, doctor):
#using some other models
support_help, created = SupportHelp.objects.get_or_create(doctor=doctor)
need_help = doctor.supporthelp.need_help
return need_help
class PlaceOfWorkBaseSerializer(serializers.ModelSerializer):
class Meta:
model = PlaceOfWork
read_only_fields = ("id",)
def validate(self, data):
if self.partial:
return super().validate(data)
if data.get("finished_at"):
data["still_working"] = False
if data["finished_at"] < data["started_at"]:
raise serializers.ValidationError(
{"finished_at": "finished_at should be greater than started_at"}
)
still_working = data.get("still_working", True)
if not still_working and not data.get("finished_at"):
raise serializers.ValidationError(
{"finished_at": "finished_at is required if still_working is false"}
)
return super().validate(data)
class PlaceOfWorkSerializer(PlaceOfWorkBaseSerializer):
class Meta(PlaceOfWorkBaseSerializer.Meta):
fields = "__all__"
class AchievementsBaseSerializer(serializers.ModelSerializer):
class Meta:
model = Achievements
read_only_fields = ("id",)
def validate_image(self, image):
if not validate_image_size(image):
raise serializers.ValidationError(
f"image should be between {settings.MIN_IMAGE_MB}MB and {settings.MAX_IMAGE_MB}MB"
)
return image
class AchievementsSerializer(AchievementsBaseSerializer):
class Meta(AchievementsBaseSerializer.Meta):
fields = "__all__"
Note: It is one the example. we have too many like this. In this example i want to get all specialities with the list of doctors and doctor it self contain its achievements, place of works and many other models
Serialisation time is normally not the problem with nested serialisers, but the number of queries being executed is.
You need to pass an optimised query set to your serialised using
select_relatedandprefetch_relatedwhen possible.Start by seeing the number of queries executed for each request, I recommend Django Debug Toolbar