10 Commits

  1. 29
      apps/hadis/migrations/0045_hadisreference_address_hadisreference_book_volume_and_more.py
  2. 18
      apps/hadis/migrations/0046_originaltextreference_hadith_number.py
  3. 27
      apps/hadis/models/hadis.py
  4. 11
      apps/hadis/models/transmitter.py
  5. 13
      apps/hadis/serializers/category.py
  6. 276
      apps/hadis/serializers/hadis.py
  7. 631
      apps/hadis/serializers/serializers_admin.py
  8. 14
      apps/hadis/urls.py
  9. 22
      apps/hadis/views/category.py
  10. 180
      apps/hadis/views/hadis.py
  11. 21
      apps/hadis/views/transmitter.py
  12. 56
      apps/hadis/views_admin.py

29
apps/hadis/migrations/0045_hadisreference_address_hadisreference_book_volume_and_more.py

@ -0,0 +1,29 @@
# Generated by Django 4.2.30 on 2026-10-06 10:04
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hadis', '0044_alter_hadiscategory_description_and_more'),
]
operations = [
migrations.AddField(
model_name='hadisreference',
name='address',
field=models.TextField(blank=True, null=True, verbose_name='Address'),
),
migrations.AddField(
model_name='hadisreference',
name='book_volume',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hadis_references', to='hadis.bookvolume', verbose_name='Volume Record'),
),
migrations.AddField(
model_name='hadisreference',
name='edition',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hadis_references', to='hadis.bookedition', verbose_name='Edition'),
),
]

18
apps/hadis/migrations/0046_originaltextreference_hadith_number.py

@ -0,0 +1,18 @@
# Generated by Django 4.2.30 on 2026-10-06 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hadis', '0045_hadisreference_address_hadisreference_book_volume_and_more'),
]
operations = [
migrations.AddField(
model_name='originaltextreference',
name='hadith_number',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Hadith / Entry Number'),
),
]

27
apps/hadis/models/hadis.py

@ -184,7 +184,15 @@ class Hadis(LowercaseSlugMixin, models.Model):
embedded_in = models.JSONField(default=list, blank=True) embedded_in = models.JSONField(default=list, blank=True)
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else f"Hadis {self.number}"
title = None
if self.title:
first_item = self.title[0] if isinstance(self.title, list) and self.title else self.title
if isinstance(first_item, dict):
title = first_item.get('text') or first_item.get('value') or first_item.get('title')
elif isinstance(first_item, str):
title = first_item
if not title:
title = f"Hadis {self.number}"
return f"{self.number} - {title}" return f"{self.number} - {title}"
def clean(self): def clean(self):
@ -307,8 +315,25 @@ class HadisReference(models.Model):
verbose_name=_('book reference'), verbose_name=_('book reference'),
related_name='hadis_references' related_name='hadis_references'
) )
edition = models.ForeignKey(
'hadis.BookEdition',
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('Edition'),
related_name='hadis_references'
)
book_volume = models.ForeignKey(
'hadis.BookVolume',
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('Volume Record'),
related_name='hadis_references'
)
volume = models.CharField(max_length=100, null=True, blank=True) volume = models.CharField(max_length=100, null=True, blank=True)
pages = models.CharField(max_length=100, null=True, blank=True) pages = models.CharField(max_length=100, null=True, blank=True)
address = models.TextField(null=True, blank=True, verbose_name=_('Address'))
url = models.URLField(max_length=1000, null=True, blank=True) url = models.URLField(max_length=1000, null=True, blank=True)
hadith_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Hadith Number in Book')) hadith_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Hadith Number in Book'))

11
apps/hadis/models/transmitter.py

@ -510,7 +510,15 @@ class TransmitterOriginalText(models.Model):
ordering = ('order', 'id') ordering = ('order', 'id')
def __str__(self): def __str__(self):
title = self.title[0]['text'] if self.title else "Untitled"
title = None
if self.title:
first_item = self.title[0] if isinstance(self.title, list) and self.title else self.title
if isinstance(first_item, dict):
title = first_item.get('text') or first_item.get('value') or first_item.get('title')
elif isinstance(first_item, str):
title = first_item
if not title:
title = "Untitled"
return f"{title} by {self.transmitter}" return f"{title} by {self.transmitter}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -582,6 +590,7 @@ class OriginalTextReference(models.Model):
edition = models.ForeignKey('hadis.BookEdition', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Edition')) edition = models.ForeignKey('hadis.BookEdition', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Edition'))
book_volume = models.ForeignKey('hadis.BookVolume', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Volume Record')) book_volume = models.ForeignKey('hadis.BookVolume', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Volume Record'))
hadith_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Hadith / Entry Number'))
# فیلدهای متنی قدیمی برای سادگی یا مواردی که هنوز ولوم ساخته نشده است # فیلدهای متنی قدیمی برای سادگی یا مواردی که هنوز ولوم ساخته نشده است
volume = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Volume Text')) volume = models.CharField(max_length=100, null=True, blank=True, verbose_name=_('Volume Text'))
pages = models.CharField(max_length=100, null=True, blank=True) # 👈 تغییر page به pages pages = models.CharField(max_length=100, null=True, blank=True) # 👈 تغییر page به pages

13
apps/hadis/serializers/category.py

@ -36,7 +36,18 @@ def get_localized_text(json_list, request=None, fallback_lang="en", language_cod
# Get target language if not provided # Get target language if not provided
if not language_code: if not language_code:
language_code = getattr(request, "LANGUAGE_CODE", None) if request else None
if request:
query_params = getattr(request, "query_params", None) or getattr(request, "GET", None)
if query_params:
language_code = query_params.get("language_code") or query_params.get("lang")
if not language_code:
language_code = getattr(request, "LANGUAGE_CODE", None)
if not language_code and hasattr(request, "headers"):
language_code = request.headers.get("Accept-Language")
elif not language_code and hasattr(request, "META"):
language_code = request.META.get("HTTP_ACCEPT_LANGUAGE")
if language_code:
language_code = language_code.split(",")[0].split(";")[0].split("-")[0].strip().lower()
if not language_code: if not language_code:
language_code = get_language() or fallback_lang language_code = get_language() or fallback_lang

276
apps/hadis/serializers/hadis.py

@ -304,16 +304,27 @@ class HadisSyncSerializer(serializers.ModelSerializer):
).data ).data
return [] return []
class HadisTagSerializer(serializers.ModelSerializer):
"""Serializer for HadisTag"""
title = LocalizedField()
class Meta:
model = HadisTag
fields = ['id', 'title']
class HadisListSerializer(serializers.ModelSerializer): class HadisListSerializer(serializers.ModelSerializer):
"""Serializer for Hadis list""" """Serializer for Hadis list"""
category = serializers.SerializerMethodField() category = serializers.SerializerMethodField()
status = serializers.SerializerMethodField() status = serializers.SerializerMethodField()
bookmark = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField()
tags = HadisTagSerializer(many=True, read_only=True)
translation = LocalizedField() translation = LocalizedField()
title = LocalizedField() title = LocalizedField()
title_narrator = LocalizedField() title_narrator = LocalizedField()
address = serializers.SerializerMethodField() address = serializers.SerializerMethodField()
links = serializers.SerializerMethodField() links = serializers.SerializerMethodField()
references = serializers.SerializerMethodField()
reference_images = serializers.SerializerMethodField() reference_images = serializers.SerializerMethodField()
images = serializers.SerializerMethodField() images = serializers.SerializerMethodField()
@ -321,7 +332,11 @@ class HadisListSerializer(serializers.ModelSerializer):
model = Hadis model = Hadis
fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text', fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text',
'translation', 'category', 'status', 'bookmark', 'share_link', 'translation', 'category', 'status', 'bookmark', 'share_link',
'address', 'links', 'reference_images', 'images']
'address', 'links', 'references', 'reference_images', 'images', 'tags']
def get_references(self, obj):
refs = obj.references.all()
return DetailedHadisReferenceSerializer(refs, many=True, context=self.context).data
def get_address(self, obj): def get_address(self, obj):
formatted = format_address_output(obj.address) formatted = format_address_output(obj.address)
@ -420,15 +435,6 @@ class HadisStatusSerializer(serializers.ModelSerializer):
fields = ['id', 'title', 'color', 'main_color_code', 'description'] fields = ['id', 'title', 'color', 'main_color_code', 'description']
class HadisTagSerializer(serializers.ModelSerializer):
"""Serializer for HadisTag"""
title = LocalizedField()
class Meta:
model = HadisTag
fields = ['id', 'title']
class TransmitterSerializer(serializers.ModelSerializer): class TransmitterSerializer(serializers.ModelSerializer):
"""Serializer for Transmitters""" """Serializer for Transmitters"""
full_name = LocalizedField() full_name = LocalizedField()
@ -1035,33 +1041,181 @@ class HadisTransmitterListSerializer(serializers.ModelSerializer):
class ReferenceImageSerializer(serializers.ModelSerializer): class ReferenceImageSerializer(serializers.ModelSerializer):
"""Serializer for ReferenceImage""" """Serializer for ReferenceImage"""
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
class Meta: class Meta:
model = ReferenceImage model = ReferenceImage
fields = ['id', 'thumbnail', 'priority']
fields = ['id', 'thumbnail', 'image', 'priority']
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
"""Get thumbnail URL""" """Get thumbnail URL"""
if obj.thumbnail:
img = getattr(obj, 'thumbnail', None) or getattr(obj, 'image', None)
if img:
request = self.context.get('request') request = self.context.get('request')
if request: if request:
return absolute_https_url(obj.thumbnail.url, request)
return absolute_https_url(obj.thumbnail.url)
return absolute_https_url(img.url, request)
return absolute_https_url(img.url)
return None return None
def get_image(self, obj):
return self.get_thumbnail(obj)
class HadisReferenceSerializer(serializers.ModelSerializer): class HadisReferenceSerializer(serializers.ModelSerializer):
"""Serializer for HadisReference with book and images"""
"""Serializer for HadisReference with book, author, edition, volume, and images"""
book_title = serializers.SerializerMethodField() book_title = serializers.SerializerMethodField()
book_authors = serializers.SerializerMethodField() book_authors = serializers.SerializerMethodField()
book_description = serializers.SerializerMethodField() book_description = serializers.SerializerMethodField()
share_link = serializers.CharField(source='book_reference.share_link', read_only=True) share_link = serializers.CharField(source='book_reference.share_link', read_only=True)
edition = serializers.SerializerMethodField()
edition_info = serializers.SerializerMethodField()
volume_info = serializers.SerializerMethodField()
volume = serializers.SerializerMethodField()
pages = serializers.SerializerMethodField()
hadith_number = serializers.SerializerMethodField()
citation_summary = serializers.SerializerMethodField()
open_book_url = serializers.SerializerMethodField()
book = serializers.SerializerMethodField()
author = serializers.SerializerMethodField()
images = ReferenceImageSerializer(many=True, read_only=True)
images_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = HadisReference model = HadisReference
fields = [ fields = [
'id', 'book_title', 'book_description','book_authors','share_link'
'id', 'book', 'author', 'book_title', 'book_description', 'book_authors', 'share_link',
'edition', 'edition_info', 'volume_info', 'volume', 'pages', 'hadith_number',
'address', 'url', 'open_book_url', 'citation_summary', 'images', 'images_count'
] ]
def get_edition_info(self, obj):
edition = obj.edition
if not edition and getattr(obj, "book_volume", None) and obj.book_volume.edition:
edition = obj.book_volume.edition
if not edition and getattr(obj, "book_reference", None) and hasattr(obj.book_reference, "editions"):
edition = obj.book_reference.editions.first()
if edition:
request = self.context.get("request")
pub = edition.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
return {
"id": edition.id,
"edition_number": edition.edition_number,
"publisher": pub or "",
"year_of_publication": edition.year_of_publication or "",
"number_of_volumes": edition.number_of_volumes,
}
return None
def get_volume_info(self, obj):
if obj.book_volume:
return {
"id": obj.book_volume.id,
"title": obj.book_volume.title
}
return None
def get_edition(self, obj):
return self.get_edition_info(obj)
def get_volume(self, obj):
if obj.volume and str(obj.volume).strip().lower() != "none":
return obj.volume
if obj.book_volume and obj.book_volume.title:
return obj.book_volume.title
return None
def get_pages(self, obj):
if obj.pages and str(obj.pages).strip().lower() != "none":
return obj.pages
return None
def get_hadith_number(self, obj):
if obj.hadith_number and str(obj.hadith_number).strip().lower() != "none":
return obj.hadith_number
return None
def get_citation_summary(self, obj):
parts = []
vol = self.get_volume(obj)
pg = self.get_pages(obj)
num = self.get_hadith_number(obj)
if vol:
vol_str = str(vol).strip()
if vol_str.startswith("جلد"):
parts.append(vol_str)
else:
parts.append(f"جلد {vol_str}")
if pg:
pg_str = str(pg).strip()
if pg_str.startswith("ص") or pg_str.startswith("صفحه"):
parts.append(pg_str)
else:
parts.append(f"صفحه {pg_str}")
if num:
num_str = str(num).strip()
if num_str.startswith("ح") or num_str.startswith("حدیث") or num_str.startswith("شماره"):
parts.append(num_str)
else:
parts.append(f"شماره حدیث {num_str}")
return " • ".join(parts) if parts else ""
def get_open_book_url(self, obj):
request = self.context.get('request')
if obj.book_volume and obj.book_volume.file:
return absolute_https_url(obj.book_volume.file.url, request) if request else absolute_https_url(obj.book_volume.file.url)
return obj.url or None
def get_book(self, obj):
if obj.book_reference:
request = self.context.get('request')
first_ed = obj.edition
if not first_ed and getattr(obj, "book_volume", None) and obj.book_volume.edition:
first_ed = obj.book_volume.edition
if not first_ed and hasattr(obj.book_reference, "editions"):
first_ed = obj.book_reference.editions.first()
pub = getattr(obj.book_reference, "publisher", None)
if pub is None and first_ed:
pub = first_ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
year = getattr(obj.book_reference, "year_of_publication", None)
if not year and first_ed:
year = first_ed.year_of_publication
num_vols = getattr(obj.book_reference, "number_of_volumes", None)
if num_vols is None and first_ed:
num_vols = first_ed.number_of_volumes
return {
'id': obj.book_reference.id,
'slug': obj.book_reference.slug,
'title': get_localized_text(obj.book_reference.title, request) if isinstance(obj.book_reference.title, list) else obj.book_reference.title,
'publisher': pub or '',
'year_of_publication': year or '',
'number_of_volumes': num_vols,
}
return None
def get_author(self, obj):
if obj.book_reference and obj.book_reference.author:
author = obj.book_reference.author
request = self.context.get('request')
return {
'id': author.id,
'slug': author.slug,
'name': get_localized_text(author.name, request) if isinstance(author.name, list) else author.name
}
return None
def get_images_count(self, obj):
if hasattr(obj, '_prefetched_objects_cache') and 'images' in obj._prefetched_objects_cache:
return len(obj.images.all())
return obj.images.count()
# def get_type_name(self, obj): # def get_type_name(self, obj):
# """Get localized type name from book_reference.type""" # """Get localized type name from book_reference.type"""
# if obj.book_reference and obj.book_reference.type: # if obj.book_reference and obj.book_reference.type:
@ -1634,17 +1788,81 @@ class DetailedCorrectionReferenceSerializer(BaseDetailedReferenceSerializer):
return len(obj.images.all()) return len(obj.images.all())
return obj.images.count() return obj.images.count()
class DetailedInterpretationReferenceSerializer(BaseDetailedReferenceSerializer):
class DetailedInterpretationReferenceSerializer(DetailedCorrectionReferenceSerializer):
images = InterpretationReferenceImageSerializer(many=True, read_only=True) images = InterpretationReferenceImageSerializer(many=True, read_only=True)
class Meta: class Meta:
model = InterpretationReference model = InterpretationReference
fields = ['id', 'book_title', 'book_authors', 'edition_info', 'volume_info', 'hadith_number', 'volume', 'pages', 'address', 'url', 'images']
fields = [
'id',
'book',
'author',
'edition',
'volume_info',
'hadith_number',
'volume',
'pages',
'address',
'url',
'open_book_url',
'citation_summary',
'images',
'images_count',
'book_title',
'book_authors',
'edition_info',
]
class DetailedOriginalTextReferenceSerializer(BaseDetailedReferenceSerializer):
class DetailedOriginalTextReferenceSerializer(DetailedCorrectionReferenceSerializer):
images = OriginalTextReferenceImageSerializer(many=True, read_only=True) images = OriginalTextReferenceImageSerializer(many=True, read_only=True)
class Meta: class Meta:
model = OriginalTextReference model = OriginalTextReference
fields = ['id', 'book_title', 'book_authors', 'edition_info', 'volume_info', 'volume', 'pages', 'address', 'url', 'images']
fields = [
'id',
'book',
'author',
'edition',
'volume_info',
'hadith_number',
'volume',
'pages',
'address',
'url',
'open_book_url',
'citation_summary',
'images',
'images_count',
'book_title',
'book_authors',
'edition_info',
]
class DetailedHadisReferenceSerializer(DetailedCorrectionReferenceSerializer):
images = ReferenceImageSerializer(many=True, read_only=True)
class Meta:
model = HadisReference
fields = [
'id',
'book',
'author',
'edition',
'volume_info',
'hadith_number',
'volume',
'pages',
'address',
'url',
'open_book_url',
'citation_summary',
'images',
'images_count',
'book_title',
'book_authors',
'edition_info',
]
# --- سریالایزرهای اصلی جزئیات (Detail Serializers) --- # --- سریالایزرهای اصلی جزئیات (Detail Serializers) ---
@ -1778,7 +1996,7 @@ class TransmitterOriginalTextDetailSerializer(serializers.ModelSerializer):
class HadisSourceDetailsSerializer(serializers.ModelSerializer): class HadisSourceDetailsSerializer(serializers.ModelSerializer):
""" """
Lightweight serializer for Hadis Source Details (Address, Links, Images).
Lightweight serializer for Hadis Source Details (Address, Links, Images, Reference).
Used on-demand when opening source detail modals in web and mobile clients. Used on-demand when opening source detail modals in web and mobile clients.
""" """
title = LocalizedField() title = LocalizedField()
@ -1787,12 +2005,16 @@ class HadisSourceDetailsSerializer(serializers.ModelSerializer):
address = serializers.SerializerMethodField() address = serializers.SerializerMethodField()
links = serializers.SerializerMethodField() links = serializers.SerializerMethodField()
images = serializers.SerializerMethodField() images = serializers.SerializerMethodField()
reference = serializers.SerializerMethodField()
references = serializers.SerializerMethodField()
tags = HadisTagSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Hadis model = Hadis
fields = [ fields = [
'id', 'slug', 'title', 'narrator', 'source_type', 'id', 'slug', 'title', 'narrator', 'source_type',
'address', 'links', 'images', 'share_link'
'address', 'links', 'images', 'share_link',
'reference', 'references', 'tags'
] ]
def get_source_type(self, obj): def get_source_type(self, obj):
@ -1834,3 +2056,13 @@ class HadisSourceDetailsSerializer(serializers.ModelSerializer):
}) })
return images_list return images_list
def get_reference(self, obj):
ref = obj.references.first()
if ref:
return DetailedHadisReferenceSerializer(ref, context=self.context).data
return None
def get_references(self, obj):
refs = obj.references.all()
return DetailedHadisReferenceSerializer(refs, many=True, context=self.context).data

631
apps/hadis/serializers/serializers_admin.py

@ -245,6 +245,174 @@ class AdminHadisCollectionSerializer(serializers.ModelSerializer):
return super().update(instance, validated_data) return super().update(instance, validated_data)
class AdminHadisReferenceSerializer(serializers.ModelSerializer):
book_reference_detail = serializers.SerializerMethodField(read_only=True)
edition_detail = serializers.SerializerMethodField(read_only=True)
book_volume_detail = serializers.SerializerMethodField(read_only=True)
images = serializers.SerializerMethodField(read_only=True)
delete_image_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
write_only=True
)
class Meta:
model = HadisReference
fields = [
"id",
"hadis",
"book_reference",
"book_reference_detail",
"edition",
"edition_detail",
"book_volume",
"book_volume_detail",
"hadith_number",
"volume",
"pages",
"address",
"url",
"images",
"delete_image_ids",
]
def get_book_reference_detail(self, obj):
if obj.book_reference:
request = self.context.get("request")
author = obj.book_reference.author
first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None
pub = getattr(obj.book_reference, "publisher", None)
if pub is None and first_ed:
pub = first_ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
year = getattr(obj.book_reference, "year_of_publication", None)
if not year and first_ed:
year = first_ed.year_of_publication
num_vols = getattr(obj.book_reference, "number_of_volumes", None)
if num_vols is None and first_ed:
num_vols = first_ed.number_of_volumes
return {
"id": obj.book_reference.id,
"title": get_localized_text(obj.book_reference.title, request) if isinstance(obj.book_reference.title, list) else obj.book_reference.title,
"slug": obj.book_reference.slug,
"publisher": pub or "",
"year_of_publication": year or "",
"number_of_volumes": num_vols,
"has_editions": getattr(obj.book_reference, "has_editions", False),
"author": {
"id": author.id,
"name": get_localized_text(author.name, request) if isinstance(author.name, list) else author.name,
"slug": author.slug
} if author else None
}
return None
def get_edition_detail(self, obj):
if obj.edition:
request = self.context.get("request")
pub = obj.edition.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
return {
"id": obj.edition.id,
"edition_number": obj.edition.edition_number,
"publisher": pub or "",
"year_of_publication": obj.edition.year_of_publication or "",
"number_of_volumes": obj.edition.number_of_volumes,
}
return None
def get_book_volume_detail(self, obj):
if obj.book_volume:
return {
"id": obj.book_volume.id,
"title": obj.book_volume.title,
}
return None
def get_images(self, obj):
request = self.context.get("request")
return [
{
"id": img.id,
"reference_id": obj.id,
"hadis_id": obj.hadis_id,
"thumbnail": absolute_https_url(img.thumbnail.url, request) if request and img.thumbnail else (img.thumbnail.url if img.thumbnail else None),
"image": absolute_https_url(img.thumbnail.url, request) if request and img.thumbnail else (img.thumbnail.url if img.thumbnail else None),
"priority": img.priority,
}
for img in obj.images.all().order_by("priority", "id")
]
def to_internal_value(self, data):
import json
data = safe_copy_data(data)
book_ref = data.get("book_reference")
if book_ref in ("", "none", "null"):
data["book_reference"] = None
edition = data.get("edition")
if edition in ("", "none", "null"):
data["edition"] = None
book_vol = data.get("book_volume")
if book_vol in ("", "none", "null"):
data["book_volume"] = None
delete_ids = data.get("delete_image_ids")
if delete_ids:
if isinstance(delete_ids, str):
try:
data["delete_image_ids"] = json.loads(delete_ids)
except ValueError:
data["delete_image_ids"] = [int(x) for x in delete_ids.split(",") if x.strip().isdigit()]
elif isinstance(delete_ids, list):
parsed = []
for x in delete_ids:
if isinstance(x, str) and x.strip().isdigit():
parsed.append(int(x))
elif isinstance(x, int):
parsed.append(x)
data["delete_image_ids"] = parsed
return super().to_internal_value(data)
def create(self, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().create(validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def update(self, instance, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().update(instance, validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def _handle_images(self, instance, delete_image_ids):
request = self.context.get("request")
if delete_image_ids:
instance.images.filter(id__in=delete_image_ids).delete()
if request:
new_images = (
request.FILES.getlist("uploaded_images")
or request.FILES.getlist("uploaded_images[]")
or request.FILES.getlist("images")
or request.FILES.getlist("images[]")
)
single_img = request.FILES.get("image") or request.FILES.get("thumbnail")
if single_img and not new_images:
new_images = [single_img]
last_img = instance.images.order_by("-priority").first()
current_priority = (last_img.priority + 1) if last_img else 1
for img in new_images:
ReferenceImage.objects.create(reference=instance, thumbnail=img, priority=current_priority)
current_priority += 1
class AdminHadisDetailSerializer(serializers.ModelSerializer): class AdminHadisDetailSerializer(serializers.ModelSerializer):
category_detail = AdminHadisCategorySerializer(source="category", read_only=True) category_detail = AdminHadisCategorySerializer(source="category", read_only=True)
hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True) hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True)
@ -279,6 +447,36 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
write_only=True, write_only=True,
allow_null=True, allow_null=True,
) )
edition_id = serializers.PrimaryKeyRelatedField(
queryset=BookEdition.objects.all(),
required=False,
write_only=True,
allow_null=True,
)
book_volume_id = serializers.PrimaryKeyRelatedField(
queryset=BookVolume.objects.all(),
required=False,
write_only=True,
allow_null=True,
)
volume = serializers.CharField(
required=False,
write_only=True,
allow_null=True,
allow_blank=True,
)
pages = serializers.CharField(
required=False,
write_only=True,
allow_null=True,
allow_blank=True,
)
hadith_number = serializers.CharField(
required=False,
write_only=True,
allow_null=True,
allow_blank=True,
)
bookmark_count = serializers.SerializerMethodField() bookmark_count = serializers.SerializerMethodField()
@ -325,6 +523,11 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
"collections", "collections",
"collections_detail", "collections_detail",
"book_reference_id", "book_reference_id",
"edition_id",
"book_volume_id",
"volume",
"pages",
"hadith_number",
"share_link", "share_link",
"explanation", "explanation",
"bookmark_count", "bookmark_count",
@ -336,22 +539,75 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
ret = super().to_representation(instance) ret = super().to_representation(instance)
request = self.context.get("request")
refs = list(instance.references.all()) refs = list(instance.references.all())
ref = refs[0] if refs else None ref = refs[0] if refs else None
ret["book_reference"] = ref.book_reference_id if ref else None ret["book_reference"] = ref.book_reference_id if ref else None
ret["edition"] = ref.edition_id if ref else None
ret["book_volume"] = ref.book_volume_id if ref else None
ret["volume"] = ref.volume if ref else None
ret["pages"] = ref.pages if ref else None
ret["hadith_number"] = ref.hadith_number if ref else None
if ref and ref.book_reference: if ref and ref.book_reference:
author = ref.book_reference.author
first_ed = ref.book_reference.editions.first() if hasattr(ref.book_reference, "editions") else None
pub = getattr(ref.book_reference, "publisher", None)
if pub is None and first_ed:
pub = first_ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
year = getattr(ref.book_reference, "year_of_publication", None)
if not year and first_ed:
year = first_ed.year_of_publication
num_vols = getattr(ref.book_reference, "number_of_volumes", None)
if num_vols is None and first_ed:
num_vols = first_ed.number_of_volumes
ret["book_reference_detail"] = { ret["book_reference_detail"] = {
"id": ref.book_reference.id, "id": ref.book_reference.id,
"title": ref.book_reference.title,
"title": get_localized_text(ref.book_reference.title, request) if isinstance(ref.book_reference.title, list) else ref.book_reference.title,
"slug": ref.book_reference.slug, "slug": ref.book_reference.slug,
"publisher": pub or "",
"year_of_publication": year or "",
"number_of_volumes": num_vols,
"has_editions": getattr(ref.book_reference, "has_editions", False),
"author": {
"id": author.id,
"name": get_localized_text(author.name, request) if isinstance(author.name, list) else author.name,
"slug": author.slug
} if author else None
} }
else: else:
ret["book_reference_detail"] = None ret["book_reference_detail"] = None
if ref and ref.edition:
ed = ref.edition
pub = ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
ret["edition_detail"] = {
"id": ed.id,
"edition_number": ed.edition_number,
"publisher": pub or "",
"year_of_publication": ed.year_of_publication or "",
"number_of_volumes": ed.number_of_volumes,
}
else:
ret["edition_detail"] = None
if ref and ref.book_volume:
ret["book_volume_detail"] = {
"id": ref.book_volume.id,
"title": ref.book_volume.title,
}
else:
ret["book_volume_detail"] = None
ref_images = [] ref_images = []
for r in refs: for r in refs:
for img in r.images.all().order_by("priority", "id"): for img in r.images.all().order_by("priority", "id"):
url = absolute_https_url(img.thumbnail.url, self.context.get("request")) if img.thumbnail else None
url = absolute_https_url(img.thumbnail.url, request) if img.thumbnail else None
ref_images.append({ ref_images.append({
"id": img.id, "id": img.id,
"reference_id": r.id, "reference_id": r.id,
@ -361,6 +617,7 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
"priority": img.priority, "priority": img.priority,
}) })
ret["reference_images"] = ref_images ret["reference_images"] = ref_images
ret["references"] = AdminHadisReferenceSerializer(instance.references.all().order_by("id"), many=True, context=self.context).data
return ret return ret
def get_bookmark_count(self, obj): def get_bookmark_count(self, obj):
@ -380,6 +637,11 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
book_reference = validated_data.pop("book_reference_id", None) book_reference = validated_data.pop("book_reference_id", None)
edition = validated_data.pop("edition_id", None)
book_volume = validated_data.pop("book_volume_id", None)
volume = validated_data.pop("volume", None)
pages = validated_data.pop("pages", None)
hadith_number = validated_data.pop("hadith_number", None)
tags = validated_data.pop("tags", []) tags = validated_data.pop("tags", [])
collections = validated_data.pop("collection_items", []) collections = validated_data.pop("collection_items", [])
hadis = super().create(validated_data) hadis = super().create(validated_data)
@ -388,13 +650,32 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
if collections: if collections:
for idx, col in enumerate(collections): for idx, col in enumerate(collections):
HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx) HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx)
if book_reference:
HadisReference.objects.create(hadis=hadis, book_reference=book_reference)
if book_reference or edition or book_volume or volume or pages or hadith_number:
HadisReference.objects.create(
hadis=hadis,
book_reference=book_reference,
edition=edition,
book_volume=book_volume,
volume=volume or "",
pages=pages or "",
hadith_number=hadith_number or ""
)
return hadis return hadis
def update(self, instance, validated_data): def update(self, instance, validated_data):
book_reference_provided = "book_reference_id" in validated_data book_reference_provided = "book_reference_id" in validated_data
book_reference = validated_data.pop("book_reference_id", None) book_reference = validated_data.pop("book_reference_id", None)
edition_provided = "edition_id" in validated_data
edition = validated_data.pop("edition_id", None)
book_volume_provided = "book_volume_id" in validated_data
book_volume = validated_data.pop("book_volume_id", None)
volume_provided = "volume" in validated_data
volume = validated_data.pop("volume", None)
pages_provided = "pages" in validated_data
pages = validated_data.pop("pages", None)
hadith_number_provided = "hadith_number" in validated_data
hadith_number = validated_data.pop("hadith_number", None)
tags = validated_data.pop("tags", None) tags = validated_data.pop("tags", None)
collections = validated_data.pop("collection_items", None) collections = validated_data.pop("collection_items", None)
hadis = super().update(instance, validated_data) hadis = super().update(instance, validated_data)
@ -404,13 +685,30 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer):
instance.collection_items.all().delete() instance.collection_items.all().delete()
for idx, col in enumerate(collections): for idx, col in enumerate(collections):
HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx) HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx)
if book_reference_provided:
any_ref_provided = (
book_reference_provided or edition_provided or book_volume_provided or
volume_provided or pages_provided or hadith_number_provided
)
if any_ref_provided:
ref = instance.references.first() ref = instance.references.first()
if not ref:
if book_reference or edition or book_volume or volume or pages or hadith_number:
ref = HadisReference.objects.create(hadis=hadis)
if ref: if ref:
if book_reference_provided:
ref.book_reference = book_reference ref.book_reference = book_reference
if edition_provided:
ref.edition = edition
if book_volume_provided:
ref.book_volume = book_volume
if volume_provided:
ref.volume = volume or ""
if pages_provided:
ref.pages = pages or ""
if hadith_number_provided:
ref.hadith_number = hadith_number or ""
ref.save() ref.save()
elif book_reference:
HadisReference.objects.create(hadis=hadis, book_reference=book_reference)
return hadis return hadis
@ -778,8 +1076,315 @@ class AdminHadisCorrectionSerializer(serializers.ModelSerializer):
return instance return instance
class AdminInterpretationReferenceSerializer(serializers.ModelSerializer):
book_reference_detail = serializers.SerializerMethodField(read_only=True)
edition_detail = serializers.SerializerMethodField(read_only=True)
book_volume_detail = serializers.SerializerMethodField(read_only=True)
images = serializers.SerializerMethodField(read_only=True)
delete_image_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
write_only=True
)
class Meta:
model = InterpretationReference
fields = [
"id",
"interpretation",
"book_reference",
"book_reference_detail",
"edition",
"edition_detail",
"book_volume",
"book_volume_detail",
"hadith_number",
"volume",
"pages",
"address",
"url",
"images",
"delete_image_ids",
]
def get_book_reference_detail(self, obj):
if obj.book_reference:
request = self.context.get("request")
author = obj.book_reference.author
first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None
pub = getattr(obj.book_reference, "publisher", None)
if pub is None and first_ed:
pub = first_ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
year = getattr(obj.book_reference, "year_of_publication", None)
if not year and first_ed:
year = first_ed.year_of_publication
num_vols = getattr(obj.book_reference, "number_of_volumes", None)
if num_vols is None and first_ed:
num_vols = first_ed.number_of_volumes
return {
"id": obj.book_reference.id,
"title": get_localized_text(obj.book_reference.title, request),
"slug": obj.book_reference.slug,
"publisher": pub or "",
"year_of_publication": year or "",
"number_of_volumes": num_vols,
"has_editions": getattr(obj.book_reference, "has_editions", False),
"author": {
"id": author.id,
"name": get_localized_text(author.name, request),
"slug": author.slug
} if author else None
}
return None
def get_edition_detail(self, obj):
if obj.edition:
request = self.context.get("request")
pub = obj.edition.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
return {
"id": obj.edition.id,
"edition_number": obj.edition.edition_number,
"publisher": pub,
"year_of_publication": obj.edition.year_of_publication,
"number_of_volumes": obj.edition.number_of_volumes,
}
return None
def get_book_volume_detail(self, obj):
if obj.book_volume:
return {
"id": obj.book_volume.id,
"title": obj.book_volume.title,
}
return None
def get_images(self, obj):
request = self.context.get("request")
return [
{
"id": img.id,
"image": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None),
"thumbnail": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None),
"priority": img.priority,
}
for img in obj.images.all()
]
def to_internal_value(self, data):
import json
data = safe_copy_data(data)
book_ref = data.get("book_reference")
if book_ref in ("", "none", "null"):
data["book_reference"] = None
edition = data.get("edition")
if edition in ("", "none", "null"):
data["edition"] = None
book_vol = data.get("book_volume")
if book_vol in ("", "none", "null"):
data["book_volume"] = None
delete_ids = data.get("delete_image_ids")
if delete_ids:
if isinstance(delete_ids, str):
try:
data["delete_image_ids"] = json.loads(delete_ids)
except ValueError:
data["delete_image_ids"] = [int(x) for x in delete_ids.split(",") if x.strip().isdigit()]
elif isinstance(delete_ids, list):
parsed = []
for x in delete_ids:
if isinstance(x, str) and x.strip().isdigit():
parsed.append(int(x))
elif isinstance(x, int):
parsed.append(x)
data["delete_image_ids"] = parsed
return super().to_internal_value(data)
def create(self, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().create(validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def update(self, instance, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().update(instance, validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def _handle_images(self, instance, delete_image_ids):
request = self.context.get("request")
if delete_image_ids:
instance.images.filter(id__in=delete_image_ids).delete()
if request:
new_images = request.FILES.getlist("uploaded_images") or request.FILES.getlist("uploaded_images[]") or request.FILES.getlist("images")
for img in new_images:
InterpretationReferenceImage.objects.create(reference=instance, image=img)
class AdminOriginalTextReferenceSerializer(serializers.ModelSerializer):
book_reference_detail = serializers.SerializerMethodField(read_only=True)
edition_detail = serializers.SerializerMethodField(read_only=True)
book_volume_detail = serializers.SerializerMethodField(read_only=True)
images = serializers.SerializerMethodField(read_only=True)
delete_image_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
write_only=True
)
class Meta:
model = OriginalTextReference
fields = [
"id",
"original_text",
"book_reference",
"book_reference_detail",
"edition",
"edition_detail",
"book_volume",
"book_volume_detail",
"hadith_number",
"volume",
"pages",
"address",
"url",
"images",
"delete_image_ids",
]
def get_book_reference_detail(self, obj):
if obj.book_reference:
request = self.context.get("request")
author = obj.book_reference.author
first_ed = obj.book_reference.editions.first() if hasattr(obj.book_reference, "editions") else None
pub = getattr(obj.book_reference, "publisher", None)
if pub is None and first_ed:
pub = first_ed.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
year = getattr(obj.book_reference, "year_of_publication", None)
if not year and first_ed:
year = first_ed.year_of_publication
num_vols = getattr(obj.book_reference, "number_of_volumes", None)
if num_vols is None and first_ed:
num_vols = first_ed.number_of_volumes
return {
"id": obj.book_reference.id,
"title": get_localized_text(obj.book_reference.title, request),
"slug": obj.book_reference.slug,
"publisher": pub or "",
"year_of_publication": year or "",
"number_of_volumes": num_vols,
"has_editions": getattr(obj.book_reference, "has_editions", False),
"author": {
"id": author.id,
"name": get_localized_text(author.name, request),
"slug": author.slug
} if author else None
}
return None
def get_edition_detail(self, obj):
if obj.edition:
request = self.context.get("request")
pub = obj.edition.publisher
if isinstance(pub, list):
pub = get_localized_text(pub, request)
return {
"id": obj.edition.id,
"edition_number": obj.edition.edition_number,
"publisher": pub,
"year_of_publication": obj.edition.year_of_publication,
"number_of_volumes": obj.edition.number_of_volumes,
}
return None
def get_book_volume_detail(self, obj):
if obj.book_volume:
return {
"id": obj.book_volume.id,
"title": obj.book_volume.title,
}
return None
def get_images(self, obj):
request = self.context.get("request")
return [
{
"id": img.id,
"image": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None),
"thumbnail": absolute_https_url(img.image.url, request) if request and img.image else (img.image.url if img.image else None),
"priority": img.priority,
}
for img in obj.images.all()
]
def to_internal_value(self, data):
import json
data = safe_copy_data(data)
book_ref = data.get("book_reference")
if book_ref in ("", "none", "null"):
data["book_reference"] = None
edition = data.get("edition")
if edition in ("", "none", "null"):
data["edition"] = None
book_vol = data.get("book_volume")
if book_vol in ("", "none", "null"):
data["book_volume"] = None
delete_ids = data.get("delete_image_ids")
if delete_ids:
if isinstance(delete_ids, str):
try:
data["delete_image_ids"] = json.loads(delete_ids)
except ValueError:
data["delete_image_ids"] = [int(x) for x in delete_ids.split(",") if x.strip().isdigit()]
elif isinstance(delete_ids, list):
parsed = []
for x in delete_ids:
if isinstance(x, str) and x.strip().isdigit():
parsed.append(int(x))
elif isinstance(x, int):
parsed.append(x)
data["delete_image_ids"] = parsed
return super().to_internal_value(data)
def create(self, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().create(validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def update(self, instance, validated_data):
delete_image_ids = validated_data.pop("delete_image_ids", [])
instance = super().update(instance, validated_data)
self._handle_images(instance, delete_image_ids)
return instance
def _handle_images(self, instance, delete_image_ids):
request = self.context.get("request")
if delete_image_ids:
instance.images.filter(id__in=delete_image_ids).delete()
if request:
new_images = request.FILES.getlist("uploaded_images") or request.FILES.getlist("uploaded_images[]") or request.FILES.getlist("images")
for img in new_images:
OriginalTextReferenceImage.objects.create(reference=instance, image=img)
class AdminHadisInterpretationSerializer(serializers.ModelSerializer): class AdminHadisInterpretationSerializer(serializers.ModelSerializer):
category_detail = AdminHadisCategoryBriefSerializer(source="category", read_only=True) category_detail = AdminHadisCategoryBriefSerializer(source="category", read_only=True)
references = AdminInterpretationReferenceSerializer(many=True, read_only=True)
# Writable relation fields # Writable relation fields
book_reference_id = serializers.PrimaryKeyRelatedField( book_reference_id = serializers.PrimaryKeyRelatedField(
@ -813,10 +1418,11 @@ class AdminHadisInterpretationSerializer(serializers.ModelSerializer):
"book_reference_id", "book_reference_id",
"address", "address",
"delete_image_ids", "delete_image_ids",
"references",
"created_at", "created_at",
"updated_at", "updated_at",
] ]
read_only_fields = ["id", "created_at", "updated_at"]
read_only_fields = ["id", "references", "created_at", "updated_at"]
def to_internal_value(self, data): def to_internal_value(self, data):
import json import json
@ -870,6 +1476,7 @@ class AdminHadisInterpretationSerializer(serializers.ModelSerializer):
} }
for img in images for img in images
] ]
ret["references"] = AdminInterpretationReferenceSerializer(instance.references.all(), many=True, context=self.context).data
return ret return ret
def create(self, validated_data): def create(self, validated_data):
@ -2038,6 +2645,7 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
title = serializers.JSONField(required=False, default=list) title = serializers.JSONField(required=False, default=list)
text = serializers.JSONField(required=False, default=list) text = serializers.JSONField(required=False, default=list)
translation = serializers.JSONField(required=False, default=list) translation = serializers.JSONField(required=False, default=list)
references = AdminOriginalTextReferenceSerializer(many=True, read_only=True)
# Writable relation fields # Writable relation fields
book_reference_id = serializers.PrimaryKeyRelatedField( book_reference_id = serializers.PrimaryKeyRelatedField(
@ -2069,6 +2677,7 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
"share_link", "share_link",
"embedded_in", "embedded_in",
"order", "order",
"references",
"book_reference_id", "book_reference_id",
"address", "address",
"delete_image_ids", "delete_image_ids",
@ -2104,6 +2713,12 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
import json import json
ret = super().to_representation(instance) ret = super().to_representation(instance)
ret["references"] = AdminOriginalTextReferenceSerializer(
instance.references.all().order_by("id"),
many=True,
context=self.context
).data
ref = instance.references.first() ref = instance.references.first()
ret["book_reference"] = ref.book_reference_id if ref else None ret["book_reference"] = ref.book_reference_id if ref else None

14
apps/hadis/urls.py

@ -3,7 +3,7 @@ from rest_framework.routers import SimpleRouter
from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers,HadisCategoryXMindView from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers,HadisCategoryXMindView
from .views.hadis import ( from .views.hadis import (
HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView, CategoryHadisCorrectionsView, HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView, HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView, CategoryHadisCorrectionsView, HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView,
HadisCorrectionDetailView, HadisInterpretationDetailView, HadisInterpretsListView, HadisSourceDetailsView
HadisCorrectionDetailView, HadisInterpretationDetailView, HadisInterpretsListView, HadisSourceDetailsView, HadisTagListView
) )
from .views.transmitter import ( from .views.transmitter import (
TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView, TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView,
@ -14,12 +14,12 @@ from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncVi
from .views.version import ContentReleaseSyncView, HadisDatabaseVersionView from .views.version import ContentReleaseSyncView, HadisDatabaseVersionView
from .views.info import HadisInfoView from .views.info import HadisInfoView
from .views_admin import ( from .views_admin import (
AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminCorrectionReferenceViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet,
AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminCorrectionReferenceViewSet, AdminHadisReferenceViewSet, AdminInterpretationReferenceViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet,
AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminHadisReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet, AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminHadisReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet,
AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet, AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet,
DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet, DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet,
DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet, DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet,
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet,
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminOriginalTextReferenceViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet,
AdminContentReleaseViewSet AdminContentReleaseViewSet
) )
from .views.reference_v2 import BookAuthorListView, BookAuthorDetailView, AuthorReferencesListView, BookAuthorSyncView from .views.reference_v2 import BookAuthorListView, BookAuthorDetailView, AuthorReferencesListView, BookAuthorSyncView
@ -36,7 +36,10 @@ admin_router.register(r'dovodi-sects', DovodiHadisSectViewSet, basename='dovodi-
admin_router.register(r'dovodi-categories', DovodiHadisCategoryViewSet, basename='dovodi-hadis-categories') admin_router.register(r'dovodi-categories', DovodiHadisCategoryViewSet, basename='dovodi-hadis-categories')
admin_router.register(r'corrections', AdminHadisCorrectionViewSet, basename='admin-hadis-corrections') admin_router.register(r'corrections', AdminHadisCorrectionViewSet, basename='admin-hadis-corrections')
admin_router.register(r'correction-references', AdminCorrectionReferenceViewSet, basename='admin-correction-references') admin_router.register(r'correction-references', AdminCorrectionReferenceViewSet, basename='admin-correction-references')
admin_router.register(r'hadis-references', AdminHadisReferenceViewSet, basename='admin-hadis-references')
admin_router.register(r'interpretations', AdminHadisInterpretationViewSet, basename='admin-hadis-interpretations') admin_router.register(r'interpretations', AdminHadisInterpretationViewSet, basename='admin-hadis-interpretations')
admin_router.register(r'interpretation-references', AdminInterpretationReferenceViewSet, basename='admin-interpretation-references')
admin_router.register(r'original-text-references', AdminOriginalTextReferenceViewSet, basename='admin-original-text-references')
admin_router.register(r'statuses', AdminHadisStatusViewSet, basename='admin-hadis-statuses') admin_router.register(r'statuses', AdminHadisStatusViewSet, basename='admin-hadis-statuses')
admin_router.register(r'tags', AdminHadisTagViewSet, basename='admin-hadis-tags') admin_router.register(r'tags', AdminHadisTagViewSet, basename='admin-hadis-tags')
admin_router.register(r'collections', AdminHadisCollectionViewSet, basename='admin-hadis-collections') admin_router.register(r'collections', AdminHadisCollectionViewSet, basename='admin-hadis-collections')
@ -91,6 +94,7 @@ urlpatterns = [
path('sync/database/', HadisDatabaseVersionView.as_view(), name='hadis-database-sync'), path('sync/database/', HadisDatabaseVersionView.as_view(), name='hadis-database-sync'),
path('sync/filters/', cached_view(HadisFiltersSyncAPIView.as_view()), name='hadis-filters-sync'), path('sync/filters/', cached_view(HadisFiltersSyncAPIView.as_view()), name='hadis-filters-sync'),
path('info/', cached_view(HadisInfoView.as_view()), name='hadis-info'), path('info/', cached_view(HadisInfoView.as_view()), name='hadis-info'),
path('tags/', cached_view(HadisTagListView.as_view()), name='hadis-tag-list'),
# Category paths (more specific first) # Category paths (more specific first)
path('categories/tree/', cached_view(HadisCategoryTreeNormalView.as_view()), name='hadis-category-tree-normal'), path('categories/tree/', cached_view(HadisCategoryTreeNormalView.as_view()), name='hadis-category-tree-normal'),
@ -112,7 +116,7 @@ urlpatterns = [
path('narrators/<str:narrator_slug>/students/', cached_view(NarratorStudentsView.as_view()), name='narrator-students'), path('narrators/<str:narrator_slug>/students/', cached_view(NarratorStudentsView.as_view()), name='narrator-students'),
path('narrators/<str:narrator_slug>/arguments/', cached_view(NarratorArgumentsListView.as_view()), name='narrator-arguments'), path('narrators/<str:narrator_slug>/arguments/', cached_view(NarratorArgumentsListView.as_view()), name='narrator-arguments'),
path('narrators/<str:narrator_slug>/opinions/', cached_view(TransmitterOpinionView.as_view()), name='narrator-opinions'), path('narrators/<str:narrator_slug>/opinions/', cached_view(TransmitterOpinionView.as_view()), name='narrator-opinions'),
path('narrators/<str:narrator_slug>/original_texts/', cached_view(TransmitterOriginalTextView.as_view()), name='narrator-original-texts'),
path('narrators/<str:narrator_slug>/original_texts/', TransmitterOriginalTextView.as_view(), name='narrator-original-texts'),
path('narrators/<str:narrator_slug>/', cached_view(TransmitterDetailView.as_view()), name='narrator-detail'), path('narrators/<str:narrator_slug>/', cached_view(TransmitterDetailView.as_view()), name='narrator-detail'),
path('narrators/', cached_view(TransmitterView.as_view()), name='narrators'), path('narrators/', cached_view(TransmitterView.as_view()), name='narrators'),
@ -132,7 +136,7 @@ urlpatterns = [
path('<str:hadis_slug>/transmitters/', cached_view(HadisTransmittersView.as_view()), name='hadis-transmitters'), path('<str:hadis_slug>/transmitters/', cached_view(HadisTransmittersView.as_view()), name='hadis-transmitters'),
path('<str:hadis_slug>/transmitters/layers/', cached_view(HadisLayersView.as_view()), name='hadis-layers'), path('<str:hadis_slug>/transmitters/layers/', cached_view(HadisLayersView.as_view()), name='hadis-layers'),
path('<str:hadis_slug>/corrections/', HadisCorrectionsView.as_view(), name='hadis-corrections'), path('<str:hadis_slug>/corrections/', HadisCorrectionsView.as_view(), name='hadis-corrections'),
path('<str:hadis_slug>/interprets/', cached_view(HadisInterpretsListView.as_view()), name='hadis-interprets'),
path('<str:hadis_slug>/interprets/', HadisInterpretsListView.as_view(), name='hadis-interprets'),
path('<str:hadis_slug>/', HadisBasicView.as_view(), name='hadis-basic'), # ← Least specific LAST path('<str:hadis_slug>/', HadisBasicView.as_view(), name='hadis-basic'), # ← Least specific LAST
# path('test/test-deploy',test_deploy , name='test'), # path('test/test-deploy',test_deploy , name='test'),

22
apps/hadis/views/category.py

@ -475,6 +475,14 @@ class CategoriesView(ListAPIView):
).filter( ).filter(
Q(active_hadis_count__gt=0) | Q(children_total_count__gt=0) | Q(source_type=HadisCategory.SourceType.QURAN) Q(active_hadis_count__gt=0) | Q(children_total_count__gt=0) | Q(source_type=HadisCategory.SourceType.QURAN)
) )
search_query = (self.request.query_params.get('search') or '').strip()
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(slug__icontains=search_query) |
Q(description__icontains=search_query)
)
is_root = self.request.query_params.get('is_root', '').lower() is_root = self.request.query_params.get('is_root', '').lower()
if is_root in ['true', '1']: if is_root in ['true', '1']:
queryset = queryset.filter(parent__isnull=True) queryset = queryset.filter(parent__isnull=True)
@ -504,11 +512,23 @@ class CategoriesBySectView(ListAPIView):
).filter( ).filter(
Q(active_hadis_count__gt=0) | Q(children_total_count__gt=0) | Q(source_type=HadisCategory.SourceType.QURAN) Q(active_hadis_count__gt=0) | Q(children_total_count__gt=0) | Q(source_type=HadisCategory.SourceType.QURAN)
) )
is_root = self.request.query_params.get('is_root', 'true').lower()
search_query = (self.request.query_params.get('search') or '').strip()
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(slug__icontains=search_query) |
Q(description__icontains=search_query)
)
is_root_param = self.request.query_params.get('is_root', None)
if is_root_param is not None:
is_root = is_root_param.lower()
if is_root in ['true', '1']: if is_root in ['true', '1']:
queryset = queryset.filter(parent__isnull=True) queryset = queryset.filter(parent__isnull=True)
elif is_root in ['false', '0']: elif is_root in ['false', '0']:
queryset = queryset.filter(parent__isnull=False) queryset = queryset.filter(parent__isnull=False)
elif not search_query:
queryset = queryset.filter(parent__isnull=True)
return apply_category_ordering_and_counts(queryset, self.request) return apply_category_ordering_and_counts(queryset, self.request)

180
apps/hadis/views/hadis.py

@ -10,15 +10,25 @@ from rest_framework.response import Response
from django.db.models import Count from django.db.models import Count
from django.db.models import Prefetch from django.db.models import Prefetch
from ..serializers.category import get_localized_text from ..serializers.category import get_localized_text
from ..models import Transmitters, HadisCategory, Hadis, HadisCollection,HadisTransmitter , HadisCorrection ,HadisReference, HadisStatus ,ReferenceImage, CorrectionReference, HadisInterpretation, InterpretationReference
from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory, NarratorLayerSerializer , PinnedHadisCollectionSerializer, HadisSourceDetailsSerializer
from ..models import Transmitters, HadisCategory, Hadis, HadisCollection,HadisTransmitter , HadisCorrection ,HadisReference, HadisStatus ,ReferenceImage, CorrectionReference, HadisInterpretation, InterpretationReference, HadisTag
from ..serializers import HadisListSerializer, HadisBasicSerializer, HadisDetailSerializer, HadisCollectionListSerializer, HadisSyncSerializer,HadisCorrectionSerializer,HadisTransmitterListSerializer , SimpleCategory, NarratorLayerSerializer , PinnedHadisCollectionSerializer, HadisSourceDetailsSerializer, HadisTagSerializer
from ..docs import arguments_filters_swagger ,hadis_list_swagger, hadis_detail_swagger, hadis_collections_swagger, hadis_sync_swagger, hadis_transmitters_swagger, hadis_corrections_swagger, hadis_basic_swagger, hadis_main_list_swagger, hadis_layers_swagger from ..docs import arguments_filters_swagger ,hadis_list_swagger, hadis_detail_swagger, hadis_collections_swagger, hadis_sync_swagger, hadis_transmitters_swagger, hadis_corrections_swagger, hadis_basic_swagger, hadis_main_list_swagger, hadis_layers_swagger
from django.db.models import Q from django.db.models import Q
from ..serializers.category import get_localized_text
from rest_framework import status from rest_framework import status
from utils.mixins import CanonicalSlugViewSetMixin, Gone410ViewMixin from utils.mixins import CanonicalSlugViewSetMixin, Gone410ViewMixin
class HadisTagListView(ListAPIView):
"""
API view to list active Hadis Tags
"""
serializer_class = HadisTagSerializer
pagination_class = NoPagination
permission_classes = [AllowAny]
def get_queryset(self):
return HadisTag.objects.filter(status=True).order_by('id')
class PinnedHadisCollectionListView(ListAPIView): class PinnedHadisCollectionListView(ListAPIView):
""" """
API view to list pinned hadis collections with metadata info API view to list pinned hadis collections with metadata info
@ -293,6 +303,14 @@ class HadisListView(ListAPIView):
for status in HadisStatus.objects.all().order_by('order') for status in HadisStatus.objects.all().order_by('order')
] ]
tags_list = [
{
"id": tag.id,
"title": get_localized_text(tag.title, request=request, language_code=lang) or "",
}
for tag in HadisTag.objects.filter(status=True).order_by('id')
]
# 6. Inject it into the response data # 6. Inject it into the response data
if isinstance(response.data, dict): if isinstance(response.data, dict):
ordered_data = {} ordered_data = {}
@ -303,8 +321,9 @@ class HadisListView(ListAPIView):
ordered_data['current_category'] = category_data ordered_data['current_category'] = category_data
ordered_data['quran_data'] = quran_data ordered_data['quran_data'] = quran_data
# 👇 تزریق لیست استاتوس‌ها قبل از نتایج
# 👇 تزریق لیست استاتوس‌ها و تگ‌ها قبل از نتایج
ordered_data['statuses'] = statuses_list ordered_data['statuses'] = statuses_list
ordered_data['tags'] = tags_list
if 'results' in response.data: if 'results' in response.data:
ordered_data['results'] = response.data['results'] ordered_data['results'] = response.data['results']
@ -323,10 +342,18 @@ class HadisListView(ListAPIView):
).annotate( ).annotate(
layer_count=Count('transmitters__narrator_layer', distinct=True) layer_count=Count('transmitters__narrator_layer', distinct=True)
).select_related('category').prefetch_related( ).select_related('category').prefetch_related(
'tags',
Prefetch( Prefetch(
'references', 'references',
queryset=HadisReference.objects.prefetch_related(
Prefetch('images', queryset=ReferenceImage.objects.order_by('priority'))
queryset=HadisReference.objects.select_related(
'book_reference',
'book_reference__author',
'edition',
'book_volume',
'book_volume__edition'
).prefetch_related(
Prefetch('images', queryset=ReferenceImage.objects.order_by('priority')),
'book_reference__editions'
) )
) )
) )
@ -338,6 +365,10 @@ class HadisListView(ListAPIView):
search_conditions |= Q(title__icontains=search_query) search_conditions |= Q(title__icontains=search_query)
search_conditions |= Q(title_narrator__icontains=search_query) search_conditions |= Q(title_narrator__icontains=search_query)
search_conditions |= Q(translation__icontains=search_query) search_conditions |= Q(translation__icontains=search_query)
search_conditions |= Q(tags__title__icontains=search_query)
search_conditions |= Q(references__book_reference__title__icontains=search_query)
search_conditions |= Q(transmitters__transmitter__full_name__icontains=search_query)
search_conditions |= Q(transmitters__transmitter__known_as__icontains=search_query)
queryset = queryset.filter(search_conditions) queryset = queryset.filter(search_conditions)
# 👇 2. Apply Status Filter (Supports multiple comma-separated slugs) # 👇 2. Apply Status Filter (Supports multiple comma-separated slugs)
@ -368,6 +399,20 @@ class HadisListView(ListAPIView):
author_q |= Q(references__book_reference__author_id__in=numeric_ids) author_q |= Q(references__book_reference__author_id__in=numeric_ids)
queryset = queryset.filter(author_q) queryset = queryset.filter(author_q)
# 👇 6. Apply Tag Filter (Supports comma-separated IDs or titles)
tag_filter = self.request.query_params.get('tag', None) or self.request.query_params.get('tags', None)
if tag_filter:
tag_items = [t.strip() for t in tag_filter.split(',') if t.strip()]
numeric_tag_ids = [int(t) for t in tag_items if t.isdigit()]
non_numeric_tags = [t for t in tag_items if not t.isdigit()]
tag_q = Q()
if numeric_tag_ids:
tag_q |= Q(tags__id__in=numeric_tag_ids)
for t_val in non_numeric_tags:
tag_q |= Q(tags__title__icontains=t_val)
if tag_q:
queryset = queryset.filter(tag_q)
# Filter by bookmarks if provided # Filter by bookmarks if provided
is_bookmark = self.request.query_params.get('is_bookmark', '').lower() is_bookmark = self.request.query_params.get('is_bookmark', '').lower()
if is_bookmark == 'true' and self.request.user.is_authenticated: if is_bookmark == 'true' and self.request.user.is_authenticated:
@ -406,7 +451,7 @@ class HadisListView(ListAPIView):
class HadisMainListView(ListAPIView): class HadisMainListView(ListAPIView):
""" """
API view to list Hadis by category_id
API view to list Hadis globally with comprehensive filtering
""" """
serializer_class = HadisListSerializer serializer_class = HadisListSerializer
authentication_classes = [TokenAuthentication] authentication_classes = [TokenAuthentication]
@ -418,31 +463,94 @@ class HadisMainListView(ListAPIView):
return self.list(request, *args, **kwargs) return self.list(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = Hadis.objects.select_related('category__sect', 'hadis_status').prefetch_related(
queryset = Hadis.objects.filter(status=True).select_related(
'category__sect', 'hadis_status'
).annotate(
layer_count=Count('transmitters__narrator_layer', distinct=True)
).prefetch_related(
'tags',
Prefetch( Prefetch(
'references', 'references',
queryset=HadisReference.objects.select_related('book_reference__author').prefetch_related(
Prefetch('images', queryset=ReferenceImage.objects.order_by('priority'))
queryset=HadisReference.objects.select_related(
'book_reference',
'book_reference__author',
'edition',
'book_volume',
'book_volume__edition'
).prefetch_related(
Prefetch('images', queryset=ReferenceImage.objects.order_by('priority')),
'book_reference__editions'
) )
) )
) )
# Get search parameters # Get search parameters
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
sect_filter = self.request.query_params.get('sect', None) or self.request.query_params.get('sect_type', None)
source_type_filter = self.request.query_params.get('source_type', None)
status_filter = self.request.query_params.get('status', None) status_filter = self.request.query_params.get('status', None)
category_filter = self.request.query_params.get('category', None)
author_filter = self.request.query_params.get('author', None) or self.request.query_params.get('source_author', None)
source_filter = self.request.query_params.get('source', None)
category_filter = self.request.query_params.get('category', None) or self.request.query_params.get('category_slug', None)
tag_filter = self.request.query_params.get('tag', None) or self.request.query_params.get('tags', None)
# Apply search filter # Apply search filter
if search_query: if search_query:
queryset = self.apply_search_filter(queryset, search_query) queryset = self.apply_search_filter(queryset, search_query)
# Apply status filter
# Apply sect filter (e.g. shia, sunni)
if sect_filter:
sects = [s.strip().lower() for s in sect_filter.split(',') if s.strip()]
if sects:
queryset = queryset.filter(category__sect__sect_type__in=sects)
# Apply source_type filter (e.g. hadith, quran, history, fatwa, quote)
if source_type_filter:
source_types = [st.strip().lower() for st in source_type_filter.split(',') if st.strip()]
if source_types:
queryset = queryset.filter(category__source_type__in=source_types)
# Apply status filter (by slug or title)
if status_filter: if status_filter:
queryset = queryset.filter(hadis_status__title__icontains=status_filter)
status_items = [s.strip() for s in status_filter.split(',') if s.strip()]
queryset = queryset.filter(
Q(hadis_status__slug__in=status_items) | Q(hadis_status__title__icontains=status_filter)
)
# Apply author filter
if author_filter:
author_slugs = [a.strip() for a in author_filter.split(',') if a.strip()]
author_q = Q(references__book_reference__author__slug__in=author_slugs)
numeric_ids = [int(a) for a in author_slugs if a.isdigit()]
if numeric_ids:
author_q |= Q(references__book_reference__author_id__in=numeric_ids)
queryset = queryset.filter(author_q)
# Apply source filter (reference books)
if source_filter:
source_slugs = [s.strip() for s in source_filter.split(',') if s.strip()]
if source_slugs:
queryset = queryset.filter(references__book_reference__slug__in=source_slugs)
# Apply category filter # Apply category filter
if category_filter: if category_filter:
queryset = queryset.filter(category__title__icontains=category_filter)
cat_items = [c.strip() for c in category_filter.split(',') if c.strip()]
queryset = queryset.filter(
Q(category__slug__in=cat_items) | Q(category__title__icontains=category_filter)
)
# Apply tag filter
if tag_filter:
tag_items = [t.strip() for t in tag_filter.split(',') if t.strip()]
numeric_tag_ids = [int(t) for t in tag_items if t.isdigit()]
non_numeric_tags = [t for t in tag_items if not t.isdigit()]
tag_q = Q()
if numeric_tag_ids:
tag_q |= Q(tags__id__in=numeric_tag_ids)
for t_val in non_numeric_tags:
tag_q |= Q(tags__title__icontains=t_val)
if tag_q:
queryset = queryset.filter(tag_q)
# Filter by bookmarks if provided # Filter by bookmarks if provided
is_bookmark = self.request.query_params.get('is_bookmark', '').lower() is_bookmark = self.request.query_params.get('is_bookmark', '').lower()
@ -455,7 +563,23 @@ class HadisMainListView(ListAPIView):
).values_list('content_id', flat=True) ).values_list('content_id', flat=True)
queryset = queryset.filter(id__in=bookmarked_ids) queryset = queryset.filter(id__in=bookmarked_ids)
return queryset
return queryset.distinct()
def get_serializer_context(self):
"""Add user bookmarks to serializer context to avoid caching issues"""
context = super().get_serializer_context()
user = self.request.user
if user.is_authenticated:
from apps.bookmark.models.bookmark import Bookmark
user_bookmarks = Bookmark.objects.filter(
user=user,
service=Bookmark.ServiceChoices.HADITH,
status=True
).values_list('content_id', flat=True)
context['user_bookmarked_hadis_ids'] = set(user_bookmarks)
else:
context['user_bookmarked_hadis_ids'] = set()
return context
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
queryset = self.get_queryset() queryset = self.get_queryset()
@ -465,19 +589,7 @@ class HadisMainListView(ListAPIView):
if page is not None: if page is not None:
serializer = self.get_serializer(page, many=True) serializer = self.get_serializer(page, many=True)
paginated_response = self.get_paginated_response(serializer.data) paginated_response = self.get_paginated_response(serializer.data)
# Get category titles
category_titles = self.get_category_titles(request)
# Get status titles
status_titles = self.get_status_titles(request)
# Modify the paginated response to include our custom data
response_data = paginated_response.data
# response_data['category_titles'] = self.get_cached_category_titles(request)
# response_data['status_titles'] = self.get_cached_status_titles(request)
return Response(response_data)
return Response(paginated_response.data)
# Fallback for when pagination is disabled # Fallback for when pagination is disabled
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
@ -486,8 +598,6 @@ class HadisMainListView(ListAPIView):
'results': serializer.data 'results': serializer.data
}) })
return Response(response_data)
def get_category_titles(self,request): def get_category_titles(self,request):
"""Get list of category titles based on language""" """Get list of category titles based on language"""
from ..models import HadisCategory from ..models import HadisCategory
@ -516,7 +626,7 @@ class HadisMainListView(ListAPIView):
def apply_search_filter(self, queryset, search_query): def apply_search_filter(self, queryset, search_query):
""" """
Apply search filter across multiple fields including JSONFields. Apply search filter across multiple fields including JSONFields.
Searches in: title, title_narrator, text, translation
Searches in: title, title_narrator, text, translation, tags, book references, transmitters
""" """
from django.db.models import Q from django.db.models import Q
@ -528,6 +638,10 @@ class HadisMainListView(ListAPIView):
search_conditions |= Q(title__icontains=search_query) search_conditions |= Q(title__icontains=search_query)
search_conditions |= Q(title_narrator__icontains=search_query) search_conditions |= Q(title_narrator__icontains=search_query)
search_conditions |= Q(translation__icontains=search_query) search_conditions |= Q(translation__icontains=search_query)
search_conditions |= Q(tags__title__icontains=search_query)
search_conditions |= Q(references__book_reference__title__icontains=search_query)
search_conditions |= Q(transmitters__transmitter__full_name__icontains=search_query)
search_conditions |= Q(transmitters__transmitter__known_as__icontains=search_query)
return queryset.filter(search_conditions) return queryset.filter(search_conditions)
@ -1273,7 +1387,9 @@ class HadisSourceDetailsView(RetrieveAPIView):
).prefetch_related( ).prefetch_related(
Prefetch( Prefetch(
'references', 'references',
queryset=HadisReference.objects.prefetch_related(
queryset=HadisReference.objects.select_related(
'book_reference', 'book_reference__author', 'edition', 'book_volume'
).prefetch_related(
Prefetch('images', queryset=ReferenceImage.objects.order_by('priority')) Prefetch('images', queryset=ReferenceImage.objects.order_by('priority'))
) )
) )

21
apps/hadis/views/transmitter.py

@ -12,8 +12,8 @@ from drf_yasg.utils import swagger_auto_schema
from ..models import Transmitters, HadisTransmitter from ..models import Transmitters, HadisTransmitter
from ..serializers import NarratorRelationSerializer from ..serializers import NarratorRelationSerializer
from utils.pagination import StandardResultsSetPagination from utils.pagination import StandardResultsSetPagination
from ..models import Transmitters , TransmitterOpinion, TransmitterOriginalText, Hadis
from ..serializers import TransmitterSerializer , TransmitterDetailSerializer, TransmitterSyncSerializer,TransmitterOpinionSerializer, TransmitterOriginalTextSerializer, HadisListSerializer
from ..models import Transmitters, TransmitterOpinion, TransmitterOriginalText, Hadis, OriginalTextReference, OriginalTextReferenceImage
from ..serializers import TransmitterSerializer, TransmitterDetailSerializer, TransmitterSyncSerializer, TransmitterOpinionSerializer, TransmitterOriginalTextSerializer, TransmitterOriginalTextDetailSerializer, HadisListSerializer
from ..docs import transmitter_filters_swagger,transmitter_list_swagger, transmitter_detail_swagger, transmitter_sync_swagger, transmitter_opinion_swagger, transmitter_original_text_swagger from ..docs import transmitter_filters_swagger,transmitter_list_swagger, transmitter_detail_swagger, transmitter_sync_swagger, transmitter_opinion_swagger, transmitter_original_text_swagger
from utils.pagination import NoPagination from utils.pagination import NoPagination
from .category import get_localized_text from .category import get_localized_text
@ -102,7 +102,7 @@ class TransmitterOpinionView(ListAPIView):
class TransmitterOriginalTextView(ListAPIView): class TransmitterOriginalTextView(ListAPIView):
serializer_class = TransmitterOriginalTextSerializer
serializer_class = TransmitterOriginalTextDetailSerializer
pagination_class = StandardResultsSetPagination pagination_class = StandardResultsSetPagination
@transmitter_original_text_swagger @transmitter_original_text_swagger
@ -117,8 +117,19 @@ class TransmitterOriginalTextView(ListAPIView):
).select_related( ).select_related(
'transmitter' 'transmitter'
).prefetch_related( ).prefetch_related(
'references__images',
'references__book_reference',
Prefetch(
'references',
queryset=OriginalTextReference.objects.select_related(
'book_reference',
'book_reference__author',
'edition',
'book_volume',
'book_volume__edition'
).prefetch_related(
Prefetch('images', queryset=OriginalTextReferenceImage.objects.order_by('priority')),
'book_reference__editions'
)
)
) )

56
apps/hadis/views_admin.py

@ -13,7 +13,7 @@ from .models import (
Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, HadisReference, ReferenceImage, Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, HadisReference, ReferenceImage,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
CorrectionReference,
CorrectionReference, InterpretationReference, OriginalTextReference,
ContentRelease, ContentRelease,
CategoryQuranVerse, CategoryQuranVerse,
) )
@ -24,6 +24,9 @@ from .serializers.serializers_admin import (
AdminHadisCategoryArchiveDetailSerializer, AdminHadisCategoryArchiveDetailSerializer,
AdminHadisCorrectionSerializer, AdminHadisCorrectionSerializer,
AdminCorrectionReferenceSerializer, AdminCorrectionReferenceSerializer,
AdminInterpretationReferenceSerializer,
AdminOriginalTextReferenceSerializer,
AdminHadisReferenceSerializer,
AdminHadisDetailSerializer, AdminHadisDetailSerializer,
AdminHadisInterpretationSerializer, AdminHadisInterpretationSerializer,
AdminHadisCollectionSerializer, AdminHadisCollectionSerializer,
@ -466,6 +469,57 @@ class AdminCorrectionReferenceViewSet(ModelViewSet):
return qs.order_by("id") return qs.order_by("id")
class AdminHadisReferenceViewSet(ModelViewSet):
serializer_class = AdminHadisReferenceSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
parser_classes = (MultiPartParser, FormParser, JSONParser)
pagination_class = OptionalPagination
def get_queryset(self):
qs = HadisReference.objects.all().select_related(
"book_reference__author", "edition", "book_volume"
).prefetch_related("images")
hadis_id = self.request.query_params.get("hadis_id")
if hadis_id:
qs = qs.filter(hadis_id=hadis_id)
return qs.order_by("id")
class AdminInterpretationReferenceViewSet(ModelViewSet):
serializer_class = AdminInterpretationReferenceSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
parser_classes = (MultiPartParser, FormParser, JSONParser)
pagination_class = OptionalPagination
def get_queryset(self):
qs = InterpretationReference.objects.all().select_related(
"book_reference__author", "edition", "book_volume"
).prefetch_related("images")
interpretation_id = self.request.query_params.get("interpretation_id") or self.request.query_params.get("interpretation")
if interpretation_id:
qs = qs.filter(interpretation_id=interpretation_id)
return qs.order_by("id")
class AdminOriginalTextReferenceViewSet(ModelViewSet):
serializer_class = AdminOriginalTextReferenceSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
parser_classes = (MultiPartParser, FormParser, JSONParser)
pagination_class = OptionalPagination
def get_queryset(self):
qs = OriginalTextReference.objects.all().select_related(
"book_reference__author", "edition", "book_volume"
).prefetch_related("images")
original_text_id = self.request.query_params.get("original_text_id") or self.request.query_params.get("original_text")
if original_text_id:
qs = qs.filter(original_text_id=original_text_id)
return qs.order_by("id")
class AdminHadisInterpretationViewSet(ModelViewSet): class AdminHadisInterpretationViewSet(ModelViewSet):
serializer_class = AdminHadisInterpretationSerializer serializer_class = AdminHadisInterpretationSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin] permission_classes = [IsAuthenticated, IsSuperAdmin]

Loading…
Cancel
Save