Browse Source

feat(corrections): apis updated for hadith corrections

master
Mohsen Taba 2 weeks ago
parent
commit
f56736a23c
  1. 189
      apps/hadis/serializers/hadis.py
  2. 158
      apps/hadis/serializers/serializers_admin.py
  3. 3
      apps/hadis/signals.py
  4. 5
      apps/hadis/urls.py
  5. 19
      apps/hadis/views_admin.py

189
apps/hadis/serializers/hadis.py

@ -1379,9 +1379,21 @@ class NarratorRelationSerializer(serializers.ModelSerializer):
# --- سریالایزرهای کمکی تصاویر رفرنس ---
class CorrectionReferenceImageSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
thumbnail = serializers.SerializerMethodField()
class Meta:
model = CorrectionReferenceImage
fields = ['id', 'image', 'priority']
fields = ['id', 'image', 'thumbnail', 'priority']
def get_image(self, obj):
if obj.image:
request = self.context.get('request')
return absolute_https_url(obj.image.url, request) if request else absolute_https_url(obj.image.url)
return None
def get_thumbnail(self, obj):
return self.get_image(obj)
class InterpretationReferenceImageSerializer(serializers.ModelSerializer):
class Meta:
@ -1417,11 +1429,22 @@ class BaseDetailedReferenceSerializer(serializers.ModelSerializer):
return []
def get_edition_info(self, obj):
if obj.edition:
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": obj.edition.id,
"edition_number": obj.edition.edition_number,
"publisher": obj.edition.publisher
"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
@ -1434,10 +1457,164 @@ class BaseDetailedReferenceSerializer(serializers.ModelSerializer):
return None
class DetailedCorrectionReferenceSerializer(BaseDetailedReferenceSerializer):
book = serializers.SerializerMethodField()
author = serializers.SerializerMethodField()
edition = serializers.SerializerMethodField()
open_book_url = serializers.SerializerMethodField()
citation_summary = serializers.SerializerMethodField()
images = CorrectionReferenceImageSerializer(many=True, read_only=True)
images_count = serializers.SerializerMethodField()
class Meta:
model = CorrectionReference
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',
]
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
if num_vols is None and hasattr(obj.book_reference, "volumes"):
num_vols = obj.book_reference.volumes.count() or None
return {
'id': obj.book_reference.id,
'slug': obj.book_reference.slug,
'title': get_localized_text(obj.book_reference.title, request),
'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)
}
return None
def get_edition(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_open_book_url(self, obj):
request = self.context.get('request')
if obj.url:
return obj.url
if obj.book_volume and obj.book_volume.file:
return absolute_https_url(obj.book_volume.file.url, request) if request else obj.book_volume.file.url
if obj.edition and obj.edition.source_url:
return obj.edition.source_url
if obj.book_reference and obj.book_reference.slug:
return f"/arguments/sources/{obj.book_reference.slug}"
return None
volume = serializers.SerializerMethodField()
pages = serializers.SerializerMethodField()
hadith_number = serializers.SerializerMethodField()
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_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()
class DetailedInterpretationReferenceSerializer(BaseDetailedReferenceSerializer):
images = InterpretationReferenceImageSerializer(many=True, read_only=True)

158
apps/hadis/serializers/serializers_admin.py

@ -13,7 +13,7 @@ from ..models import (
OriginalTextReference, OriginalTextReferenceImage,
ContentRelease
)
from .category import LocalizedField
from .category import LocalizedField, get_localized_text
def safe_copy_data(data):
@ -489,11 +489,163 @@ def save_relation_reference_data(instance, validated_data, request=None, is_corr
InterpretationReferenceImage.objects.create(reference=ref, image=img)
class AdminCorrectionReferenceSerializer(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 = CorrectionReference
fields = [
"id",
"correction",
"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:
CorrectionReferenceImage.objects.create(reference=instance, image=img)
class AdminHadisCorrectionSerializer(serializers.ModelSerializer):
hadis_detail = AdminHadisBriefSerializer(source="hadis", read_only=True)
bookmark_count = serializers.IntegerField(source="annotated_bookmark_count", read_only=True)
references = AdminCorrectionReferenceSerializer(many=True, read_only=True)
# Writable relation fields
book_reference_id = serializers.PrimaryKeyRelatedField(
@ -529,10 +681,11 @@ class AdminHadisCorrectionSerializer(serializers.ModelSerializer):
"book_reference_id",
"address",
"delete_image_ids",
"references",
"created_at",
"updated_at",
]
read_only_fields = ["id", "slug", "bookmark_count", "created_at", "updated_at"]
read_only_fields = ["id", "slug", "bookmark_count", "references", "created_at", "updated_at"]
def to_internal_value(self, data):
import json
@ -586,6 +739,7 @@ class AdminHadisCorrectionSerializer(serializers.ModelSerializer):
}
for img in images
]
ret["references"] = AdminCorrectionReferenceSerializer(instance.references.all(), many=True, context=self.context).data
return ret
def create(self, validated_data):

3
apps/hadis/signals.py

@ -12,7 +12,8 @@ TARGET_MODELS = [
Hadis, HadisCategory, HadisCollection, HadisCorrection, HadisInCollection, HadisReference,
HadisSect, HadisStatus, HadisTag, HadisTransmitter, Transmitters, TransmitterOpinion, TransmitterReliability,
TransmitterOriginalText, ReferenceImage, BookReference, BookAttribute, BookAuthor, BookReferenceImage,
NarratorLayer, OpinionStatus, ContentRelease
NarratorLayer, OpinionStatus, ContentRelease,
CorrectionReference, BookEdition, BookVolume, CorrectionReferenceImage
]

5
apps/hadis/urls.py

@ -14,7 +14,7 @@ from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncVi
from .views.version import ContentReleaseSyncView
from .views.info import HadisInfoView
from .views_admin import (
AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet,
AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminCorrectionReferenceViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet,
AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminHadisReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet,
AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet,
DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet,
@ -37,6 +37,7 @@ admin_router.register(r'quran-verses', AdminCategoryQuranVerseViewSet, basename=
admin_router.register(r'dovodi-sects', DovodiHadisSectViewSet, basename='dovodi-hadis-sects')
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'correction-references', AdminCorrectionReferenceViewSet, basename='admin-correction-references')
admin_router.register(r'interpretations', AdminHadisInterpretationViewSet, basename='admin-hadis-interpretations')
admin_router.register(r'statuses', AdminHadisStatusViewSet, basename='admin-hadis-statuses')
admin_router.register(r'tags', AdminHadisTagViewSet, basename='admin-hadis-tags')
@ -141,7 +142,7 @@ urlpatterns = [
# Interpretation Detail Endpoint (تفسیر بر اساس آی‌دی)
path('interpretations/<str:interpretation_slug>/', cached_view(HadisInterpretationDetailView.as_view()), name='interpretation-detail'),
# Correction Detail Endpoint (تصحیح بر اساس اسلاگ)
path('corrections/<str:correction_slug>/', cached_view(HadisCorrectionDetailView.as_view()), name='correction-detail'),
path('corrections/<str:correction_slug>/', HadisCorrectionDetailView.as_view(), name='correction-detail'),
# Original Text Detail Endpoint (متن اصلی راوی بر اساس اسلاگ)
path('original-texts/<str:original_text_slug>/', cached_view(TransmitterOriginalTextDetailView.as_view()), name='original-text-detail'),

19
apps/hadis/views_admin.py

@ -13,6 +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,
ContentRelease
)
from .serializers.serializers_admin import (
@ -21,6 +22,7 @@ from .serializers.serializers_admin import (
AdminHadisCategoryArchiveSerializer,
AdminHadisCategoryArchiveDetailSerializer,
AdminHadisCorrectionSerializer,
AdminCorrectionReferenceSerializer,
AdminHadisDetailSerializer,
AdminHadisInterpretationSerializer,
AdminHadisCollectionSerializer,
@ -425,6 +427,23 @@ class AdminHadisCorrectionViewSet(ModelViewSet):
return Response({"status": "success"})
class AdminCorrectionReferenceViewSet(ModelViewSet):
serializer_class = AdminCorrectionReferenceSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
parser_classes = (MultiPartParser, FormParser, JSONParser)
pagination_class = OptionalPagination
def get_queryset(self):
qs = CorrectionReference.objects.all().select_related(
"book_reference__author", "edition", "book_volume"
).prefetch_related("images")
correction_id = self.request.query_params.get("correction_id")
if correction_id:
qs = qs.filter(correction_id=correction_id)
return qs.order_by("id")
class AdminHadisInterpretationViewSet(ModelViewSet):
serializer_class = AdminHadisInterpretationSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]

Loading…
Cancel
Save