diff --git a/apps/hadis/migrations/0067_bookreference_hadis_bookr_id_1b53f6_idx_and_more.py b/apps/hadis/migrations/0067_bookreference_hadis_bookr_id_1b53f6_idx_and_more.py new file mode 100644 index 0000000..0c90fcc --- /dev/null +++ b/apps/hadis/migrations/0067_bookreference_hadis_bookr_id_1b53f6_idx_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.27 on 2025-12-22 14:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0066_alter_transmitteroriginaltext_options_and_more"), + ] + + operations = [ + migrations.AddIndex( + model_name="bookreference", + index=models.Index(fields=["id"], name="hadis_bookr_id_1b53f6_idx"), + ), + migrations.AddIndex( + model_name="hadisreference", + index=models.Index( + fields=["book_reference"], name="hadis_hadis_book_re_3fb4f0_idx" + ), + ), + ] diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index 50755a2..884ce55 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -324,6 +324,10 @@ class HadisReference(models.Model): description = models.JSONField(default = list , verbose_name=_('Description')) class Meta: + indexes = [ + # For fetching hadises by book + models.Index(fields=['book_reference']), + ] verbose_name = _('Hadis Reference') verbose_name_plural = _('Hadis References') # unique_together = ('hadis', 'book_reference') diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index 4ffd12f..b8912c7 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -31,6 +31,9 @@ class BookReference(models.Model): updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) class Meta: + indexes = [ + models.Index(fields=['id']), # Already indexed (PK) + ] verbose_name = _('Book Reference') verbose_name_plural = _('Book References') ordering = ('-created_at',) diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index a5ee461..13a8dcb 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -123,10 +123,10 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): attribute = BookAttributeSerializer( many=True, - read_only = True, - source = 'attributes' + read_only=True, + source='attributes' ) - author= BookAuthorSerializer( + author = BookAuthorSerializer( many=True, read_only=True, source='authors' @@ -142,59 +142,48 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): hadises = serializers.SerializerMethodField() title = LocalizedField() - class Meta: model = BookReference fields = [ - 'id', 'title','rate' , 'author' ,'detail','image','attribute', 'hadises' + 'id', 'title', 'rate', 'author', 'detail', 'image', 'attribute', 'hadises' ] - # def get_authors(self,obj): - # request = self.context.get('request') - # authors = [] - # try: - # for author in obj.authors.all(): - # authors.append({ - # 'id': author.id, - # 'name': get_localized_text(author.name,request) - # }) - # except: - # authors = [] - # return authors - def get_detail(self, obj): - """Get basic book information including authors and rating""" + """Get basic book information""" request = self.context.get('request') return { - 'description': get_localized_text(obj.description,request), + 'description': get_localized_text(obj.description, request), 'volume': obj.volume, - 'language': get_localized_text(obj.language,request), + 'language': get_localized_text(obj.language, request), 'isbn': obj.isbn, - 'number_page':obj.number_page, + 'number_page': obj.number_page, 'year_of_publication': obj.year_of_publication, - 'number_of_pages': obj.number_page, 'volume_info': obj.volume, 'rating': obj.rate } def get_hadises(self, obj): - """Get all hadises related to this book reference""" + """Get all hadises related to this book reference (already prefetched)""" request = self.context.get('request') hadis_list = [] - try: - for hadis_ref in obj.hadis_references.all(): - hadis = hadis_ref.hadis - hadis_list.append({ - 'id': hadis.id, - 'title': get_localized_text(hadis.title,request), - 'title_narrator': get_localized_text(hadis.title_narrator,request), - 'text': get_localized_text(hadis.text,request), - 'translation': get_localized_text(hadis.get_translation,request), - 'share_link': hadis.share_link - }) - except: - pass + # obj.hadis_references.all() is already prefetched! + for hadis_ref in obj.hadis_references.all(): + hadis = hadis_ref.hadis # Already prefetched, no query! + + # Handle get_translation - if it's a method, call it; if field, access it + translation = hadis.get_translation if callable(hadis.get_translation) else hadis.translation + if callable(translation): + translation = translation() + + hadis_list.append({ + 'id': hadis.id, + 'title': get_localized_text(hadis.title, request), + 'title_narrator': get_localized_text(hadis.title_narrator, request), + 'text': get_localized_text(hadis.text, request), + 'translation': get_localized_text(translation, request), + 'share_link': hadis.share_link + }) return hadis_list \ No newline at end of file diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py index 2b7693d..b975476 100644 --- a/apps/hadis/views/reference.py +++ b/apps/hadis/views/reference.py @@ -41,6 +41,8 @@ class BookDetailView(RetrieveAPIView): # ) +from django.db.models import Prefetch + class BookReferenceSyncView(ListAPIView): """ API view to sync all book reference data for offline mode @@ -52,30 +54,22 @@ class BookReferenceSyncView(ListAPIView): @reference_sync_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) - - def get_queryset(self): - return BookReference.objects.prefetch_related( - 'authors', - 'hadis_references__hadis' - ).order_by('id') - - # def list(self, request, *args, **kwargs): - # queryset = self.get_queryset() - # serializer = self.get_serializer(queryset, many=True, context={'request': request}) - - # # Group book references by ID for easy lookup - # grouped_data = {} - # for book_data in serializer.data: - # book_id = str(book_data['id']) - # grouped_data[book_id] = book_data - - # response_data = { - # 'count': queryset.count(), - # 'results': grouped_data - # } - - # return Response(response_data) + def get_queryset(self): + """ + Prefetch ALL related data to avoid N+1 queries + """ + return ( + BookReference.objects + .select_related() # If any ForeignKey fields + .prefetch_related( + 'authors', + 'attributes', # ← ADDED + 'images', # ← ADDED + 'hadis_references__hadis' + ) + .order_by('id') + ) class BookAttributeView(ListCreateAPIView):