From f66003a45a48e24b215a98a28a0e11cd7695f83a Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Sat, 13 Dec 2025 15:18:19 +0330 Subject: [PATCH] hadis detail and sync apis --- apps/hadis/docs.py | 365 +++++++--- ..._remove_hadistransmitter_narrator_layer.py | 16 + .../0015_hadistransmitter_narrator_layer.py | 26 + ...adistransmitter_narrator_layer_and_more.py | 19 + ...orlayer_hadistransmitter_narrator_layer.py | 64 ++ .../0018_remove_hadistransmitter_status.py | 16 + .../0019_hadistransmitter_status.py | 27 + .../0020_hadisreference_description.py | 17 + ..._hadisreference_book_reference_and_more.py | 20 + ..._hadisreference_book_reference_and_more.py | 30 + ..._hadisreference_book_reference_and_more.py | 29 + .../0024_remove_hadisreference_book.py | 16 + .../0025_hadisreference_book_reference.py | 25 + ...26_remove_hadisreference_book_reference.py | 16 + .../0027_hadisreference_book_reference.py | 25 + .../0028_hadistransmitter_description.py | 17 + ...e_hadistransmitter_description_and_more.py | 21 + .../0030_remove_hadistransmitter_is_gap.py | 16 + .../0031_hadistransmitter_is_gap.py | 17 + .../0032_remove_hadis_a_hadis_description.py | 21 + apps/hadis/migrations/0033_hadiscorrection.py | 57 ++ apps/hadis/models/hadis.py | 25 +- apps/hadis/models/transmitter.py | 3 +- apps/hadis/serializers/hadis.py | 216 +++++- apps/hadis/serializers/reference.py | 73 +- apps/hadis/urls.py | 12 +- apps/hadis/views/category.py | 128 +++- apps/hadis/views/hadis.py | 18 +- apps/hadis/views/reference.py | 38 +- apps/hadis/views/transmitter.py | 38 +- docs/SyncHadis.md | 635 ++++++++++++++++++ docs/hadisdetail.md | 130 ++++ 32 files changed, 2032 insertions(+), 144 deletions(-) create mode 100644 apps/hadis/migrations/0014_remove_hadistransmitter_narrator_layer.py create mode 100644 apps/hadis/migrations/0015_hadistransmitter_narrator_layer.py create mode 100644 apps/hadis/migrations/0016_remove_hadistransmitter_narrator_layer_and_more.py create mode 100644 apps/hadis/migrations/0017_narratorlayer_hadistransmitter_narrator_layer.py create mode 100644 apps/hadis/migrations/0018_remove_hadistransmitter_status.py create mode 100644 apps/hadis/migrations/0019_hadistransmitter_status.py create mode 100644 apps/hadis/migrations/0020_hadisreference_description.py create mode 100644 apps/hadis/migrations/0021_remove_hadisreference_book_reference_and_more.py create mode 100644 apps/hadis/migrations/0022_hadisreference_book_reference_and_more.py create mode 100644 apps/hadis/migrations/0023_remove_hadisreference_book_reference_and_more.py create mode 100644 apps/hadis/migrations/0024_remove_hadisreference_book.py create mode 100644 apps/hadis/migrations/0025_hadisreference_book_reference.py create mode 100644 apps/hadis/migrations/0026_remove_hadisreference_book_reference.py create mode 100644 apps/hadis/migrations/0027_hadisreference_book_reference.py create mode 100644 apps/hadis/migrations/0028_hadistransmitter_description.py create mode 100644 apps/hadis/migrations/0029_remove_hadistransmitter_description_and_more.py create mode 100644 apps/hadis/migrations/0030_remove_hadistransmitter_is_gap.py create mode 100644 apps/hadis/migrations/0031_hadistransmitter_is_gap.py create mode 100644 apps/hadis/migrations/0032_remove_hadis_a_hadis_description.py create mode 100644 apps/hadis/migrations/0033_hadiscorrection.py create mode 100644 docs/SyncHadis.md create mode 100644 docs/hadisdetail.md diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index ab6b600..2d0dff8 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -52,104 +52,104 @@ hadis_sect_list_swagger = swagger_auto_schema( # Swagger documentation for HadisCategoryTreeView hadis_category_tree_swagger = swagger_auto_schema( - operation_description="Get hierarchical tree structure of Hadis categories for a specific sect, grouped by source type (Quran/Hadith)", - operation_summary="Get Hadis Category Tree by Sect", + 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_summary="Get Complete Hadis Category Tree", tags=['Hadis'], - manual_parameters=[ - openapi.Parameter( - 'sect_id', - openapi.IN_PATH, - description="ID of the Hadis sect", - type=openapi.TYPE_INTEGER, - required=True - ) - ], responses={ status.HTTP_200_OK: openapi.Response( - description="Hierarchical tree structure of categories with total count", + description="Complete hierarchical tree structure of categories grouped by sect type with enhanced child information", examples={ "application/json": { - "count": 6, + "count": 12, "results": { - "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_count": 2, - "children": [ + "shia": { + "sects": { + "1": { + "id": 1, + "sect_type": "shia", + "title": "Shi'a Hadith Collections", + "description": "Collections of Shi'a hadith", + "order": 1 + } + }, + "categories": { + "quran": [ { - "id": 2, - "name": "Surah Al-Fatiha", - "hadis_count": 25, - "has_hadis": True, + "id": 1, + "name": "Tafsir", + "hadis_count": 150, + "has_hadis": False, "order": 1, - "xmind_file": None, - "has_xmind_file": False, - "children_count": 0, - "children": [] - }, - { - "id": 3, - "name": "Surah Al-Baqarah", - "hadis_count": 125, - "has_hadis": True, - "order": 2, - "xmind_file": "http://example.com/media/xmind/baqarah.xmind", + "xmind_file": "http://example.com/media/xmind/tafsir.xmind", "has_xmind_file": True, - "children_count": 0, - "children": [] + "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": [] } - ], - "hadith": [ - { - "id": 4, - "name": "Sahih Bukhari", - "hadis_count": 300, - "has_hadis": False, - "order": 1, - "xmind_file": "http://example.com/media/xmind/bukhari.xmind", - "has_xmind_file": True, - "children_count": 2, - "children": [ - { - "id": 5, - "name": "Book of Faith", - "hadis_count": 50, - "has_hadis": True, - "order": 1, - "xmind_file": None, - "has_xmind_file": False, - "children_count": 0, - "children": [] - }, + }, + "sunni": { + "sects": { + "2": { + "id": 2, + "sect_type": "sunni", + "title": "Sunni Hadith Collections", + "description": "Collections of Sunni hadith", + "order": 2 + } + }, + "categories": { + "hadith": [ { - "id": 6, - "name": "Book of Prayer", - "hadis_count": 250, + "id": 10, + "name": "Sahih al-Bukhari", + "hadis_count": 2500, "has_hadis": True, - "order": 2, - "xmind_file": "http://example.com/media/xmind/prayer.xmind", - "has_xmind_file": True, - "children_count": 0, + "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": [] } ] } - ] + } } } } ), - status.HTTP_404_NOT_FOUND: openapi.Response( - description="Sect not found" - ), status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( description="Internal server error" ) @@ -157,6 +157,9 @@ hadis_category_tree_swagger = swagger_auto_schema( ) +# Swagger documentation for HadisSyncView + + # Swagger documentation for HadisListView hadis_list_swagger = swagger_auto_schema( operation_description="Get paginated list of Hadis for a specific category with translations based on request language", @@ -408,13 +411,93 @@ hadis_sync_swagger = swagger_auto_schema( tags=['Hadis'], responses={ status.HTTP_200_OK: openapi.Response( - description="Complete hadis data for synchronization", + description="Complete hadis data for synchronization with enhanced information", examples={ "application/json": { - "categories": [], - "hadis": [], - "references": [], - "transmitters": [] + "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": "اعمال به نیت ها قضاوت می شود..." + } + } + ] + } + } } } ), @@ -559,6 +642,68 @@ transmitter_detail_swagger = swagger_auto_schema( } ) +transmitter_sync_swagger = swagger_auto_schema( + operation_description="Get complete transmitter (narrator) data for offline synchronization including biographical information and scholarly opinions", + operation_summary="Sync Transmitter Data", + operation_id="syncTransmitterData", + tags=['Hadis'], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Complete transmitter data for synchronization", + examples={ + "application/json": { + "count": 150, + "results": { + "1": { + "id": 1, + "full_name": "Abu Daud Sulaiman ibn al-Ash'ath al-Azdi al-Sijistani", + "biographical": { + "personal_info": { + "full_name": "Abu Daud Sulaiman ibn al-Ash'ath al-Azdi al-Sijistani", + "kunya": "Abu Daud", + "known_as": "Imam Abu Daud", + "nickname": "Al-Sijistani" + }, + "dates": { + "birth_year_hijri": 202, + "death_year_hijri": 275, + "age_at_death": 73 + }, + "locations": { + "origin": "Sijistan (modern Sistan)", + "lived_in": "Basra, Baghdad", + "died_in": "Basra" + }, + "religious_profile": { + "reliability": "very_reliable", + "madhhab": "shafii", + "in_sahih_muslim": True, + "in_sahih_bukhari": False + }, + "description": "One of the six canonical hadith collectors...", + "thumbnail": "http://example.com/media/transmitters/abu_daud.jpg" + }, + "scholars_opinions": [ + { + "id": 1, + "scholar_name": "Imam al-Nawawi", + "opinion_text": "Abu Daud is reliable and trustworthy...", + "status": "confirmed", + "created_at": "2025-12-13T10:00:00Z", + "updated_at": "2025-12-13T10:00:00Z" + } + ] + } + } + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error" + ) + } +) + # Swagger documentation for BookReferencesView book_references_list_swagger = swagger_auto_schema( @@ -603,6 +748,62 @@ book_references_list_swagger = swagger_auto_schema( } ) +reference_sync_swagger = swagger_auto_schema( + operation_description="Get complete book reference data for offline synchronization including basic information, detailed publication info, and related hadises", + operation_summary="Sync Book References Data", + operation_id="syncReferenceData", + tags=['Hadis'], + responses={ + status.HTTP_200_OK: openapi.Response( + description="Complete book references data for synchronization", + examples={ + "application/json": { + "count": 50, + "results": { + "1": { + "id": 1, + "title": "Sahih al-Bukhari", + "basic_info": { + "title": "Sahih al-Bukhari", + "authors": [ + { + "id": 1, + "name": "Muhammad ibn Isma'il al-Bukhari" + } + ], + "rating": 5.0, + "description": "The most authentic collection of hadith compiled by Imam Bukhari", + "volume": "9 volumes" + }, + "information": { + "language": "Arabic", + "isbn": "978-1234567890", + "year_of_publication": "846", + "number_of_pages": 4200, + "volume_info": "9 volumes", + "rating": 5.0 + }, + "hadis": [ + { + "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" + } + ] + } + } + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error" + ) + } +) + # Swagger documentation for BookAuthorView book_authors_list_swagger = swagger_auto_schema( diff --git a/apps/hadis/migrations/0014_remove_hadistransmitter_narrator_layer.py b/apps/hadis/migrations/0014_remove_hadistransmitter_narrator_layer.py new file mode 100644 index 0000000..5262724 --- /dev/null +++ b/apps/hadis/migrations/0014_remove_hadistransmitter_narrator_layer.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-13 08:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0013_hadis_title_narrator"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="narrator_layer", + ), + ] diff --git a/apps/hadis/migrations/0015_hadistransmitter_narrator_layer.py b/apps/hadis/migrations/0015_hadistransmitter_narrator_layer.py new file mode 100644 index 0000000..2e955e2 --- /dev/null +++ b/apps/hadis/migrations/0015_hadistransmitter_narrator_layer.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.9 on 2025-12-13 08:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0014_remove_hadistransmitter_narrator_layer"), + ] + + operations = [ + migrations.AddField( + model_name="hadistransmitter", + name="narrator_layer", + field=models.ForeignKey( + blank=True, + help_text="The layer/class (Tabaqah) this narrator belongs to", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="transmitters", + to="hadis.narratorlayer", + verbose_name="narrator layer", + ), + ), + ] diff --git a/apps/hadis/migrations/0016_remove_hadistransmitter_narrator_layer_and_more.py b/apps/hadis/migrations/0016_remove_hadistransmitter_narrator_layer_and_more.py new file mode 100644 index 0000000..79cc759 --- /dev/null +++ b/apps/hadis/migrations/0016_remove_hadistransmitter_narrator_layer_and_more.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2025-12-13 08:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0015_hadistransmitter_narrator_layer"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="narrator_layer", + ), + migrations.DeleteModel( + name="NarratorLayer", + ), + ] diff --git a/apps/hadis/migrations/0017_narratorlayer_hadistransmitter_narrator_layer.py b/apps/hadis/migrations/0017_narratorlayer_hadistransmitter_narrator_layer.py new file mode 100644 index 0000000..19eeee9 --- /dev/null +++ b/apps/hadis/migrations/0017_narratorlayer_hadistransmitter_narrator_layer.py @@ -0,0 +1,64 @@ +# Generated by Django 5.2.9 on 2025-12-13 08:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0016_remove_hadistransmitter_narrator_layer_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="NarratorLayer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name")), + ( + "number", + models.PositiveIntegerField( + unique=True, verbose_name="layer number" + ), + ), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="description"), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="updated at"), + ), + ], + options={ + "verbose_name": "Narrator Layer", + "verbose_name_plural": "Narrator Layers", + "ordering": ["number"], + }, + ), + migrations.AddField( + model_name="hadistransmitter", + name="narrator_layer", + field=models.ForeignKey( + blank=True, + help_text="The layer/class (Tabaqah) this narrator belongs to", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="transmitters", + to="hadis.narratorlayer", + verbose_name="narrator layer", + ), + ), + ] diff --git a/apps/hadis/migrations/0018_remove_hadistransmitter_status.py b/apps/hadis/migrations/0018_remove_hadistransmitter_status.py new file mode 100644 index 0000000..622e121 --- /dev/null +++ b/apps/hadis/migrations/0018_remove_hadistransmitter_status.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-13 09:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0017_narratorlayer_hadistransmitter_narrator_layer"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="status", + ), + ] diff --git a/apps/hadis/migrations/0019_hadistransmitter_status.py b/apps/hadis/migrations/0019_hadistransmitter_status.py new file mode 100644 index 0000000..6d2cf04 --- /dev/null +++ b/apps/hadis/migrations/0019_hadistransmitter_status.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.9 on 2025-12-13 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0018_remove_hadistransmitter_status"), + ] + + operations = [ + migrations.AddField( + model_name="hadistransmitter", + name="status", + field=models.CharField( + choices=[ + ("reliable", "Reliable"), + ("weak", "Weak"), + ("unknown", "Unknown"), + ], + default="unknown", + help_text="Reliability status of the narrator", + max_length=20, + verbose_name="reliability status", + ), + ), + ] diff --git a/apps/hadis/migrations/0020_hadisreference_description.py b/apps/hadis/migrations/0020_hadisreference_description.py new file mode 100644 index 0000000..1dd2842 --- /dev/null +++ b/apps/hadis/migrations/0020_hadisreference_description.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.9 on 2025-12-13 09:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0019_hadistransmitter_status"), + ] + + operations = [ + migrations.AddField( + model_name="hadisreference", + name="description", + field=models.TextField(blank=True, null=True, verbose_name="description"), + ), + ] diff --git a/apps/hadis/migrations/0021_remove_hadisreference_book_reference_and_more.py b/apps/hadis/migrations/0021_remove_hadisreference_book_reference_and_more.py new file mode 100644 index 0000000..72df463 --- /dev/null +++ b/apps/hadis/migrations/0021_remove_hadisreference_book_reference_and_more.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.9 on 2025-12-13 09:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0020_hadisreference_description"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadisreference", + name="book_reference", + ), + migrations.RemoveField( + model_name="hadisreference", + name="description", + ), + ] diff --git a/apps/hadis/migrations/0022_hadisreference_book_reference_and_more.py b/apps/hadis/migrations/0022_hadisreference_book_reference_and_more.py new file mode 100644 index 0000000..b08c1d9 --- /dev/null +++ b/apps/hadis/migrations/0022_hadisreference_book_reference_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.9 on 2025-12-13 09:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0021_remove_hadisreference_book_reference_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="hadisreference", + name="book_reference", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_references", + to="hadis.bookreference", + verbose_name="book reference", + ), + ), + migrations.AddField( + model_name="hadisreference", + name="description", + field=models.TextField(blank=True, null=True, verbose_name="description"), + ), + ] diff --git a/apps/hadis/migrations/0023_remove_hadisreference_book_reference_and_more.py b/apps/hadis/migrations/0023_remove_hadisreference_book_reference_and_more.py new file mode 100644 index 0000000..21fa282 --- /dev/null +++ b/apps/hadis/migrations/0023_remove_hadisreference_book_reference_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0022_hadisreference_book_reference_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadisreference", + name="book_reference", + ), + migrations.AddField( + model_name="hadisreference", + name="book", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_references", + to="hadis.bookreference", + verbose_name="book reference", + ), + ), + ] diff --git a/apps/hadis/migrations/0024_remove_hadisreference_book.py b/apps/hadis/migrations/0024_remove_hadisreference_book.py new file mode 100644 index 0000000..82716e5 --- /dev/null +++ b/apps/hadis/migrations/0024_remove_hadisreference_book.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0023_remove_hadisreference_book_reference_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadisreference", + name="book", + ), + ] diff --git a/apps/hadis/migrations/0025_hadisreference_book_reference.py b/apps/hadis/migrations/0025_hadisreference_book_reference.py new file mode 100644 index 0000000..c8e4844 --- /dev/null +++ b/apps/hadis/migrations/0025_hadisreference_book_reference.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:02 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0024_remove_hadisreference_book"), + ] + + operations = [ + migrations.AddField( + model_name="hadisreference", + name="book_reference", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_references", + to="hadis.bookreference", + verbose_name="book reference", + ), + ), + ] diff --git a/apps/hadis/migrations/0026_remove_hadisreference_book_reference.py b/apps/hadis/migrations/0026_remove_hadisreference_book_reference.py new file mode 100644 index 0000000..cdb9c5e --- /dev/null +++ b/apps/hadis/migrations/0026_remove_hadisreference_book_reference.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0025_hadisreference_book_reference"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadisreference", + name="book_reference", + ), + ] diff --git a/apps/hadis/migrations/0027_hadisreference_book_reference.py b/apps/hadis/migrations/0027_hadisreference_book_reference.py new file mode 100644 index 0000000..c017d46 --- /dev/null +++ b/apps/hadis/migrations/0027_hadisreference_book_reference.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:05 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0026_remove_hadisreference_book_reference"), + ] + + operations = [ + migrations.AddField( + model_name="hadisreference", + name="book_reference", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_references", + to="hadis.bookreference", + verbose_name="book reference", + ), + ), + ] diff --git a/apps/hadis/migrations/0028_hadistransmitter_description.py b/apps/hadis/migrations/0028_hadistransmitter_description.py new file mode 100644 index 0000000..236aeed --- /dev/null +++ b/apps/hadis/migrations/0028_hadistransmitter_description.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0027_hadisreference_book_reference"), + ] + + operations = [ + migrations.AddField( + model_name="hadistransmitter", + name="description", + field=models.TextField(blank=True, null=True, verbose_name="description"), + ), + ] diff --git a/apps/hadis/migrations/0029_remove_hadistransmitter_description_and_more.py b/apps/hadis/migrations/0029_remove_hadistransmitter_description_and_more.py new file mode 100644 index 0000000..b0ab814 --- /dev/null +++ b/apps/hadis/migrations/0029_remove_hadistransmitter_description_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0028_hadistransmitter_description"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="description", + ), + migrations.AddField( + model_name="hadistransmitter", + name="is_gap", + field=models.BooleanField(default=False, verbose_name="is gap"), + ), + ] diff --git a/apps/hadis/migrations/0030_remove_hadistransmitter_is_gap.py b/apps/hadis/migrations/0030_remove_hadistransmitter_is_gap.py new file mode 100644 index 0000000..7b05eba --- /dev/null +++ b/apps/hadis/migrations/0030_remove_hadistransmitter_is_gap.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0029_remove_hadistransmitter_description_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="is_gap", + ), + ] diff --git a/apps/hadis/migrations/0031_hadistransmitter_is_gap.py b/apps/hadis/migrations/0031_hadistransmitter_is_gap.py new file mode 100644 index 0000000..77a7f46 --- /dev/null +++ b/apps/hadis/migrations/0031_hadistransmitter_is_gap.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0030_remove_hadistransmitter_is_gap"), + ] + + operations = [ + migrations.AddField( + model_name="hadistransmitter", + name="is_gap", + field=models.BooleanField(default=False, verbose_name="is gap"), + ), + ] diff --git a/apps/hadis/migrations/0032_remove_hadis_a_hadis_description.py b/apps/hadis/migrations/0032_remove_hadis_a_hadis_description.py new file mode 100644 index 0000000..4cd1086 --- /dev/null +++ b/apps/hadis/migrations/0032_remove_hadis_a_hadis_description.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.9 on 2025-12-13 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0031_hadistransmitter_is_gap"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadis", + name="a", + ), + migrations.AddField( + model_name="hadis", + name="description", + field=models.TextField(blank=True, null=True, verbose_name="description"), + ), + ] diff --git a/apps/hadis/migrations/0033_hadiscorrection.py b/apps/hadis/migrations/0033_hadiscorrection.py new file mode 100644 index 0000000..84df46f --- /dev/null +++ b/apps/hadis/migrations/0033_hadiscorrection.py @@ -0,0 +1,57 @@ +# Generated by Django 5.2.9 on 2025-12-13 11:39 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0032_remove_hadis_a_hadis_description"), + ] + + operations = [ + migrations.CreateModel( + name="HadisCorrection", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="description"), + ), + ( + "translation", + models.JSONField(default=list, verbose_name="translation"), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="updated at"), + ), + ( + "hadis", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="hadis.hadis", + verbose_name="hadis correction", + ), + ), + ], + options={ + "verbose_name": "Hadis Correction", + "verbose_name_plural": "Hadis Corrections", + "ordering": ("-created_at",), + }, + ), + ] diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index cf2d27c..e7cebf5 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -1,9 +1,10 @@ from django.db import models -from django.db.models import F +from django.db.models import F, ForeignKey from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.utils.text import slugify from filer.fields.image import FilerImageField +from .reference import BookReference class HadisCollection(models.Model): @@ -100,7 +101,7 @@ class Hadis(models.Model): number = models.PositiveIntegerField(verbose_name=_('number'), default=1) title_narrator = models.CharField(max_length=255, verbose_name=_('title narrator'), null=True, blank=True) title = models.CharField(max_length=255, verbose_name=_('title'), null=True, blank=True) - a =models.IntegerField(blank=True ,null=True) + description = models.TextField(verbose_name=_('description'), blank=True, null=True) text = models.TextField(verbose_name=_('text')) translation = models.JSONField(verbose_name=_('translation'), default=list) @@ -158,15 +159,18 @@ class HadisReference(models.Model): related_name='references' ) book_reference = models.ForeignKey( - "hadis.BookReference", + BookReference, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('book reference'), related_name='hadis_references' ) + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) + description = models.TextField(verbose_name=_('description'), null=True, blank=True) + class Meta: verbose_name = _('Hadis Reference') verbose_name_plural = _('Hadis References') @@ -201,3 +205,18 @@ class ReferenceImage(models.Model): super().save(*args, **kwargs) +class HadisCorrection(models.Model): + hadis = models.ForeignKey(Hadis, verbose_name=_("hadis correction"), on_delete=models.CASCADE) + title = models.CharField(max_length=255, verbose_name=_("title")) + description = models.TextField(verbose_name=_("description"), blank=True, null=True) + translation = models.JSONField(verbose_name=_("translation"), default=list) + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("created at")) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_("updated at")) + + class Meta: + verbose_name = _("Hadis Correction") + verbose_name_plural = _("Hadis Corrections") + ordering = ("-created_at",) + + def __str__(self): + return f"{self.hadis.number} - {self.title}" diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 693e1dd..333ad4a 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -149,9 +149,10 @@ class HadisTransmitter(models.Model): verbose_name=_('Order'), help_text=_('Order in the chain of transmission') ) - + is_gap = models.BooleanField(default=False, verbose_name=_('is gap')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) + class Meta: verbose_name = _('Hadis Transmitter') verbose_name_plural = _('Hadis Transmitters') diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 3fe5813..34c10be 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -5,7 +5,7 @@ from urllib3 import fields from ..models import ( Hadis, HadisStatus, HadisTag, HadisTransmitter, HadisReference, ReferenceImage, Transmitters, HadisCollection, - TransmitterOpinion , + TransmitterOpinion, BookReference, BookReferenceImage, BookAuthor, HadisCorrection ) from apps.library.serializers import BookSerializer @@ -31,13 +31,18 @@ class HadisSyncSerializer(serializers.ModelSerializer): translations = serializers.SerializerMethodField() hadis_status = serializers.SerializerMethodField() tags = serializers.SerializerMethodField() - + transmitters = serializers.SerializerMethodField() + description = serializers.SerializerMethodField() + references = serializers.SerializerMethodField() + corrections = serializers.SerializerMethodField() + class Meta: model = Hadis fields = [ 'id', 'number', 'category_id', 'title', 'title_narrator', - 'text', 'translations', 'explanation', 'address', - 'hadis_status', 'hadis_status_text', 'share_link', 'tags', 'links' + 'text', 'translations', 'explanation', 'address', 'description', + 'hadis_status', 'hadis_status_text', 'share_link', 'tags', 'links', + 'transmitters', 'references', 'corrections' ] def get_translations(self, obj): @@ -66,6 +71,75 @@ class HadisSyncSerializer(serializers.ModelSerializer): """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""" + transmitters_data = [] + for transmitter_rel in obj.transmitters.all().order_by('order'): + transmitter_info = { + 'id': transmitter_rel.id, + 'order': transmitter_rel.order, + '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 + + def get_description(self, obj): + """Get hadis description""" + return getattr(obj, 'description', None) + + 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_corrections(self, obj): + """Get hadis corrections""" + corrections_data = [] + for correction in obj.hadiscorrection_set.all(): + correction_info = { + 'id': correction.id, + 'title': correction.title, + 'description': correction.description, + 'translation': correction.translation + } + corrections_data.append(correction_info) + return corrections_data + class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" @@ -152,7 +226,66 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): 'death_year_hijri','age_at_death','reliability', 'madhhab',"in_sahih_muslim","in_sahih_bukhari","description", "thumbnail",'opinions','hadis_transmissions' - ] + ] + + +class TransmitterSyncSerializer(serializers.ModelSerializer): + """Serializer for syncing all transmitter data for offline mode""" + + # Biographical data group + biographical = serializers.SerializerMethodField() + + # Scholar's opinions group + scholars_opinions = serializers.SerializerMethodField() + + class Meta: + model = Transmitters + fields = [ + 'id', 'full_name', 'biographical', 'scholars_opinions' + ] + + def get_biographical(self, obj): + """Get biographical information""" + 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, + }, + 'description': obj.description, + 'thumbnail': obj.thumbnail.url if obj.thumbnail else None, + } + + def get_scholars_opinions(self, obj): + """Get all scholarly opinions about this transmitter""" + opinions = [] + for opinion in obj.opinions.all(): + opinions.append({ + 'id': opinion.id, + 'scholar_name': opinion.scholar_name, + 'opinion_text': opinion.opinion_text, + '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 @@ -163,7 +296,7 @@ class HadisTransmitterSerializer(serializers.ModelSerializer): class Meta: model = HadisTransmitter fields = [ - 'id', 'transmitter', 'order', 'is_gap' + 'id', 'transmitter', 'order', 'is_gap','narrator_layer' ] @@ -187,15 +320,60 @@ class ReferenceImageSerializer(serializers.ModelSerializer): class HadisReferenceSerializer(serializers.ModelSerializer): """Serializer for HadisReference with book and images""" - book = BookSerializer(read_only=True) - images = ReferenceImageSerializer(many=True, read_only=True) - + book_title = serializers.SerializerMethodField() + book_images = serializers.SerializerMethodField() + book_authors = serializers.SerializerMethodField() + book_description = serializers.SerializerMethodField() class Meta: model = HadisReference fields = [ - 'id', 'book', 'page_number', 'hadis_number_in_book', - 'description', 'images' + 'id', 'book_title', 'book_images', 'book_authors', 'book_description' ] + + def get_book_title(self, obj): + """Get book title""" + try : + title = obj.book_reference.title + return title + 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""" + try : + authors = obj.book_reference.authors.all() + return authors + except: + return None + + def get_book_description(self, obj): + """Get book description""" + try : + description = obj.book_reference.description + return description + except: + return None + +class HadisCorrectionSerializer(serializers.ModelSerializer): + """Serializer for HadisCorrection""" + hadis_translation = serializers.SerializerMethodField() + class Meta: + model = HadisCorrection + fields = ['id', 'title', 'description', 'hadis_translation', 'created_at', 'updated_at'] + + def get_hadis_translation(self, obj): + """Get translation based on request language""" + request = self.context.get('request') + language_code = getattr(request, 'LANGUAGE_CODE', 'en') + return obj.translation.get(language_code, '') class HadisDetailSerializer(serializers.ModelSerializer): @@ -203,12 +381,15 @@ class HadisDetailSerializer(serializers.ModelSerializer): hadis_status = HadisStatusSerializer(read_only=True) tags = HadisTagSerializer(many=True, read_only=True) transmitters = HadisTransmitterSerializer( - source='hadistransmitter_set', many=True, read_only=True ) references = HadisReferenceSerializer( - source='hadisreference_set', + many=True, + read_only=True + ) + corrections = HadisCorrectionSerializer( + source='hadiscorrection_set', many=True, read_only=True ) @@ -218,9 +399,10 @@ class HadisDetailSerializer(serializers.ModelSerializer): class Meta: model = Hadis fields = [ - 'id', 'number', 'title', 'text', 'translation', 'explanation', - 'address', 'hadis_status_text', 'links', 'status', - 'category', 'hadis_status', 'tags', 'transmitters', 'references' + 'id', 'number', 'title', 'title_narrator','text', 'translation', 'explanation', + 'address', 'hadis_status_text', 'links','share_link', 'status', + 'category', 'hadis_status', 'tags', 'transmitters', 'description', + 'references', 'corrections' ] def get_category(self, obj): @@ -229,7 +411,7 @@ class HadisDetailSerializer(serializers.ModelSerializer): return { 'id': obj.category.id, 'title': obj.category.title, - 'category_type': obj.category.category_type + 'category_type': obj.category.content_type } return None diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index 124a546..c9ddc4b 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -1,7 +1,7 @@ from rest_framework import serializers from ..serializers import HadisListSerializer -from ..models import BookReference , BookAuthor , BookReferenceImage +from ..models import BookReference , BookAuthor , BookReferenceImage, HadisReference class BookAuthorSerializer(serializers.ModelSerializer): class Meta: @@ -56,4 +56,73 @@ class BookDetailSerializer(serializers.ModelSerializer): # def create(self , validated_data): # author = validated_data.pop('author') # book = BookReference.objects.create(**validated_data) - # for author in author \ No newline at end of file + # for author in author + + +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() + + # Hadis group (related hadises) + hadis = serializers.SerializerMethodField() + + class Meta: + model = BookReference + fields = [ + 'id', 'title', 'basic_info', 'information', 'hadis' + ] + + def get_basic_info(self, obj): + """Get basic book information including authors and rating""" + authors = [] + try: + for author in obj.authors.all(): + authors.append({ + 'id': author.id, + 'name': author.name + }) + except: + 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""" + return { + 'language': obj.language, + 'isbn': obj.isbn, + 'year_of_publication': obj.year_of_publication, + 'number_of_pages': obj.number_page, + 'volume_info': obj.volume, + 'rating': obj.rate + } + + def get_hadis(self, obj): + """Get all hadises related to this book reference""" + hadis_list = [] + try: + for hadis_ref in obj.hadis_references.all(): + hadis = hadis_ref.hadis + hadis_list.append({ + 'id': hadis.id, + 'title': hadis.title, + 'title_narrator': hadis.title_narrator, + 'text': hadis.text, + 'translation': hadis.get_translation(self.context.get('request').LANGUAGE_CODE if self.context.get('request') else 'en'), + 'share_link': hadis.share_link + }) + except: + pass + + return hadis_list \ No newline at end of file diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index fac4221..b05b7b4 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -1,8 +1,8 @@ from django.urls import path from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView from .views.hadis import HadisCollectionListView, HadisListView, HadisDetailView, HadisSyncView -from .views.transmitter import TransmitterView ,TransmitterDetailView -from .views.reference import BookDetailView, BookReferencesView +from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView +from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView from .views.info import HadisInfoView @@ -12,9 +12,13 @@ urlpatterns = [ path('sync/categories/tree/', HadisCategoryTreeView.as_view(), name='hadis-category-tree'), path('categories/tree/', HadisCategoryTreeNormalView.as_view(), name='hadis-category-tree-normal'), path('sync/hadis/', HadisSyncView.as_view(), name='hadis-sync'), + path('sync/narrators/', TransmitterSyncView.as_view(), name='transmitter-sync'), + path('sync/references/', BookReferenceSyncView.as_view(), name='reference-sync'), path('info/', HadisInfoView.as_view(), name='hadis-info'), path('category//', HadisListView.as_view(), name='hadis-list'), - path('/', HadisDetailView.as_view(), name='hadis-detail'), + #path('/', HadisDetailView.as_view(), name='hadis-detail'), + #path('hadis//transmitters', HadisDetailView.as_view(), name='hadis-detail'), + #path('hadis//corrections', HadisDetailView.as_view(), name='hadis-detail'), path('categories////', HadisCategorySelectBySectSourceView.as_view(), name='categories-tree-by-sect-source'), path('categories///', HadisCategorySelectBySectView.as_view(), name='categories-tree-by-sect'), path('categories//', CategoriesBySectView.as_view(), name='categories-by-sect'), @@ -23,6 +27,4 @@ urlpatterns = [ path('narrators/',TransmitterDetailView.as_view(), name='narrator-detail'), path('references/',BookReferencesView.as_view(), name='references'), path('references/',BookDetailView.as_view(), name='reference-detail'), - - ] \ No newline at end of file diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index d65eb2b..8132a39 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -83,33 +83,34 @@ class HadisCategoryTreeView(ListAPIView): serializer_instance = HadisCategoryTreeSerializer(context={'request': request}) - # گروه‌بندی بر اساس sect + # گروه‌بندی بر اساس sect_type (shia/sunni) for category in queryset: - sect_id = str(category.sect.id) - - if sect_id not in grouped_data: - # اضافه کردن اطلاعات sect - grouped_data[sect_id] = { - 'sect_info': { - 'id': category.sect.id, - 'sect_type': category.sect.sect_type, - 'title': category.sect.title, - 'description': category.sect.description - }, - 'source_types': [], + sect_type = category.sect.sect_type + + if sect_type not in grouped_data: + # ایجاد گروه برای هر sect_type + grouped_data[sect_type] = { + 'sects': {}, 'categories': {} } - - # اضافه کردن source_type به لیست - if category.source_type not in grouped_data[sect_id]['source_types']: - grouped_data[sect_id]['source_types'].append(category.source_type) - + + # اضافه کردن اطلاعات sect به گروه sect_type + sect_id = str(category.sect.id) + if sect_id not in grouped_data[sect_type]['sects']: + grouped_data[sect_type]['sects'][sect_id] = { + 'id': category.sect.id, + 'sect_type': category.sect.sect_type, + 'title': category.sect.title, + 'description': category.sect.description, + 'order': category.sect.order + } + # گروه‌بندی categories بر اساس source_type - if category.source_type not in grouped_data[sect_id]['categories']: - grouped_data[sect_id]['categories'][category.source_type] = [] - - category_data = serializer_instance.to_dict(category) - grouped_data[sect_id]['categories'][category.source_type].append(category_data) + 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) def count_children(children_list): count = 0 @@ -120,8 +121,8 @@ class HadisCategoryTreeView(ListAPIView): return count total_count = 0 - for sect_data in grouped_data.values(): - for source_categories in sect_data['categories'].values(): + 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']: @@ -134,6 +135,83 @@ class HadisCategoryTreeView(ListAPIView): return Response(response_data) + def build_enhanced_category_tree(self, category, serializer_instance): + """Build enhanced category tree with father category info and hadis details""" + base_data = serializer_instance.to_dict(category) + + # Enhance children with additional information + enhanced_children = [] + for child_data in base_data.get('children', []): + enhanced_child = self.enhance_child_data(child_data, category, serializer_instance) + enhanced_children.append(enhanced_child) + + base_data['children'] = enhanced_children + 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 + + # Add father category information + child_data['father_category'] = { + 'id': parent_category.id, + 'name': parent_category.title, + 'sect_id': parent_category.sect.id, + 'sect_type': parent_category.sect.sect_type, + '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 + + # Recursively enhance children's children + if child_data.get('children', []): + enhanced_grandchildren = [] + try: + from ..models import HadisCategory + child_category = HadisCategory.objects.get(id=child_data['id']) + + for grandchild_data in child_data['children']: + enhanced_grandchild = self.enhance_child_data(grandchild_data, child_category, serializer_instance) + enhanced_grandchildren.append(enhanced_grandchild) + except: + # If there's an error, keep original children + enhanced_grandchildren = child_data['children'] + + child_data['children'] = enhanced_grandchildren + + return child_data + class HadisCategoryTreeNormalView(ListAPIView): """ diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 495de07..645c249 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -33,7 +33,13 @@ class HadisSyncView(ListAPIView): def get_queryset(self): return Hadis.objects.filter(status=True).select_related( 'category', 'hadis_status' - ).prefetch_related('tags').order_by('id') + ).prefetch_related( + 'tags', + 'transmitters__transmitter', + 'references__book_reference__images', + 'references__book_reference__authors', + 'hadiscorrection_set' + ).order_by('id') def list(self, request, *args, **kwargs): queryset = self.get_queryset() @@ -90,7 +96,11 @@ class HadisDetailView(RetrieveAPIView): 'category', 'hadis_status' ).prefetch_related( 'tags', - 'hadistransmitter_set__transmitter', - 'hadisreference_set__book', - 'hadisreference_set__images' + 'transmitters__transmitter', + 'references__book_reference__title', + 'references__book_reference__images', + 'references__book_reference__authors', + 'references__book_reference__id', + 'references__book_reference__description', + 'hadiscorrection_set', ) diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py index 70d5c43..97a5817 100644 --- a/apps/hadis/views/reference.py +++ b/apps/hadis/views/reference.py @@ -1,7 +1,8 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView +from rest_framework.response import Response from ..models import BookReference , BookAuthor , BookReferenceImage -from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer , BookReferenceSerializer -from ..docs import book_references_list_swagger, book_authors_list_swagger, book_detail_swagger +from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer , BookReferenceSerializer, BookReferenceSyncSerializer +from ..docs import book_references_list_swagger, book_authors_list_swagger, book_detail_swagger, reference_sync_swagger from utils.pagination import NoPagination @@ -39,6 +40,39 @@ class BookDetailView(RetrieveAPIView): ) +class BookReferenceSyncView(ListAPIView): + """ + API view to sync all book reference data for offline mode + Returns all book references with basic info, detailed information, and related hadises + """ + serializer_class = BookReferenceSyncSerializer + pagination_class = NoPagination + + @reference_sync_swagger + 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) + + diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 365be46..a09a10c 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -1,8 +1,9 @@ from django.contrib.admin.utils import lookup_field from rest_framework.generics import ListAPIView , RetrieveAPIView +from rest_framework.response import Response from ..models import Transmitters , TransmitterOpinion -from ..serializers import TransmitterSerializer , TransmitterDetailSerializer -from ..docs import transmitter_list_swagger, transmitter_detail_swagger +from ..serializers import TransmitterSerializer , TransmitterDetailSerializer, TransmitterSyncSerializer +from ..docs import transmitter_list_swagger, transmitter_detail_swagger, transmitter_sync_swagger from utils.pagination import NoPagination class TransmitterView(ListAPIView): @@ -51,4 +52,35 @@ class TransmitterDetailView(RetrieveAPIView): def get_queryset(self): input = self.kwargs['transmitters_id'] - return Transmitters.objects.filter(id=input) \ No newline at end of file + return Transmitters.objects.filter(id=input) + + +class TransmitterSyncView(ListAPIView): + """ + API view to sync all transmitter data for offline mode + Returns all transmitters with biographical data and scholarly opinions + """ + serializer_class = TransmitterSyncSerializer + pagination_class = NoPagination + + @transmitter_sync_swagger + + def get_queryset(self): + return Transmitters.objects.prefetch_related('opinions').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 + } + + return Response(response_data) \ No newline at end of file diff --git a/docs/SyncHadis.md b/docs/SyncHadis.md new file mode 100644 index 0000000..265ef27 --- /dev/null +++ b/docs/SyncHadis.md @@ -0,0 +1,635 @@ +# Hadis Sync API Endpoints Documentation + +This document provides comprehensive examples of all sync endpoints in the Hadis API. These endpoints are designed for offline mobile applications and return complete datasets in optimized formats. + +## Overview + +The sync endpoints provide complete data synchronization for offline use. They return all data at once (no pagination) and are optimized with `select_related` and `prefetch_related` for performance. + +## Base URL +``` +https://api.example.com/api/hadis/ +``` + +--- + +## 1. Sync Sects +**Endpoint:** `GET /sync/sects/` +**Purpose:** Get all active sects grouped by sect type (Shia/Sunni) + +### Response Structure +```json +{ + "shia": [ + { + "id": 1, + "title": "Shia Hadith Collections", + "description": "Primary collections of Shia hadith", + "source_types": ["quran", "hadith"] + } + ], + "sunni": [ + { + "id": 2, + "title": "Sunni Hadith Collections", + "description": "Primary collections of Sunni hadith", + "source_types": ["hadith"] + } + ] +} +``` + +--- + +## 2. Sync Categories Tree +**Endpoint:** `GET /sync/categories/tree/` +**Purpose:** Get complete hierarchical category tree grouped by sect type with enhanced child information + +### Response Structure +```json +{ + "count": 12, + "results": { + "shia": { + "sects": { + "1": { + "id": 1, + "sect_type": "shia", + "title": "Shia Hadith Collections", + "description": "Collections of Shia hadith", + "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" + }, + { + "id": 2, + "title": "Prayer Times", + "title_narrator": "From Abdullah ibn Masud", + "text": "The five daily prayers...", + "translation": "The five daily prayers...", + "share_link": "http://example.com/hadis/2" + } + ], + "children": [] + }, + { + "id": 3, + "name": "Surah Al-Baqarah", + "hadis_count": 125, + "has_hadis": false, + "order": 2, + "father_category": { + "id": 1, + "name": "Tafsir", + "sect_id": 1, + "sect_type": "shia", + "source_type": "quran" + }, + "children": [ + { + "id": 4, + "name": "Verses 1-50", + "hadis_count": 75, + "has_hadis": true, + "father_category": { + "id": 3, + "name": "Surah Al-Baqarah", + "sect_id": 1, + "sect_type": "shia", + "source_type": "quran" + }, + "hadis_details": [ + { + "id": 5, + "title": "About Prayer", + "title_narrator": "From Ali ibn Abi Talib", + "text": "Prayer is the pillar of religion...", + "translation": "Prayer is the pillar of religion...", + "share_link": "http://example.com/hadis/5" + } + ] + } + ] + } + ] + } + ], + "hadith": [] + } + }, + "sunni": { + "sects": { + "2": { + "id": 2, + "sect_type": "sunni", + "title": "Sunni Hadith Collections", + "description": "Collections of Sunni hadith", + "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 came to Prophet Muhammad...", + "translation": "The first revelation came to Prophet Muhammad...", + "share_link": "http://example.com/hadis/100" + }, + { + "id": 101, + "title": "Prayer in the Mosque", + "title_narrator": "From Umar ibn Khattab", + "text": "The reward of prayer in congregation...", + "translation": "The reward of prayer in congregation...", + "share_link": "http://example.com/hadis/101" + } + ], + "children": [] + }, + { + "id": 11, + "name": "Sahih Muslim", + "hadis_count": 2200, + "has_hadis": false, + "children": [ + { + "id": 12, + "name": "Book of Faith", + "hadis_count": 150, + "has_hadis": true, + "father_category": { + "id": 11, + "name": "Sahih Muslim", + "sect_id": 2, + "sect_type": "sunni", + "source_type": "hadith" + }, + "hadis_details": [ + { + "id": 200, + "title": "Faith and Actions", + "title_narrator": "From Abu Hurairah", + "text": "Faith consists of more than sixty branches...", + "translation": "Faith consists of more than sixty branches...", + "share_link": "http://example.com/hadis/200" + } + ] + } + ] + } + ] + } + } + } +} +``` + +--- + +## 3. Sync Hadis +**Endpoint:** `GET /sync/hadis/` +**Purpose:** Get all hadis data for offline synchronization + +### Response Structure +```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": "اعمال به نیت ها قضاوت می شود..." + } + } + ] + }, + "2": { + "id": 2, + "number": 2, + "category_id": 3, + "title": "Five Pillars of Islam", + "title_narrator": "From Abdullah ibn Umar", + "text": "Islam is built on five pillars...", + "translations": { + "en": "Islam is built on five pillars...", + "ar": "بني الإسلام على خمس...", + "fa": "اسلام بر پنج پایه استوار است..." + }, + "explanation": "This hadith outlines the fundamental practices of Islam...", + "address": "Sahih al-Bukhari, Book of Faith", + "hadis_status": { + "id": 1, + "title": "Sahih", + "color": "green" + }, + "hadis_status_text": "Authentic", + "share_link": "http://example.com/hadis/2", + "tags": [ + {"id": 3, "title": "Pillars of Islam"}, + {"id": 4, "title": "Faith"} + ], + "links": { + "audio": "http://example.com/audio/hadis2.mp3" + } + } + } +} +``` + +--- + +## 4. Sync Narrators +**Endpoint:** `GET /sync/narrators/` +**Purpose:** Get all transmitter (narrator) data with biographical information and scholarly opinions + +### Response Structure +```json +{ + "count": 200, + "results": { + "1": { + "id": 1, + "full_name": "Abu Daud Sulaiman ibn al-Ash'ath al-Azdi al-Sijistani", + "biographical": { + "personal_info": { + "full_name": "Abu Daud Sulaiman ibn al-Ash'ath al-Azdi al-Sijistani", + "kunya": "Abu Daud", + "known_as": "Imam Abu Daud", + "nickname": "Al-Sijistani" + }, + "dates": { + "birth_year_hijri": 202, + "death_year_hijri": 275, + "age_at_death": 73 + }, + "locations": { + "origin": "Sijistan (modern Sistan)", + "lived_in": "Basra, Baghdad", + "died_in": "Basra" + }, + "religious_profile": { + "reliability": "very_reliable", + "madhhab": "shafii", + "in_sahih_muslim": true, + "in_sahih_bukhari": false + }, + "description": "One of the six canonical hadith collectors. Known for his compilation 'Sunan Abu Daud'.", + "thumbnail": "http://example.com/media/transmitters/abu_daud.jpg" + }, + "scholars_opinions": [ + { + "id": 1, + "scholar_name": "Imam al-Nawawi", + "opinion_text": "Abu Daud is reliable and trustworthy in his transmissions. His collection is one of the six authentic books.", + "status": "confirmed", + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-15T10:30:00Z" + }, + { + "id": 2, + "scholar_name": "Ibn Kathir", + "opinion_text": "Abu Daud was meticulous in his research and only included authentic hadith in his collection.", + "status": "confirmed", + "created_at": "2024-01-16T14:20:00Z", + "updated_at": "2024-01-16T14:20:00Z" + } + ] + }, + "2": { + "id": 2, + "full_name": "Muhammad ibn Isma'il al-Bukhari", + "biographical": { + "personal_info": { + "full_name": "Muhammad ibn Isma'il al-Bukhari", + "kunya": "Abu Abdullah", + "known_as": "Imam al-Bukhari", + "nickname": "The Collector" + }, + "dates": { + "birth_year_hijri": 194, + "death_year_hijri": 256, + "age_at_death": 62 + }, + "locations": { + "origin": "Bukhara (modern Uzbekistan)", + "lived_in": "Bukhara, Nishapur, Baghdad", + "died_in": "Khartank, near Bukhara" + }, + "religious_profile": { + "reliability": "very_reliable", + "madhhab": "hanafi", + "in_sahih_muslim": true, + "in_sahih_bukhari": true + }, + "description": "The compiler of Sahih al-Bukhari, considered the most authentic hadith collection by Muslims.", + "thumbnail": "http://example.com/media/transmitters/bukhari.jpg" + }, + "scholars_opinions": [ + { + "id": 3, + "scholar_name": "Imam Muslim", + "opinion_text": "Al-Bukhari is the most knowledgeable person regarding the conditions of narrators and the defects of hadith.", + "status": "confirmed", + "created_at": "2024-01-17T09:15:00Z", + "updated_at": "2024-01-17T09:15:00Z" + }, + { + "id": 4, + "scholar_name": "Ibn Hajar al-Asqalani", + "opinion_text": "The hadith of al-Bukhari are the most authentic after the Quran.", + "status": "confirmed", + "created_at": "2024-01-18T11:45:00Z", + "updated_at": "2024-01-18T11:45:00Z" + } + ] + } + } +} +``` + +--- + +## 5. Sync References +**Endpoint:** `GET /sync/references/` +**Purpose:** Get all book reference data with basic information, detailed publication info, and related hadises + +### Response Structure +```json +{ + "count": 50, + "results": { + "1": { + "id": 1, + "title": "Sahih al-Bukhari", + "basic_info": { + "title": "Sahih al-Bukhari", + "authors": [ + { + "id": 1, + "name": "Muhammad ibn Isma'il al-Bukhari" + } + ], + "rating": 5.0, + "description": "The most authentic collection of hadith compiled by Imam Bukhari. Contains over 7000 hadith with complete chains of narration.", + "volume": "9 volumes" + }, + "information": { + "language": "Arabic", + "isbn": "978-1234567890", + "year_of_publication": "846", + "number_of_pages": 4200, + "volume_info": "9 volumes", + "rating": 5.0 + }, + "hadis": [ + { + "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" + }, + { + "id": 2, + "title": "Five Pillars of Islam", + "title_narrator": "From Abdullah ibn Umar", + "text": "Islam is built on five pillars...", + "translation": "Islam is built on five pillars...", + "share_link": "http://example.com/hadis/2" + }, + { + "id": 100, + "title": "The Beginning of Revelation", + "title_narrator": "From Aisha", + "text": "The first revelation came to Prophet Muhammad...", + "translation": "The first revelation came to Prophet Muhammad...", + "share_link": "http://example.com/hadis/100" + } + ] + }, + "2": { + "id": 2, + "title": "Sahih Muslim", + "basic_info": { + "title": "Sahih Muslim", + "authors": [ + { + "id": 2, + "name": "Muslim ibn al-Hajjaj al-Naysaburi" + } + ], + "rating": 4.9, + "description": "Second most authentic hadith collection, compiled by Imam Muslim. Known for its strict criteria for authenticity.", + "volume": "8 volumes" + }, + "information": { + "language": "Arabic", + "isbn": "978-0987654321", + "year_of_publication": "875", + "number_of_pages": 3800, + "volume_info": "8 volumes", + "rating": 4.9 + }, + "hadis": [ + { + "id": 3, + "title": "Faith and Actions", + "title_narrator": "From Abu Hurairah", + "text": "Faith consists of more than sixty branches...", + "translation": "Faith consists of more than sixty branches...", + "share_link": "http://example.com/hadis/3" + }, + { + "id": 4, + "title": "Purification", + "title_narrator": "From Abu Hurairah", + "text": "The key to prayer is purification...", + "translation": "The key to prayer is purification...", + "share_link": "http://example.com/hadis/4" + } + ] + }, + "3": { + "id": 3, + "title": "Tafsir Ibn Kathir", + "basic_info": { + "title": "Tafsir Ibn Kathir", + "authors": [ + { + "id": 3, + "name": "Ibn Kathir" + } + ], + "rating": 4.8, + "description": "Comprehensive tafsir (exegesis) of the Quran, combining hadith and scholarly opinions.", + "volume": "4 volumes" + }, + "information": { + "language": "Arabic", + "isbn": "978-1122334455", + "year_of_publication": "1370", + "number_of_pages": 2800, + "volume_info": "4 volumes", + "rating": 4.8 + }, + "hadis": [ + { + "id": 5, + "title": "Quranic Interpretation", + "title_narrator": "From Ibn Abbas", + "text": "The Quran should be interpreted in light of the Prophet's explanations...", + "translation": "The Quran should be interpreted in light of the Prophet's explanations...", + "share_link": "http://example.com/hadis/5" + } + ] + } + } +} +``` + +--- + +## Usage Notes + +### **Performance Optimizations** +- All sync endpoints use `NoPagination` for complete dataset retrieval +- Database queries are optimized with `select_related` and `prefetch_related` +- Related data is prefetched to avoid N+1 query problems + +### **Data Relationships** +- **Categories Tree**: Hierarchical structure with father category references and embedded hadis details +- **Hadis**: Include full translation dictionaries and metadata +- **Narrators**: Biographical data grouped with scholarly opinions +- **References**: Publication details with embedded related hadis + +### **Offline Synchronization** +- Designed for mobile apps requiring complete offline datasets +- Structured for efficient client-side caching and updates +- Includes all necessary related data to minimize API calls + +### **Response Format** +All sync endpoints return: +```json +{ + "count": , + "results": { + "": { ...record_data... } + } +} +``` + +This format allows for easy lookup by ID and provides total count information. diff --git a/docs/hadisdetail.md b/docs/hadisdetail.md new file mode 100644 index 0000000..e9d2c7b --- /dev/null +++ b/docs/hadisdetail.md @@ -0,0 +1,130 @@ +```json +{ + "id": 1800, + "number": 1, + "title": "Достоинство молитвы и ее место в религии - Толкование суры Аль-Фатиха (1)", + "title_narrator": null, + "text": "قال رسول الله صلى الله عليه وآله: الصلاة عمود الدين، إن قبلت قبل ما سواها، وإن ردت رد ما سواها. وهي أول ما يحاسب عليه العبد يوم القيامة، فإن صلحت صلح سائر عمله، وإن فسدت فسد سائر عمله.\n\nوالصلاة معراج المؤمن، وهي قربان كل تقي، وهي حب الله تعالى. من أحبها وأقامها في أوقاتها وحافظ على حدودها رفعه الله إلى درجة الأبرار. ومن استخف بها وضيعها وتركها فقد استخف بدين الله، ولا نصيب له في الإسلام.\n\nإن الله تعالى فرض خمس صلوات في اليوم والليلة، وجعل لكل صلاة وقتاً معلوماً، فمن صلاها في وقتها وأتم ركوعها وسجودها وخشوعها، كانت له نوراً وبرهاناً ونجاة يوم القيامة.", + "translation": "The Messenger of Allah said: Prayer is the pillar of religion, if it is accepted, other deeds are accepted, and if it is rejected, other deeds are rejected. It is the first thing for which a servant will be held accountable on the Day of Judgment, and if it is sound, all his other deeds will be sound, and if it is corrupted, all his other deeds will be corrupted.\n\nPrayer is the ascension of the believer, it is the offering of every God-fearing person, and it is the love of Allah the Almighty. Whoever loves it and establishes it at its times and maintains its boundaries, Allah will raise him to the rank of the righteous. And whoever takes it lightly, wastes it and abandons it, has taken Allah's religion lightly, and has no share in Islam.\n\nIndeed, Allah the Almighty has prescribed five prayers in a day and night, and has appointed a specific time for each prayer. Whoever prays them at their time and completes their bowing, prostration and humility, they will be light, proof and salvation for him on the Day of Judgment.", + "explanation": "Этот обширный хадис представляет собой фундаментальное учение о молитве в Исламе. Он раскрывает несколько ключевых аспектов:\n\nВо-первых, молитва описывается как \"столп религии\" (عمود الدين), что указывает на ее центральную роль в исламской вере. Это метафора подчеркивает, что как здание не может стоять без столпов, так и религиозная жизнь мусульманина не может быть полноценной без молитвы.\n\nВо-вторых, хадис устанавливает принцип, согласно которому принятие или отвержение молитвы Аллахом определяет судьбу всех остальных деяний верующего. Это подчеркивает качественный аспект молитвы - важна не только ее форма, но и искренность, концентрация и правильное выполнение.\n\nВ-третьих, молитва представлена как \"معراج المؤمن\" (вознесение верующего), что отсылает к ночному путешествию Пророка (мир ему) и подчеркивает духовное измерение молитвы как средства приближения к Всевышнему.\n\nХадис также подчеркивает важность своевременного совершения молитв и соблюдения их внешних и внутренних условий, обещая великую награду тем, кто относится к молитве с должным вниманием и уважением.", + "address": null, + "hadis_status_text": null, + "links": [ + { + "link": "https://example.com/source1", + "title": "Source 1" + }, + { + "link": "https://example.com/source2", + "title": "Source 2" + } + ], + "share_link": "https://imamjavad.nwhco.ir/hadis/None", + "status": true, + "category": { + "id": 330, + "title": "Толкование суры Аль-Фатиха", + "category_type": null + }, + "hadis_status": { + "id": 130, + "title": "Прерванный", + "color": "orange" + }, + "tags": [ + { + "id": 510, + "title": "Хадж" + }, + { + "id": 514, + "title": "Терпение" + }, + { + "id": 520, + "title": "Постановления" + } + ], + "transmitters": [ + { + "id": 5992, + "transmitter": { + "id": 53, + "full_name": "Мухаммад ибн аль-Хасан ат-Туси", + "birth_year_hijri": 385, + "death_year_hijri": 460, + "description": "Шейх Туси, автор книг Тахзиб аль-Ахкам и аль-Истибсар", + "reliability": "unknown", + "madhhab": "unknown" + }, + "order": 1, + "is_gap": false + }, + { + "id": 5993, + "transmitter": { + "id": 60, + "full_name": "Мухаммад ибн Муслим", + "birth_year_hijri": 70, + "death_year_hijri": 150, + "description": "Мухаммад ибн Муслим, из сподвижников имама Бакира и имама Садика (мир им)", + "reliability": "unknown", + "madhhab": "unknown" + }, + "order": 2, + "is_gap": false + }, + { + "id": 5994, + "transmitter": { + "id": 56, + "full_name": "Абу Дауд ас-Сиджистани", + "birth_year_hijri": 202, + "death_year_hijri": 275, + "description": "Имам Абу Дауд, автор Сунан Абу Дауд", + "reliability": "unknown", + "madhhab": "unknown" + }, + "order": 3, + "is_gap": false + }, + { + "id": 5995, + "transmitter": { + "id": 59, + "full_name": "Али ибн аль-Хусейн ас-Саджжад", + "birth_year_hijri": 38, + "death_year_hijri": 95, + "description": "Имам Али ибн аль-Хусейн (мир ему), четвертый имам шиитов", + "reliability": "unknown", + "madhhab": "unknown" + }, + "order": 4, + "is_gap": true + }, + { + "id": 5996, + "transmitter": { + "id": 58, + "full_name": "Мухаммад ибн Али аль-Бакир", + "birth_year_hijri": 57, + "death_year_hijri": 114, + "description": "Имам Мухаммад Бакир (мир ему), пятый имам шиитов", + "reliability": "unknown", + "madhhab": "unknown" + }, + "order": 5, + "is_gap": false + } + ], + "description": null, + "references": [ + { + "id": 2193, + "book_title": null, + "book_images": null, + "book_authors": null + } + ] +} +``` \ No newline at end of file