From 00804b361ae9f69271b2e32e8772305a059664aa Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 22 Dec 2025 14:13:41 +0330 Subject: [PATCH] Sync transmitters fixed n+1 and indexing --- ...ransmitteroriginaltext_options_and_more.py | 35 +++++++++++ apps/hadis/models/transmitter.py | 16 +++++ apps/hadis/serializers/hadis.py | 63 +++++++++---------- apps/hadis/views/transmitter.py | 18 +++++- 4 files changed, 96 insertions(+), 36 deletions(-) create mode 100644 apps/hadis/migrations/0066_alter_transmitteroriginaltext_options_and_more.py diff --git a/apps/hadis/migrations/0066_alter_transmitteroriginaltext_options_and_more.py b/apps/hadis/migrations/0066_alter_transmitteroriginaltext_options_and_more.py new file mode 100644 index 0000000..6dcfc78 --- /dev/null +++ b/apps/hadis/migrations/0066_alter_transmitteroriginaltext_options_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.27 on 2025-12-22 14:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0065_hadiscategory_hadis_hadis_parent__e7a217_idx_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="transmitteroriginaltext", + options={ + "verbose_name": "Transmitter Original Text", + "verbose_name_plural": "Transmitter Original Text", + }, + ), + migrations.AddIndex( + model_name="transmitteropinion", + index=models.Index( + fields=["transmitter"], name="hadis_trans_transmi_0f1df2_idx" + ), + ), + migrations.AddIndex( + model_name="transmitteroriginaltext", + index=models.Index( + fields=["transmitter"], name="hadis_trans_transmi_fff93f_idx" + ), + ), + migrations.AddIndex( + model_name="transmitters", + index=models.Index(fields=["id"], name="hadis_trans_id_bd318c_idx"), + ), + ] diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 00c2276..ff66636 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -148,6 +148,10 @@ class Transmitters(models.Model): updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: + indexes = [ + # For ordering in sync API + models.Index(fields=['id']), + ] verbose_name = _('Transmitter') verbose_name_plural = _('Transmitters') ordering = ('full_name',) @@ -303,6 +307,10 @@ class TransmitterOpinion(models.Model): updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: + indexes = [ + # For filtering by transmitter + ordering + models.Index(fields=['transmitter']), + ] verbose_name = _('Transmitter Opinion') verbose_name_plural = _('Transmitter Opinions') ordering = ('-created_at',) @@ -357,6 +365,14 @@ class TransmitterOriginalText(models.Model): translation = models.JSONField(verbose_name=_('translation'), default=list) share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True) + class Meta: + indexes = [ + # For filtering by transmitter + ordering + models.Index(fields=['transmitter']), + ] + verbose_name = _('Transmitter Original Text') + verbose_name_plural = _('Transmitter Original Text') + def __str__(self): return f"{self.title[0]['text']} by {self.transmitter.full_name[0]['text']}" diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index bd23f86..22b8755 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -323,16 +323,10 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): class TransmitterSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all transmitter data for offline mode""" - # Biographical data group (flattened) biographical = serializers.SerializerMethodField() - - # Scholar's opinions group scholars_opinions = serializers.SerializerMethodField() - - # Original texts group original_texts = serializers.SerializerMethodField() - - full_name =LocalizedField() + full_name = LocalizedField() class Meta: model = Transmitters @@ -342,15 +336,16 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): def get_biographical(self, obj): """Get biographical information (flattened)""" - request = self.context.get('request') + request = self.context.get('request') # ← FIX: Define request + return { - 'full_name': get_localized_text(obj.full_name,request), - 'kunya': get_localized_text(obj.kunya,request), - 'known_as':get_localized_text(obj.known_as,request) , - 'nickname':get_localized_text( obj.nickname,request), - 'origin': get_localized_text(obj.origin,request), - 'lived_in':get_localized_text(obj.lived_in,request) , - 'died_in': get_localized_text(obj.died_in,request), + 'full_name': get_localized_text(obj.full_name, request), + 'kunya': get_localized_text(obj.kunya, request), + 'known_as': get_localized_text(obj.known_as, request), + 'nickname': get_localized_text(obj.nickname, request), + 'origin': get_localized_text(obj.origin, request), + 'lived_in': get_localized_text(obj.lived_in, request), + 'died_in': get_localized_text(obj.died_in, request), 'birth_year_hijri': obj.birth_year_hijri, 'death_year_hijri': obj.death_year_hijri, 'age_at_death': obj.age_at_death, @@ -359,38 +354,40 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): 'madhhab': obj.madhhab, 'in_sahih_muslim': obj.in_sahih_muslim, 'in_sahih_bukhari': obj.in_sahih_bukhari, - 'description': get_localized_text(obj.description,request), + 'description': get_localized_text(obj.description, request), 'thumbnail': obj.thumbnail.url if obj.thumbnail else None, } def get_scholars_opinions(self, obj): """Get all scholarly opinions about this transmitter""" - request = self.context.get('request') - opinions = [] - for opinion in obj.opinions.all(): - opinions.append({ + request = self.context.get('request') # ← FIX: Define request + + return [ + { 'id': opinion.id, - 'scholar_name': get_localized_text(opinion.scholar_name,request), - 'opinion_text': get_localized_text(opinion.opinion_text,request), + 'scholar_name': get_localized_text(opinion.scholar_name, request), + 'opinion_text': get_localized_text(opinion.opinion_text, request), 'status': opinion.status, 'created_at': opinion.created_at.isoformat() if opinion.created_at else None, 'updated_at': opinion.updated_at.isoformat() if opinion.updated_at else None, - }) - return opinions + } + for opinion in obj.opinions.all() # Already prefetched + ] def get_original_texts(self, obj): """Get original texts of the transmitter""" - request = self.context.get('request') - texts = [] - for t in obj.originaltexts.all(): - texts.append({ + request = self.context.get('request') # ← FIX: Define request + + return [ + { 'id': t.id, - 'title': get_localized_text(t.title,request), - 'text': get_localized_text(t.text,request), - 'translation': get_localized_text(t.translation,request), + 'title': get_localized_text(t.title, request), + 'text': get_localized_text(t.text, request), + 'translation': get_localized_text(t.translation, request), 'share_link': t.share_link, - }) - return texts + } + for t in obj.originaltexts.all() # Already prefetched + ] diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 114c217..2145e07 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -91,6 +91,7 @@ class TransmitterOriginalTextView(ListAPIView): ) + class TransmitterSyncView(ListAPIView): """ API view to sync all transmitter data for offline mode @@ -104,15 +105,26 @@ class TransmitterSyncView(ListAPIView): return self.list(request, *args, **kwargs) def get_queryset(self): - return Transmitters.objects.prefetch_related('opinions', 'originaltexts').order_by('id') + """ + Prefetch ALL related data at once + """ + return ( + Transmitters.objects + .prefetch_related( + 'opinions', # Already prefetched + 'originaltexts' # Already prefetched + ) + .order_by('id') + ) def list(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True, context={'request': request}) response_data = { - 'count': queryset.count(), + 'count': len(serializer.data), # ← No extra query! 'results': serializer.data } - return Response(response_data) \ No newline at end of file + return Response(response_data) +