From 01ee93b0d07c616dcc2e1f9da17b303ca516dab3 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 15 Dec 2025 16:16:01 +0330 Subject: [PATCH] sync apis updated --- apps/hadis/docs.py | 360 ++++++++++++++-------------- apps/hadis/models/hadis.py | 2 +- apps/hadis/serializers/category.py | 22 +- apps/hadis/serializers/hadis.py | 255 +++++++++++--------- apps/hadis/serializers/reference.py | 41 +--- apps/hadis/views/category.py | 60 +---- apps/hadis/views/hadis.py | 15 +- apps/hadis/views/reference.py | 3 + apps/hadis/views/transmitter.py | 12 +- 9 files changed, 368 insertions(+), 402 deletions(-) diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index f5f1b34..ff29b7f 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -52,99 +52,95 @@ hadis_sect_list_swagger = swagger_auto_schema( # Swagger documentation for HadisCategoryTreeView hadis_category_tree_swagger = swagger_auto_schema( - operation_description="Get complete hierarchical tree structure of Hadis categories grouped by sect type (shia/sunni), with enhanced child information including father category details and hadis information", + operation_description=( + "Get complete hierarchical tree of Hadis categories grouped by sect type (shia/sunni). " + "Categories are not grouped by source_type in the response (mobile filters source_type client-side). " + "This sync endpoint returns only category metadata (no hadis payload) for fast offline building of navigation trees." + ), operation_summary="Get Complete Hadis Category Tree", tags=['Hadis'], responses={ status.HTTP_200_OK: openapi.Response( - description="Complete hierarchical tree structure of categories grouped by sect type with enhanced child information", + description="Complete hierarchical tree grouped by sect with only category metadata (no hadis payloads)", examples={ "application/json": { - "count": 12, + "count": 6, "results": { "shia": { "sects": { "1": { "id": 1, "sect_type": "shia", - "title": "Shi'a Hadith Collections", - "description": "Collections of Shi'a hadith", + "title": "Shi'a Collections", + "description": "Shia roots", "order": 1 } }, - "categories": { - "quran": [ - { - "id": 1, - "name": "Tafsir", - "hadis_count": 150, - "has_hadis": False, - "order": 1, - "xmind_file": "http://example.com/media/xmind/tafsir.xmind", - "has_xmind_file": True, - "children": [ - { - "id": 2, - "name": "Surah Al-Fatiha", - "hadis_count": 25, - "has_hadis": True, - "order": 1, - "father_category": { - "id": 1, - "name": "Tafsir", - "sect_id": 1, - "sect_type": "shia", - "source_type": "quran" - }, - "hadis_details": [ - { - "id": 1, - "title": "The Opening", - "title_narrator": "From Abu Hurairah", - "text": "Actions are but by intention...", - "translation": "Actions are but by intention...", - "share_link": "http://example.com/hadis/1" - } - ], - "children": [] - } - ] - } - ], - "hadith": [] - } + "categories": [ + { + "id": 10, + "name": "Tafsir", + "description": "Quran commentary", + "source_type": "quran", + "hadis_count": 2, + "has_hadis": False, + "hadis_index": [], + "order": 1, + "thumbnail": None, + "xmind_file": None, + "has_xmind_file": False, + "children": [ + { + "id": 11, + "name": "Surah Al-Fatiha", + "description": "Opening chapter", + "source_type": "quran", + "hadis_count": 2, + "has_hadis": True, + "hadis_index": [1, 2], + "order": 1, + "thumbnail": None, + "xmind_file": None, + "has_xmind_file": False, + "father_category": { + "id": 10, + "name": "Tafsir", + "sect_id": 1, + "sect_type": "shia", + "source_type": "quran" + }, + "children": [] + } + ] + } + ] }, "sunni": { "sects": { "2": { "id": 2, "sect_type": "sunni", - "title": "Sunni Hadith Collections", - "description": "Collections of Sunni hadith", + "title": "Sunni Collections", + "description": "Sunni roots", "order": 2 } }, - "categories": { - "hadith": [ - { - "id": 10, - "name": "Sahih al-Bukhari", - "hadis_count": 2500, - "has_hadis": True, - "hadis_details": [ - { - "id": 100, - "title": "The Beginning of Revelation", - "title_narrator": "From Aisha", - "text": "The first revelation...", - "translation": "The first revelation...", - "share_link": "http://example.com/hadis/100" - } - ], - "children": [] - } - ] - } + "categories": [ + { + "id": 20, + "name": "Book of Faith", + "description": "Iman topics", + "source_type": "hadith", + "hadis_count": 1, + "has_hadis": True, + "hadis_index": [50], + "order": 1, + "thumbnail": None, + "xmind_file": None, + "has_xmind_file": False, + "children": [] + } + ] } } } @@ -159,6 +155,126 @@ hadis_category_tree_swagger = swagger_auto_schema( # Swagger documentation for HadisSyncView +hadis_sync_swagger = swagger_auto_schema( + operation_description="Get all Hadis data for offline sync. Returns a dictionary keyed by Hadis ID.", + operation_summary="Sync Hadis Data", + operation_id="syncHadisData", + tags=['Hadis'], + manual_parameters=[ + openapi.Parameter( + 'last_updated', + openapi.IN_QUERY, + description="Timestamp (ISO 8601) to fetch only modified records", + type=openapi.TYPE_STRING, + required=False + ) + ], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Successful Sync Response", + examples={ + "application/json": { + "count": 1, + "results":[ + { + "id": 1001, + "number": 45, + "category_id": 205, + "title": "The Reward of Intentions", + "title_narrator": "Imam Sadiq (as)", + "text": "إنما الأعمال بالنیات...", + "translations": { + "en": "Actions are but by intentions..." + }, + "detail": + {"address": 'null', + "hadis_status": { + "id": 130, + "title": "Прерванный", + "color": "orange" + }, + "status_text": 'null', + "share_link": "https://imamjavad.nwhco.ir/hadis/None", + "links": [ + { + "link": "https://example.com/source1", + "title": "Source 1" + }, + { + "link": "https://example.com/source2", + "title": "Source 2" + } + ], + "tags": [ + { + "id": 520, + "title": "Постановления" + }, + { + "id": 514, + "title": "Терпение" + }, + ], + "references": [ + { + "id": 2193, + "title": 'null', + "authors": [], + "description": 'null' + } + ], + "reference_images": [ + { + "id": 1768, + "thumbnail": "http://127.0.0.1:8000/media/hadis/reference_images/ref_2193.png", + "priority": 0 + } + ] + }, + "narrators": { + "description": 'null', + "transmitters": [ + { + "id": 53, + "name": "Мухаммад ибн аль-Хасан ат-Туси", + "reliability": "unknown", + "layer_level": 'null', + "layer_name": 'null', + "is_gap": 'false', + "birth_year_hijri": 385, + "death_year_hijri": 460, + "order": 1 + }, + { + "id": 60, + "name": "Мухаммад ибн Муслим", + "reliability": "unknown", + "layer_level": 'null', + "layer_name": 'null', + "is_gap": 'false', + "birth_year_hijri": 70, + "death_year_hijri": 150, + "order": 2 + }, + ] + }, + "explanations": "Example explanation...", + "corrections": [ + { + "id":"id", + 'title':'title', + 'description':'description', + 'translation':'translation', + 'share_link':'share_link' + }, + ] + } + ] + } + } + ) + } +) # Swagger documentation for HadisListView hadis_list_swagger = swagger_auto_schema( @@ -633,112 +749,6 @@ hadis_info_swagger = swagger_auto_schema( } ) - -# Swagger documentation for HadisSyncView -hadis_sync_swagger = swagger_auto_schema( - operation_description="Get complete hadis data for offline synchronization including all categories, hadis, and related information", - operation_summary="Sync Hadis Data", - operation_id="syncHadisData", - tags=['Hadis'], - responses={ - status.HTTP_200_OK: openapi.Response( - description="Complete hadis data for synchronization with enhanced information", - examples={ - "application/json": { - "count": 1500, - "results": { - "1": { - "id": 1, - "number": 1, - "category_id": 2, - "title": "The Opening", - "title_narrator": "From Abu Hurairah", - "text": "Actions are but by intention...", - "description": "This hadith emphasizes the importance of intention in all actions...", - "translations": { - "en": "Actions are but by intention...", - "ar": "إنما الأعمال بالنيات...", - "fa": "اعمال به نیت است..." - }, - "explanation": "This hadith emphasizes the importance of intention in all actions...", - "address": "Sahih al-Bukhari, Book of Revelation", - "hadis_status": { - "id": 1, - "title": "Sahih", - "color": "green" - }, - "hadis_status_text": "Authentic", - "share_link": "http://example.com/hadis/1", - "tags": [ - {"id": 1, "title": "Intention"}, - {"id": 2, "title": "Actions"} - ], - "links": { - "audio": "http://example.com/audio/hadis1.mp3", - "video": "http://example.com/video/hadis1.mp4" - }, - "transmitters": [ - { - "id": 1, - "order": 1, - "is_gap": False, - "narrator_layer": "sahaba", - "transmitter": { - "id": 1, - "full_name": "Abu Hurairah", - "birth_year_hijri": 18, - "death_year_hijri": 59, - "madhhab": "sunni", - "description": "One of the most prolific narrators of hadith", - "reliability": "very_reliable" - } - } - ], - "references": [ - { - "id": 1, - "title": "Sahih al-Bukhari", - "images": [ - { - "id": 1, - "image": "http://example.com/media/books/bukhari_cover.jpg", - "order": 1, - "description": "Front cover of Sahih al-Bukhari" - } - ], - "authors": [ - { - "id": 1, - "name": "Muhammad ibn Isma'il al-Bukhari" - } - ], - "description": "The most authentic collection of hadith compiled by Imam Bukhari" - } - ], - "corrections": [ - { - "id": 1, - "title": "Translation Correction", - "description": "Corrected translation for better accuracy", - "translation": { - "en": "Actions are judged by intentions...", - "ar": "إنما الأعمال بالنيات...", - "fa": "اعمال به نیت ها قضاوت می شود..." - } - } - ] - } - } - } - } - ), - status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( - description="Internal server error" - ) - } -) - - # Swagger documentation for TransmitterView transmitter_list_swagger = swagger_auto_schema( operation_description="Get list of transmitters (narrators) with optional filtering by status, madhhab, and generation", diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index 8db81fa..5c13c64 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -180,7 +180,7 @@ class HadisReference(models.Model): return f'{self.hadis.number}-{self.book_reference.title if self.book_reference else "No Book Reference"}' class ReferenceImage(models.Model): - reference = models.ForeignKey(HadisReference, verbose_name="Hadis Reference", on_delete=models.CASCADE) + reference = models.ForeignKey(HadisReference,related_name = 'images', verbose_name="Hadis Reference", on_delete=models.CASCADE) thumbnail = models.ImageField(upload_to='hadis/reference_images/', null=True, blank=True, verbose_name=_('thumbnail')) priority = models.IntegerField( default=0, diff --git a/apps/hadis/serializers/category.py b/apps/hadis/serializers/category.py index 45ca0b4..38fee0e 100644 --- a/apps/hadis/serializers/category.py +++ b/apps/hadis/serializers/category.py @@ -40,16 +40,9 @@ class HadisCategoryTreeSerializer(serializers.ModelSerializer): return obj.get_translation(language_code) if hasattr(obj, 'get_translation') else obj.title def get_children(self, obj): - """Get active children categories that have children or hadis""" + """Get all active children categories (no filtering by hadis/children)""" children = obj.get_children().filter(sect=obj.sect).order_by('order') - # Filter children that have either children or hadis - filtered_children = [] - for child in children: - has_children = child.get_children().filter(sect=obj.sect).exists() - has_hadis = Hadis.objects.filter(category=child, status=True).exists() - if has_children or has_hadis: - filtered_children.append(child) - return [self.to_dict(cat) for cat in filtered_children] + return [self.to_dict(cat) for cat in children] def get_hadis_count(self, obj): """Get total hadis count including children categories""" @@ -110,25 +103,20 @@ class HadisCategoryTreeSerializer(serializers.ModelSerializer): def to_dict(self, c): """Convert category to dictionary""" children = c.get_children().filter(sect=c.sect).order_by('order') - filtered_children = [] - for child in children: - has_children = child.get_children().filter(sect=c.sect).exists() - has_hadis = Hadis.objects.filter(category=child, status=True).exists() - if has_children or has_hadis: - filtered_children.append(child) return { 'id': c.id, 'name': self.get_name(c), 'description': c.description, + 'source_type': c.source_type, 'hadis_count': self.get_hadis_count(c), 'has_hadis': self.get_has_hadis(c), - 'hadis_index': self.get_hadis_index(c) if self.get_has_hadis(c) else [], 'order': c.order, 'thumbnail': self.get_thumbnail(c), 'xmind_file': self.get_xmind_file(c), 'has_xmind_file': self.get_has_xmind_file(c), - 'children': [] if not filtered_children else [self.to_dict(i) for i in filtered_children], + 'children_count': 0 if not children else len([self.to_dict(i) for i in children]), + 'children': [] if not children else [self.to_dict(i) for i in children], } class HadisCategorySelectSerializer(serializers.ModelSerializer): diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 53e0421..bbf1da8 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -1,5 +1,6 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from rest_framework.fields import SerializerMethodField from urllib3 import fields from ..models import ( @@ -27,26 +28,25 @@ class HadisCollectionListSerializer(serializers.ModelSerializer): class HadisSyncSerializer(serializers.ModelSerializer): - """Serializer for syncing all hadis data""" + """Serializer for syncing all hadis data (grouped fields)""" + translations = serializers.SerializerMethodField() - hadis_status = serializers.SerializerMethodField() - tags = serializers.SerializerMethodField() - transmitters = serializers.SerializerMethodField() - description = serializers.SerializerMethodField() - references = serializers.SerializerMethodField() + detail = serializers.SerializerMethodField() + narrators = serializers.SerializerMethodField() + explanations = serializers.SerializerMethodField() corrections = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ - 'id', 'number', 'category_id', 'title', 'title_narrator', - 'text', 'translations', 'explanation', 'address', 'description', - 'hadis_status', 'hadis_status_text', 'share_link', 'tags', 'links', - 'transmitters', 'references', 'corrections' + # header (no-extend) + 'id', 'number', 'category_id', 'title', 'title_narrator', 'text', 'translations', + # grouped sections + 'detail', 'narrators', 'explanations', 'corrections', ] - + def get_translations(self, obj): - """Get all translations""" + """Get all translations as {lang: text}""" translations_dict = {} if obj.translation and isinstance(obj.translation, list): for tr in obj.translation: @@ -56,88 +56,92 @@ class HadisSyncSerializer(serializers.ModelSerializer): if lang_code: translations_dict[lang_code] = title return translations_dict - - def get_hadis_status(self, obj): - """Get hadis status info""" + + def get_detail(self, obj): + """Detail group: address, status, share, links, tags, references, reference_images""" + request = self.context.get('request') + + # status + status_block = None if obj.hadis_status: - return { + status_block = { 'id': obj.hadis_status.id, 'title': obj.hadis_status.title, 'color': obj.hadis_status.color } - return None - - def get_tags(self, obj): - """Get tags""" - return [{'id': tag.id, 'title': tag.title} for tag in obj.tags.all()] - def get_transmitters(self, obj): - """Get transmitters with their details""" + # tags + tags_block = [{'id': tag.id, 'title': tag.title} for tag in obj.tags.all()] + + # references and reference images + references_block = [] + reference_images_block = [] + for reference in obj.references.select_related('book_reference').prefetch_related('book_reference__authors', 'images'): + book = reference.book_reference + references_block.append({ + 'id': reference.id, + 'title': book.title if book else None, + 'authors': [{'id': a.id, 'name': a.name} for a in book.authors.all()] if book else [], + 'description': book.description if book else None, + }) + for img in reference.images.all().order_by('priority'): + thumb_url = None + if img.thumbnail: + thumb_url = request.build_absolute_uri(img.thumbnail.url) if request else img.thumbnail.url + reference_images_block.append({ + 'id': img.id, + 'thumbnail': thumb_url, + 'priority': img.priority, + }) + + return { + 'address': obj.address, + 'hadis_status': status_block, + 'status_text': obj.hadis_status_text, + 'share_link': obj.share_link, + 'links': obj.links, + 'tags': tags_block, + 'references': references_block, + 'reference_images': reference_images_block, + } + + def get_narrators(self, obj): + """Narrators group: description + transmitters""" transmitters_data = [] - for transmitter_rel in obj.transmitters.all().order_by('order'): - transmitter_info = { - 'id': transmitter_rel.id, - 'order': transmitter_rel.order, + for transmitter_rel in obj.transmitters.select_related('transmitter', 'narrator_layer').order_by('order'): + t = transmitter_rel.transmitter + transmitters_data.append({ + 'id': t.id, + 'name': t.full_name, + 'reliability': t.reliability, + 'layer_level': transmitter_rel.narrator_layer.number if transmitter_rel.narrator_layer else None, + 'layer_name': transmitter_rel.narrator_layer.name if transmitter_rel.narrator_layer else None, 'is_gap': transmitter_rel.is_gap, - 'narrator_layer': transmitter_rel.narrator_layer, - 'transmitter': { - 'id': transmitter_rel.transmitter.id, - 'full_name': transmitter_rel.transmitter.full_name, - 'birth_year_hijri': transmitter_rel.transmitter.birth_year_hijri, - 'death_year_hijri': transmitter_rel.transmitter.death_year_hijri, - 'madhhab': transmitter_rel.transmitter.madhhab, - 'description': transmitter_rel.transmitter.description, - 'reliability': transmitter_rel.transmitter.reliability - } - } - transmitters_data.append(transmitter_info) - return transmitters_data + 'birth_year_hijri': t.birth_year_hijri, + 'death_year_hijri': t.death_year_hijri, + 'order': transmitter_rel.order, + }) - def get_description(self, obj): - """Get hadis description""" - return getattr(obj, 'description', None) + return { + 'description': getattr(obj, 'description', None), + 'transmitters': transmitters_data + } - def get_references(self, obj): - """Get references with book information""" - references_data = [] - for reference in obj.references.all(): - try: - book = reference.book_reference - reference_info = { - 'id': reference.id, - 'title': book.title if book else None, - 'images': [ - { - 'id': img.id, - 'image': img.image.url if img.image else None, - 'order': img.order, - 'description': img.description - } for img in book.images.all() if book - ] if book else [], - 'authors': [ - { - 'id': author.id, - 'name': author.name - } for author in book.authors.all() if book - ] if book else [], - 'description': book.description if book else None - } - references_data.append(reference_info) - except: - continue - return references_data + def get_explanations(self, obj): + """Explanations group""" + return obj.explanation def get_corrections(self, obj): - """Get hadis corrections""" + """Corrections group""" corrections_data = [] for correction in obj.hadiscorrection_set.all(): - correction_info = { + corrections_data.append({ 'id': correction.id, 'title': correction.title, 'description': correction.description, - 'translation': correction.translation - } - corrections_data.append(correction_info) + 'translation': correction.translation, + 'share_link': correction.share_link, + }) return corrections_data @@ -232,43 +236,39 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): class TransmitterSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all transmitter data for offline mode""" - # Biographical data group + # Biographical data group (flattened) biographical = serializers.SerializerMethodField() # Scholar's opinions group scholars_opinions = serializers.SerializerMethodField() + # Original texts group + original_texts = serializers.SerializerMethodField() + class Meta: model = Transmitters fields = [ - 'id', 'full_name', 'biographical', 'scholars_opinions' + 'id', 'full_name', 'biographical', 'scholars_opinions', 'original_texts' ] def get_biographical(self, obj): - """Get biographical information""" + """Get biographical information (flattened)""" return { - 'personal_info': { - 'full_name': obj.full_name, - 'kunya': obj.kunya, - 'known_as': obj.known_as, - 'nickname': obj.nickname, - }, - 'dates': { - 'birth_year_hijri': obj.birth_year_hijri, - 'death_year_hijri': obj.death_year_hijri, - 'age_at_death': obj.age_at_death, - }, - 'locations': { - 'origin': obj.origin, - 'lived_in': obj.lived_in, - 'died_in': obj.died_in, - }, - 'religious_profile': { - 'reliability': obj.reliability, - 'madhhab': obj.madhhab, - 'in_sahih_muslim': obj.in_sahih_muslim, - 'in_sahih_bukhari': obj.in_sahih_bukhari, - }, + 'full_name': obj.full_name, + 'kunya': obj.kunya, + 'known_as': obj.known_as, + 'nickname': obj.nickname, + 'origin': obj.origin, + 'lived_in': obj.lived_in, + 'died_in': obj.died_in, + 'birth_year_hijri': obj.birth_year_hijri, + 'death_year_hijri': obj.death_year_hijri, + 'age_at_death': obj.age_at_death, + 'generation': obj.generation, + 'reliability': obj.reliability, + 'madhhab': obj.madhhab, + 'in_sahih_muslim': obj.in_sahih_muslim, + 'in_sahih_bukhari': obj.in_sahih_bukhari, 'description': obj.description, 'thumbnail': obj.thumbnail.url if obj.thumbnail else None, } @@ -285,7 +285,20 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): '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 + return opinions + + def get_original_texts(self, obj): + """Get original texts of the transmitter""" + texts = [] + for t in obj.originaltexts.all(): + texts.append({ + 'id': t.id, + 'title': t.title, + 'text': t.text, + 'translation': t.translation, + 'share_link': t.share_link, + }) + return texts @@ -310,24 +323,23 @@ class ReferenceImageSerializer(serializers.ModelSerializer): def get_thumbnail(self, obj): """Get thumbnail URL""" - if obj.image: + if obj.thumbnail: request = self.context.get('request') if request: - return request.build_absolute_uri(obj.image.url) - return obj.image.url + return request.build_absolute_uri(obj.thumbnail.url) + return obj.thumbnail.url return None class HadisReferenceSerializer(serializers.ModelSerializer): """Serializer for HadisReference with book and images""" book_title = serializers.SerializerMethodField() - book_images = serializers.SerializerMethodField() book_authors = serializers.SerializerMethodField() book_description = serializers.SerializerMethodField() class Meta: model = HadisReference fields = [ - 'id', 'book_title', 'book_images', 'book_authors', 'book_description' + 'id', 'book_title','book_authors', 'book_description' ] def get_book_title(self, obj): @@ -338,13 +350,13 @@ class HadisReferenceSerializer(serializers.ModelSerializer): except: return None - def get_book_images(self, obj): - """Get book images""" - try : - images = obj.book_reference.images.all() - return images - except: - return None + # def get_book_images(self, obj): + # """Get book images""" + # try : + # images = obj.book_reference.images.all() + # return images + # except: + # return None def get_book_authors(self, obj): """Get book authors""" @@ -414,16 +426,25 @@ class HadisDetailSerializer(serializers.ModelSerializer): many=True, read_only=True ) - # category = serializers.SerializerMethodField() + reference_images = SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'number', 'hadis_status_text','hadis_status', 'links','share_link', - 'tags', 'references','address' + 'tags', 'references','reference_images','address' ] + def get_reference_images(self, obj): + """Get all reference images from all references""" + all_images = [] + for reference in obj.references.all(): + images = reference.images.all().order_by('priority') + serializer = ReferenceImageSerializer(images, many=True, context=self.context) + all_images.extend(serializer.data) + return all_images + # def get_category(self, obj): # """Get category details""" # if obj.category: diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index 58cbf38..c8033b6 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -19,17 +19,13 @@ class BookReferenceSerializer(serializers.ModelSerializer): read_only = True , source = 'bookreference_set' ) - volume_count = serializers.SerializerMethodField() author = serializers.SerializerMethodField() class Meta: model = BookReference - fields = ['id','title','rate','author','description','image','volume_count'] + fields = ['id','title','rate','author','description','image','volume'] def get_author (self,obj): author = obj.bookauthor_set return author.name - def get_volume_count(self,obj): - request = self.context.get('request') - return BookReference.objects.filter(title=obj.title).count() class BookAttributeSerializer(serializers.ModelSerializer): class Meta: @@ -53,7 +49,6 @@ class BookDetailSerializer(serializers.ModelSerializer): read_only = True , source = 'bookreference_set' ) - volume_count = serializers.SerializerMethodField() hadis = HadisListSerializer( many=True, @@ -64,9 +59,6 @@ class BookDetailSerializer(serializers.ModelSerializer): class Meta: model = BookReference fields = '__all__' - def get_volume_count(self,obj): - request = self.context.get('request') - return BookReference.objects.filter(title=obj.title).count() # def create(self , validated_data): # author = validated_data.pop('author') @@ -78,22 +70,19 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all book reference data for offline mode""" # Basic information - basic_info = serializers.SerializerMethodField() - - # Information group (detailed publication info) - information = serializers.SerializerMethodField() + detail = serializers.SerializerMethodField() # Hadis group (related hadises) - hadis = serializers.SerializerMethodField() + hadises = serializers.SerializerMethodField() + + authors = serializers.SerializerMethodField() class Meta: model = BookReference fields = [ - 'id', 'title', 'basic_info', 'information', 'hadis' + 'id', 'title','rate' , 'authors' ,'detail', 'hadises' ] - - def get_basic_info(self, obj): - """Get basic book information including authors and rating""" + def get_authors(self,obj): authors = [] try: for author in obj.authors.all(): @@ -103,18 +92,14 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): }) except: authors = [] + return authors - return { - 'title': obj.title, - 'authors': authors, - 'rating': obj.rate, - 'description': obj.description, - 'volume': obj.volume - } - def get_information(self, obj): - """Get detailed publication information""" + def get_detail(self, obj): + """Get basic book information including authors and rating""" return { + 'description': obj.description, + 'volume': obj.volume, 'language': obj.language, 'isbn': obj.isbn, 'year_of_publication': obj.year_of_publication, @@ -123,7 +108,7 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): 'rating': obj.rate } - def get_hadis(self, obj): + def get_hadises(self, obj): """Get all hadises related to this book reference""" hadis_list = [] try: diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index 458a4a6..0a30cc4 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -60,8 +60,8 @@ class HadisCategorySectListView(ListAPIView): class HadisCategoryTreeView(ListAPIView): """ - API view to get all HadisCategory tree structure grouped by sect and source_type - Returns all categories in a tree structure + API view to get all HadisCategory tree structure grouped by sect + Returns all categories in a tree structure (source_type grouping removed for mobile filtering) """ serializer_class = HadisCategoryTreeSerializer pagination_class = NoPagination @@ -74,6 +74,9 @@ class HadisCategoryTreeView(ListAPIView): return HadisCategory.objects.filter( parent__isnull=True, sect__is_active=True + ).select_related('sect').prefetch_related( + 'children', + 'children__children' ).order_by('sect__order', 'order') def list(self, request, *args, **kwargs): @@ -91,7 +94,7 @@ class HadisCategoryTreeView(ListAPIView): # ایجاد گروه برای هر sect_type grouped_data[sect_type] = { 'sects': {}, - 'categories': {} + 'categories': [] } # اضافه کردن اطلاعات sect به گروه sect_type @@ -105,12 +108,8 @@ class HadisCategoryTreeView(ListAPIView): 'order': category.sect.order } - # گروه‌بندی categories بر اساس source_type - if category.source_type not in grouped_data[sect_type]['categories']: - grouped_data[sect_type]['categories'][category.source_type] = [] - category_data = self.build_enhanced_category_tree(category, serializer_instance) - grouped_data[sect_type]['categories'][category.source_type].append(category_data) + grouped_data[sect_type]['categories'].append(category_data) def count_children(children_list): count = 0 @@ -122,11 +121,10 @@ class HadisCategoryTreeView(ListAPIView): total_count = 0 for sect_type_data in grouped_data.values(): - for source_categories in sect_type_data['categories'].values(): - for item in source_categories: - total_count += 1 - if 'children' in item and item['children']: - total_count += count_children(item['children']) + for item in sect_type_data['categories']: + total_count += 1 + if 'children' in item and item['children']: + total_count += count_children(item['children']) response_data = { 'count': total_count, @@ -149,8 +147,7 @@ class HadisCategoryTreeView(ListAPIView): return base_data def enhance_child_data(self, child_data, parent_category, serializer_instance): - """Enhance child data with father category info and hadis details""" - from ..models import Hadis + """Enhance child data with father category info (no hadis payload for sync tree)""" # Add father category information child_data['father_category'] = { @@ -161,38 +158,7 @@ class HadisCategoryTreeView(ListAPIView): 'source_type': parent_category.source_type } - # If this child has no children but has hadis, add hadis details - if not child_data.get('children', []) and child_data.get('has_hadis', False): - try: - # Get the category object - from ..models import HadisCategory - child_category = HadisCategory.objects.get(id=child_data['id']) - - # Get hadis for this category - hadis_list = Hadis.objects.filter( - category=child_category, - status=True - ).order_by('number') - - hadis_details = [] - for hadis in hadis_list: - hadis_detail = { - 'id': hadis.id, - 'title': hadis.title, - 'title_narrator': hadis.title_narrator, - 'text': hadis.text, - 'translation': hadis.get_translation( - self.request.LANGUAGE_CODE if hasattr(self, 'request') and self.request else 'en' - ), - 'share_link': hadis.share_link - } - hadis_details.append(hadis_detail) - - child_data['hadis_details'] = hadis_details - - except Exception as e: - # If there's any error, just continue without hadis details - pass + # Note: we intentionally DO NOT load or attach hadis details here for performance. # Recursively enhance children's children if child_data.get('children', []): diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 41a1e64..3535ee6 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -28,8 +28,6 @@ class HadisSyncView(ListAPIView): serializer_class = HadisSyncSerializer pagination_class = NoPagination - @hadis_sync_swagger - def get_queryset(self): return Hadis.objects.filter(status=True).select_related( 'category', 'hadis_status' @@ -41,18 +39,17 @@ class HadisSyncView(ListAPIView): 'hadiscorrection_set' ).order_by('id') + @hadis_sync_swagger + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + def list(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True) - - grouped_data = {} - for hadis_data in serializer.data: - hadis_id = str(hadis_data['id']) - grouped_data[hadis_id] = hadis_data - + response_data = { 'count': queryset.count(), - 'results': grouped_data + 'results': serializer.data } return Response(response_data) diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py index f97d02d..107e918 100644 --- a/apps/hadis/views/reference.py +++ b/apps/hadis/views/reference.py @@ -49,6 +49,9 @@ class BookReferenceSyncView(ListAPIView): pagination_class = NoPagination @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', diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 54a6127..4b85ed0 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -83,23 +83,19 @@ class TransmitterSyncView(ListAPIView): pagination_class = NoPagination @transmitter_sync_swagger + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) def get_queryset(self): - return Transmitters.objects.prefetch_related('opinions').order_by('id') + return Transmitters.objects.prefetch_related('opinions', 'originaltexts').order_by('id') def list(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True, context={'request': request}) - # Group transmitters by ID for easy lookup - grouped_data = {} - for transmitter_data in serializer.data: - transmitter_id = str(transmitter_data['id']) - grouped_data[transmitter_id] = transmitter_data - response_data = { 'count': queryset.count(), - 'results': grouped_data + 'results': serializer.data } return Response(response_data) \ No newline at end of file