Browse Source

feat(hadis): add multi-reference endpoints and serializers for transmitter original texts with scan images and hadith_number

master
Mohsen Taba 4 days ago
parent
commit
82ecac802f
  1. 18
      apps/hadis/migrations/0046_originaltextreference_hadith_number.py
  2. 1
      apps/hadis/models/transmitter.py
  3. 161
      apps/hadis/serializers/serializers_admin.py
  4. 3
      apps/hadis/urls.py
  5. 20
      apps/hadis/views_admin.py

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'),
),
]

1
apps/hadis/models/transmitter.py

@ -582,6 +582,7 @@ class OriginalTextReference(models.Model):
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'))
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'))
pages = models.CharField(max_length=100, null=True, blank=True) # 👈 تغییر page به pages

161
apps/hadis/serializers/serializers_admin.py

@ -1229,6 +1229,159 @@ class AdminInterpretationReferenceSerializer(serializers.ModelSerializer):
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):
category_detail = AdminHadisCategoryBriefSerializer(source="category", read_only=True)
references = AdminInterpretationReferenceSerializer(many=True, read_only=True)
@ -2492,6 +2645,7 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
title = serializers.JSONField(required=False, default=list)
text = serializers.JSONField(required=False, default=list)
translation = serializers.JSONField(required=False, default=list)
references = AdminOriginalTextReferenceSerializer(many=True, read_only=True)
# Writable relation fields
book_reference_id = serializers.PrimaryKeyRelatedField(
@ -2523,6 +2677,7 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
"share_link",
"embedded_in",
"order",
"references",
"book_reference_id",
"address",
"delete_image_ids",
@ -2558,6 +2713,12 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
import json
ret = super().to_representation(instance)
ret["references"] = AdminOriginalTextReferenceSerializer(
instance.references.all().order_by("id"),
many=True,
context=self.context
).data
ref = instance.references.first()
ret["book_reference"] = ref.book_reference_id if ref else None

3
apps/hadis/urls.py

@ -19,7 +19,7 @@ from .views_admin import (
AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet,
DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet,
DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet,
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet,
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminOriginalTextReferenceViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet,
AdminContentReleaseViewSet
)
from .views.reference_v2 import BookAuthorListView, BookAuthorDetailView, AuthorReferencesListView, BookAuthorSyncView
@ -39,6 +39,7 @@ admin_router.register(r'correction-references', AdminCorrectionReferenceViewSet,
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'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'tags', AdminHadisTagViewSet, basename='admin-hadis-tags')
admin_router.register(r'collections', AdminHadisCollectionViewSet, basename='admin-hadis-collections')

20
apps/hadis/views_admin.py

@ -13,7 +13,7 @@ from .models import (
Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, HadisReference, ReferenceImage,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
CorrectionReference, InterpretationReference,
CorrectionReference, InterpretationReference, OriginalTextReference,
ContentRelease,
CategoryQuranVerse,
)
@ -25,6 +25,7 @@ from .serializers.serializers_admin import (
AdminHadisCorrectionSerializer,
AdminCorrectionReferenceSerializer,
AdminInterpretationReferenceSerializer,
AdminOriginalTextReferenceSerializer,
AdminHadisReferenceSerializer,
AdminHadisDetailSerializer,
AdminHadisInterpretationSerializer,
@ -502,6 +503,23 @@ class AdminInterpretationReferenceViewSet(ModelViewSet):
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):
serializer_class = AdminHadisInterpretationSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]

Loading…
Cancel
Save