You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8009 lines
162 KiB
8009 lines
162 KiB
from rest_framework import serializers
|
|
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from django.db.models import Exists, OuterRef, Q
|
|
|
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
|
|
|
|
|
|
from utils.image_compression import maybe_compress_uploaded_file
|
|
|
|
from utils import absolute_https_url
|
|
|
|
from ..models import (
|
|
|
|
Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, ReferenceImage,
|
|
|
|
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookPublisher, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
|
|
|
|
Transmitters, TransmitterReliability, TransmitterRelative, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText,
|
|
|
|
City, Country, Kunya, TransmitterFeature, TransmitterProfession, SahabiStatus, Madhhab, RelationType, YearValue, NarratorDate,
|
|
|
|
CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage,
|
|
|
|
OriginalTextReference, OriginalTextReferenceImage,
|
|
|
|
ContentRelease
|
|
|
|
)
|
|
|
|
from .category import LocalizedField, get_localized_text
|
|
|
|
|
|
|
|
|
|
|
|
def safe_copy_data(data):
|
|
|
|
if hasattr(data, "lists"):
|
|
|
|
new_data = {}
|
|
|
|
for key, values in data.lists():
|
|
|
|
if key in ["subject_area", "authors", "book_references", "delete_image_ids", "uploaded_images"] or key.endswith("[]"):
|
|
|
|
new_data[key] = values
|
|
|
|
elif len(values) > 1:
|
|
|
|
new_data[key] = values
|
|
|
|
else:
|
|
|
|
new_data[key] = values[0] if values else None
|
|
|
|
return new_data
|
|
|
|
elif hasattr(data, "copy"):
|
|
|
|
return data.copy()
|
|
|
|
else:
|
|
|
|
return dict(data)
|
|
|
|
|
|
|
|
|
|
|
|
class AbsoluteImageField(serializers.ImageField):
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
uploaded = super().to_internal_value(data)
|
|
|
|
if not uploaded:
|
|
|
|
return uploaded
|
|
|
|
|
|
|
|
compressed_bytes = maybe_compress_uploaded_file(uploaded)
|
|
|
|
if compressed_bytes is None:
|
|
|
|
return uploaded
|
|
|
|
|
|
|
|
return SimpleUploadedFile(
|
|
|
|
name=getattr(uploaded, "name", "image"),
|
|
|
|
content=compressed_bytes,
|
|
|
|
content_type=getattr(uploaded, "content_type", None),
|
|
|
|
)
|
|
|
|
|
|
|
|
def to_representation(self, value):
|
|
|
|
if not value:
|
|
|
|
return None
|
|
|
|
request = self.context.get("request")
|
|
|
|
url = value.url if hasattr(value, "url") else str(value)
|
|
|
|
return absolute_https_url(url, request)
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategorySerializer(serializers.ModelSerializer):
|
|
|
|
sect_type = serializers.CharField(source='sect.sect_type', read_only=True)
|
|
|
|
title = LocalizedField()
|
|
|
|
description = LocalizedField()
|
|
|
|
has_children = serializers.SerializerMethodField()
|
|
|
|
is_leaf = serializers.SerializerMethodField()
|
|
|
|
has_hadith = serializers.SerializerMethodField()
|
|
|
|
has_quran_verse = serializers.SerializerMethodField()
|
|
|
|
ancestors = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
def get_has_children(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_children"):
|
|
|
|
return bool(obj.annotated_has_children)
|
|
|
|
return obj.children.exists()
|
|
|
|
|
|
|
|
def get_is_leaf(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_children"):
|
|
|
|
return not bool(obj.annotated_has_children)
|
|
|
|
return not obj.children.exists()
|
|
|
|
|
|
|
|
def get_has_hadith(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_hadith"):
|
|
|
|
return bool(obj.annotated_has_hadith)
|
|
|
|
return obj.hadis_set.exists() or (hasattr(obj, "hadis_items") and obj.hadis_items.exists())
|
|
|
|
|
|
|
|
def get_has_quran_verse(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_quran_verse"):
|
|
|
|
return bool(obj.annotated_has_quran_verse)
|
|
|
|
return hasattr(obj, "quran_verse") and obj.quran_verse is not None
|
|
|
|
|
|
|
|
def get_ancestors(self, obj):
|
|
|
|
if not obj:
|
|
|
|
return []
|
|
|
|
view = self.context.get("view")
|
|
request = self.context.get("request")
|
|
if getattr(view, "action", None) == "list":
|
|
include_ancestors = request.query_params.get("include_ancestors") if request else None
|
|
if str(include_ancestors).lower() not in ("true", "1"):
|
|
return []
|
|
|
|
ancestors_qs = obj.get_ancestors(include_self=True).select_related('sect')
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": a.id,
|
|
|
|
"title": a.title,
|
|
|
|
"slug": a.slug,
|
|
|
|
"is_topic": a.is_topic,
|
|
|
|
"source_type": a.source_type,
|
|
|
|
"sect_type": a.sect.sect_type if a.sect else None,
|
|
|
|
"order": a.order,
|
|
|
|
"is_root": a.parent_id is None,
|
|
|
|
"level": getattr(a, a._mptt_meta.level_attr) if hasattr(a, '_mptt_meta') else 0,
|
|
|
|
}
|
|
|
|
for a in ancestors_qs
|
|
|
|
]
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCategory
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"parent",
|
|
|
|
"title",
|
|
|
|
"slug",
|
|
|
|
"is_topic",
|
|
|
|
"source_type",
|
|
|
|
"order",
|
|
|
|
"sect",
|
|
|
|
"sect_type",
|
|
|
|
"description",
|
|
|
|
"has_children",
|
|
|
|
"is_leaf",
|
|
|
|
"has_hadith",
|
|
|
|
"has_quran_verse",
|
|
|
|
"ancestors",
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisStatusSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisStatus
|
|
|
|
fields = ["id", "title", "slug", "color", "main_color_code", "order", "description"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisTagSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisTag
|
|
|
|
fields = ["id", "title", "status"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategoryListSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCategory
|
|
|
|
fields = ["id", "title", "slug", "source_type", "order"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisListSerializer(serializers.ModelSerializer):
|
|
|
|
category_detail = AdminHadisCategoryListSerializer(source="category", read_only=True)
|
|
|
|
categories_detail = AdminHadisCategoryListSerializer(source="categories", many=True, read_only=True)
|
|
|
|
hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True)
|
|
|
|
collections_detail = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Hadis
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"number",
|
|
|
|
"slug",
|
|
|
|
"title",
|
|
|
|
"title_narrator",
|
|
|
|
"text",
|
|
|
|
"translation",
|
|
|
|
"status",
|
|
|
|
"category",
|
|
|
|
"category_detail",
|
|
|
|
"categories",
|
|
|
|
"categories_detail",
|
|
|
|
"hadis_status",
|
|
|
|
"hadis_status_detail",
|
|
|
|
"collections_detail",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def get_collections_detail(self, obj):
|
|
|
|
return [
|
|
|
|
{"id": item.collection_id, "title": item.collection.title, "slug": item.collection.slug}
|
|
|
|
for item in obj.collection_items.all()
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class HadisCollectionsField(serializers.PrimaryKeyRelatedField):
|
|
|
|
def to_representation(self, value):
|
|
|
|
return value.collection_id
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCollectionSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
summary = serializers.JSONField()
|
|
|
|
thumbnail = AbsoluteImageField(required=False, allow_null=True)
|
|
|
|
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCollection
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"title",
|
|
|
|
"slug",
|
|
|
|
"summary",
|
|
|
|
"status",
|
|
|
|
"display_position",
|
|
|
|
"order",
|
|
|
|
"thumbnail",
|
|
|
|
"remove_thumbnail",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
mutable_data = safe_copy_data(data)
|
|
|
|
for json_field in ["title", "summary"]:
|
|
|
|
val = mutable_data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
mutable_data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
thumbnail = mutable_data.get("thumbnail") if hasattr(mutable_data, "get") else None
|
|
|
|
if thumbnail and hasattr(thumbnail, "read"):
|
|
|
|
compressed_bytes = maybe_compress_uploaded_file(thumbnail)
|
|
|
|
if compressed_bytes is not None:
|
|
|
|
mutable_data = safe_copy_data(mutable_data)
|
|
|
|
mutable_data["thumbnail"] = SimpleUploadedFile(
|
|
|
|
name=getattr(thumbnail, "name", "image"),
|
|
|
|
content=compressed_bytes,
|
|
|
|
content_type=getattr(thumbnail, "content_type", None),
|
|
|
|
)
|
|
|
|
return super().to_internal_value(mutable_data)
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
validated_data.pop("remove_thumbnail", False)
|
|
|
|
return super().create(validated_data)
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
remove_thumbnail = validated_data.pop("remove_thumbnail", False)
|
|
|
|
if remove_thumbnail and instance.thumbnail:
|
|
|
|
instance.thumbnail.delete(save=False)
|
|
|
|
instance.thumbnail = None
|
|
|
|
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 = next(iter(obj.book_reference.editions.all()), None) 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 sorted(obj.images.all(), key=lambda x: (x.priority or 0, x.id or 0))
|
|
|
|
]
|
|
|
|
|
|
|
|
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):
|
|
|
|
category_detail = AdminHadisCategorySerializer(source="category", read_only=True)
|
|
|
|
categories_detail = AdminHadisCategorySerializer(source="categories", many=True, read_only=True)
|
|
|
|
hadis_status_detail = AdminHadisStatusSerializer(source="hadis_status", read_only=True)
|
|
|
|
tags_detail = AdminHadisTagSerializer(source="tags", many=True, read_only=True)
|
|
|
|
|
|
|
|
category = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisCategory.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
categories = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisCategory.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
hadis_status = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisStatus.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
tags = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisTag.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
collections = HadisCollectionsField(
|
|
|
|
source="collection_items",
|
|
|
|
queryset=HadisCollection.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
collections_detail = serializers.SerializerMethodField()
|
|
|
|
category_path = serializers.SerializerMethodField()
|
|
|
|
categories_paths = serializers.SerializerMethodField()
|
|
|
|
book_reference_id = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
required=False,
|
|
|
|
write_only=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()
|
|
|
|
|
|
|
|
def get_category_path(self, obj):
|
|
|
|
cat = obj.category
|
|
|
|
if not cat and hasattr(obj, "categories"):
|
|
|
|
cat = obj.categories.first()
|
|
|
|
if not cat:
|
|
|
|
return []
|
|
|
|
ancestors_qs = cat.get_ancestors(include_self=True).select_related('sect')
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": a.id,
|
|
|
|
"title": a.title,
|
|
|
|
"slug": a.slug,
|
|
|
|
"source_type": a.source_type,
|
|
|
|
"sect_type": a.sect.sect_type if a.sect else None,
|
|
|
|
"order": a.order,
|
|
|
|
"is_root": a.parent_id is None,
|
|
|
|
"level": getattr(a, a._mptt_meta.level_attr) if hasattr(a, '_mptt_meta') else 0,
|
|
|
|
}
|
|
|
|
for a in ancestors_qs
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_categories_paths(self, obj):
|
|
|
|
cats = list(obj.categories.all()) if hasattr(obj, "categories") else []
|
|
|
|
if not cats and obj.category:
|
|
|
|
cats = [obj.category]
|
|
|
|
all_paths = []
|
|
|
|
for cat in cats:
|
|
|
|
ancestors_qs = cat.get_ancestors(include_self=True).select_related('sect')
|
|
|
|
all_paths.append([
|
|
|
|
{
|
|
|
|
"id": a.id,
|
|
|
|
"title": a.title,
|
|
|
|
"slug": a.slug,
|
|
|
|
"source_type": a.source_type,
|
|
|
|
"sect_type": a.sect.sect_type if a.sect else None,
|
|
|
|
"order": a.order,
|
|
|
|
"is_root": a.parent_id is None,
|
|
|
|
"level": getattr(a, a._mptt_meta.level_attr) if hasattr(a, '_mptt_meta') else 0,
|
|
|
|
}
|
|
|
|
for a in ancestors_qs
|
|
|
|
])
|
|
|
|
return all_paths
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Hadis
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"number",
|
|
|
|
"slug",
|
|
|
|
"category",
|
|
|
|
"category_detail",
|
|
|
|
"category_path",
|
|
|
|
"categories",
|
|
|
|
"categories_detail",
|
|
|
|
"categories_paths",
|
|
|
|
"title_narrator",
|
|
|
|
"title",
|
|
|
|
"description",
|
|
|
|
"text",
|
|
|
|
"translation",
|
|
|
|
"status",
|
|
|
|
"hadis_status",
|
|
|
|
"hadis_status_detail",
|
|
|
|
"hadis_status_text",
|
|
|
|
"address",
|
|
|
|
"links",
|
|
|
|
"tags",
|
|
|
|
"tags_detail",
|
|
|
|
"collections",
|
|
|
|
"collections_detail",
|
|
|
|
"book_reference_id",
|
|
|
|
"edition_id",
|
|
|
|
"book_volume_id",
|
|
|
|
"volume",
|
|
|
|
"pages",
|
|
|
|
"hadith_number",
|
|
|
|
"share_link",
|
|
|
|
"explanation",
|
|
|
|
"bookmark_count",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
"embedded_in",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "share_link", "bookmark_count", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
ret = super().to_representation(instance)
|
|
|
|
request = self.context.get("request")
|
|
|
|
refs = list(instance.references.all())
|
|
|
|
ref = refs[0] if refs 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:
|
|
|
|
author = ref.book_reference.author
|
|
|
|
first_ed = next(iter(ref.book_reference.editions.all()), None) 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"] = {
|
|
|
|
"id": ref.book_reference.id,
|
|
|
|
"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,
|
|
|
|
"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:
|
|
|
|
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 = []
|
|
|
|
for r in refs:
|
|
|
|
for img in r.images.all().order_by("priority", "id"):
|
|
|
|
url = absolute_https_url(img.thumbnail.url, request) if img.thumbnail else None
|
|
|
|
ref_images.append({
|
|
|
|
"id": img.id,
|
|
|
|
"reference_id": r.id,
|
|
|
|
"hadis_id": instance.id,
|
|
|
|
"thumbnail": url,
|
|
|
|
"image": url,
|
|
|
|
"priority": img.priority,
|
|
|
|
})
|
|
|
|
ret["reference_images"] = ref_images
|
|
|
|
ret["references"] = AdminHadisReferenceSerializer(instance.references.all().order_by("id"), many=True, context=self.context).data
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def get_bookmark_count(self, obj):
|
|
|
|
from apps.bookmark.models.bookmark import Bookmark
|
|
|
|
return Bookmark.objects.filter(service=Bookmark.ServiceChoices.HADITH, content_id=obj.id, status=True).count()
|
|
|
|
|
|
|
|
def validate_category(self, value):
|
|
|
|
if value and value.children.exists():
|
|
|
|
raise serializers.ValidationError(
|
|
|
|
_("Cannot assign a hadith/item to a category that has subcategories. Only leaf categories can contain hadiths.")
|
|
|
|
)
|
|
|
|
return value
|
|
|
|
|
|
|
|
def validate_categories(self, values):
|
|
|
|
for val in values:
|
|
|
|
if val and val.children.exists():
|
|
|
|
raise serializers.ValidationError(
|
|
|
|
_("Cannot assign a hadith/item to a category that has subcategories. Only leaf categories can contain hadiths.")
|
|
|
|
)
|
|
|
|
return values
|
|
|
|
|
|
|
|
def get_collections_detail(self, obj):
|
|
|
|
collections = [item.collection for item in obj.collection_items.all()]
|
|
|
|
return AdminHadisCollectionSerializer(collections, many=True).data
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
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", [])
|
|
|
|
collections = validated_data.pop("collection_items", [])
|
|
|
|
categories = validated_data.pop("categories", None)
|
|
|
|
hadis = super().create(validated_data)
|
|
|
|
if categories is not None:
|
|
|
|
hadis.categories.set(categories)
|
|
|
|
if not hadis.category and categories:
|
|
|
|
hadis.category = categories[0]
|
|
|
|
hadis.save(update_fields=['category'])
|
|
|
|
elif hadis.category:
|
|
|
|
hadis.categories.set([hadis.category])
|
|
|
|
if tags:
|
|
|
|
hadis.tags.set(tags)
|
|
|
|
if collections:
|
|
|
|
for idx, col in enumerate(collections):
|
|
|
|
HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx)
|
|
|
|
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 ""
|
|
|
|
)
|
|
|
|
if not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator)):
|
|
|
|
if hadis.auto_populate_title_narrator():
|
|
|
|
hadis.save(update_fields=['title_narrator', 'normalized_text'])
|
|
|
|
return hadis
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
book_reference_provided = "book_reference_id" in validated_data
|
|
|
|
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)
|
|
|
|
collections = validated_data.pop("collection_items", None)
|
|
|
|
categories = validated_data.pop("categories", None)
|
|
|
|
hadis = super().update(instance, validated_data)
|
|
|
|
if categories is not None:
|
|
|
|
hadis.categories.set(categories)
|
|
|
|
if not hadis.category and categories:
|
|
|
|
hadis.category = categories[0]
|
|
|
|
hadis.save(update_fields=['category'])
|
|
|
|
elif "category" in validated_data and validated_data.get("category"):
|
|
|
|
if not hadis.categories.exists():
|
|
|
|
hadis.categories.set([validated_data["category"]])
|
|
|
|
|
|
|
|
if tags is not None:
|
|
|
|
hadis.tags.set(tags)
|
|
|
|
if collections is not None:
|
|
|
|
instance.collection_items.all().delete()
|
|
|
|
for idx, col in enumerate(collections):
|
|
|
|
HadisInCollection.objects.create(hadis=hadis, collection=col, order=idx)
|
|
|
|
|
|
|
|
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()
|
|
|
|
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 book_reference_provided:
|
|
|
|
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()
|
|
|
|
if not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator)):
|
|
|
|
if hadis.auto_populate_title_narrator():
|
|
|
|
hadis.save(update_fields=['title_narrator', 'normalized_text'])
|
|
|
|
return hadis
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisBriefSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = Hadis
|
|
|
|
fields = ["id", "number", "slug", "title", "title_narrator", "status"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategoryBriefSerializer(serializers.ModelSerializer):
|
|
|
|
title = LocalizedField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCategory
|
|
|
|
fields = ["id", "title"]
|
|
|
|
|
|
|
|
|
|
|
|
def save_relation_reference_data(instance, validated_data, request=None, is_correction=True, ref_type=None):
|
|
|
|
book_reference = validated_data.pop("book_reference_id", None)
|
|
|
|
address = validated_data.pop("address", None)
|
|
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
|
|
|
|
new_images = []
|
|
|
|
if request:
|
|
|
|
files_obj = getattr(request, "FILES", None)
|
|
|
|
if not files_obj and hasattr(request, "_request"):
|
|
|
|
files_obj = getattr(request._request, "FILES", None)
|
|
|
|
|
|
|
|
if files_obj:
|
|
|
|
if hasattr(files_obj, "getlist"):
|
|
|
|
new_images = files_obj.getlist("uploaded_images") or files_obj.getlist("uploaded_images[]")
|
|
|
|
if not new_images:
|
|
|
|
val = files_obj.get("uploaded_images") or files_obj.get("uploaded_images[]")
|
|
|
|
if val:
|
|
|
|
new_images = [val] if not isinstance(val, list) else val
|
|
|
|
|
|
|
|
if not new_images and hasattr(request, "data"):
|
|
|
|
data_obj = request.data
|
|
|
|
if hasattr(data_obj, "getlist"):
|
|
|
|
new_images = data_obj.getlist("uploaded_images") or data_obj.getlist("uploaded_images[]")
|
|
|
|
if not new_images and hasattr(data_obj, "get"):
|
|
|
|
val = data_obj.get("uploaded_images") or data_obj.get("uploaded_images[]")
|
|
|
|
if val:
|
|
|
|
new_images = val if isinstance(val, list) else [val]
|
|
|
|
|
|
|
|
actual_images = [img for img in new_images if img and not isinstance(img, (str, int, float, bool, dict))]
|
|
|
|
|
|
|
|
ref = instance.references.first()
|
|
|
|
if not ref:
|
|
|
|
if book_reference or address or actual_images:
|
|
|
|
if ref_type == "original_text":
|
|
|
|
ref = OriginalTextReference.objects.create(original_text=instance)
|
|
|
|
elif is_correction:
|
|
|
|
ref = CorrectionReference.objects.create(correction=instance)
|
|
|
|
else:
|
|
|
|
ref = InterpretationReference.objects.create(interpretation=instance)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
if book_reference is not None:
|
|
|
|
ref.book_reference = book_reference
|
|
|
|
if address is not None:
|
|
|
|
if isinstance(address, (list, dict)):
|
|
|
|
import json
|
|
|
|
ref.address = json.dumps(address, ensure_ascii=False)
|
|
|
|
else:
|
|
|
|
ref.address = str(address)
|
|
|
|
ref.save()
|
|
|
|
|
|
|
|
if delete_image_ids:
|
|
|
|
ref.images.filter(id__in=delete_image_ids).delete()
|
|
|
|
|
|
|
|
for img in actual_images:
|
|
|
|
if ref_type == "original_text":
|
|
|
|
OriginalTextReferenceImage.objects.create(reference=ref, image=img)
|
|
|
|
elif is_correction:
|
|
|
|
CorrectionReferenceImage.objects.create(reference=ref, image=img)
|
|
|
|
else:
|
|
|
|
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 = next(iter(obj.book_reference.editions.all()), None) 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(
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
address = serializers.JSONField(
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
delete_image_ids = serializers.ListField(
|
|
|
|
child=serializers.IntegerField(),
|
|
|
|
required=False,
|
|
|
|
write_only=True
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCorrection
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"hadis",
|
|
|
|
"hadis_detail",
|
|
|
|
"title",
|
|
|
|
"slug",
|
|
|
|
"text",
|
|
|
|
"translation",
|
|
|
|
"priority",
|
|
|
|
"bookmark_count",
|
|
|
|
"links",
|
|
|
|
"book_reference_id",
|
|
|
|
"address",
|
|
|
|
"delete_image_ids",
|
|
|
|
"references",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "bookmark_count", "references", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
for json_field in ["title", "translation", "links", "address"]:
|
|
|
|
val = data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
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 to_representation(self, instance):
|
|
|
|
import json
|
|
|
|
ret = super().to_representation(instance)
|
|
|
|
refs = list(instance.references.all())
|
|
|
|
ref = refs[0] if refs else None
|
|
|
|
ret["book_reference"] = ref.book_reference_id if ref else None
|
|
|
|
|
|
|
|
addr_val = ref.address if ref and ref.address else ""
|
|
|
|
if isinstance(addr_val, str) and (addr_val.strip().startswith("[") or addr_val.strip().startswith("{")):
|
|
|
|
try:
|
|
|
|
ret["address"] = json.loads(addr_val)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
ret["address"] = addr_val
|
|
|
|
else:
|
|
|
|
ret["address"] = addr_val
|
|
|
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
images = list(ref.images.all()) if ref else []
|
|
|
|
ret["images"] = [
|
|
|
|
{
|
|
|
|
"id": img.id,
|
|
|
|
"image": absolute_https_url(img.image.url, request) if request and img.image else absolute_https_url(img.image.url)
|
|
|
|
}
|
|
|
|
for img in images
|
|
|
|
]
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
request = self.context.get("request")
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
address = validated_data.pop("address", None)
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
# اگر تایتل فرستاده نشده بود و کتاب انتخاب شده بود، تایتل را مستقیم از کتاب بخواند
|
|
title_val = validated_data.get("title")
|
|
has_title = isinstance(title_val, list) and any(
|
|
isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip()
|
|
for item in title_val
|
|
)
|
|
if not has_title and book_reference_id:
|
|
validated_data["title"] = book_reference_id.title
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
ref_data = {
|
|
"book_reference_id": book_reference_id,
|
|
"address": address,
|
|
"delete_image_ids": delete_image_ids,
|
|
}
|
|
save_relation_reference_data(instance, ref_data, request=request, is_correction=True)
|
|
|
|
# اطمینان از پر شدن تایتل در صورت خالی بودن
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
if instance.auto_populate_title():
|
|
instance.save(update_fields=['title', 'slug'])
|
|
|
|
return instance
|
|
|
|
def update(self, instance, validated_data):
|
|
request = self.context.get("request")
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
address = validated_data.pop("address", None)
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
ref_data = {
|
|
"book_reference_id": book_reference_id,
|
|
"address": address,
|
|
"delete_image_ids": delete_image_ids,
|
|
}
|
|
save_relation_reference_data(instance, ref_data, request=request, is_correction=True)
|
|
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
if instance.auto_populate_title():
|
|
instance.save(update_fields=['title', 'slug'])
|
|
|
|
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 = next(iter(obj.book_reference.editions.all()), None) 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 = next(iter(obj.book_reference.editions.all()), None) 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)
|
|
|
|
|
|
|
|
# Writable relation fields
|
|
|
|
book_reference_id = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
address = serializers.JSONField(
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
delete_image_ids = serializers.ListField(
|
|
|
|
child=serializers.IntegerField(),
|
|
|
|
required=False,
|
|
|
|
write_only=True
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisInterpretation
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"category",
|
|
|
|
"category_detail",
|
|
|
|
"title",
|
|
|
|
"text",
|
|
|
|
"translation",
|
|
|
|
"priority",
|
|
|
|
"links",
|
|
|
|
"book_reference_id",
|
|
|
|
"address",
|
|
|
|
"delete_image_ids",
|
|
|
|
"references",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "references", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
for json_field in ["title", "translation", "links", "address"]:
|
|
|
|
val = data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
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 to_representation(self, instance):
|
|
|
|
import json
|
|
|
|
ret = super().to_representation(instance)
|
|
|
|
refs = list(instance.references.all())
|
|
|
|
ref = refs[0] if refs else None
|
|
|
|
ret["book_reference"] = ref.book_reference_id if ref else None
|
|
|
|
|
|
|
|
addr_val = ref.address if ref and ref.address else ""
|
|
|
|
if isinstance(addr_val, str) and (addr_val.strip().startswith("[") or addr_val.strip().startswith("{")):
|
|
|
|
try:
|
|
|
|
ret["address"] = json.loads(addr_val)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
ret["address"] = addr_val
|
|
|
|
else:
|
|
|
|
ret["address"] = addr_val
|
|
|
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
images = list(ref.images.all()) if ref else []
|
|
|
|
ret["images"] = [
|
|
|
|
{
|
|
|
|
"id": img.id,
|
|
|
|
"image": absolute_https_url(img.image.url, request) if request and img.image else absolute_https_url(img.image.url)
|
|
|
|
}
|
|
|
|
for img in images
|
|
|
|
]
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
request = self.context.get("request")
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
address = validated_data.pop("address", None)
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
title_val = validated_data.get("title")
|
|
has_title = isinstance(title_val, list) and any(
|
|
isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip()
|
|
for item in title_val
|
|
)
|
|
if not has_title and book_reference_id:
|
|
validated_data["title"] = book_reference_id.title
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
ref_data = {
|
|
"book_reference_id": book_reference_id,
|
|
"address": address,
|
|
"delete_image_ids": delete_image_ids,
|
|
}
|
|
save_relation_reference_data(instance, ref_data, request=request, is_correction=False)
|
|
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
if instance.auto_populate_title():
|
|
instance.save(update_fields=['title'])
|
|
|
|
return instance
|
|
|
|
def update(self, instance, validated_data):
|
|
request = self.context.get("request")
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
address = validated_data.pop("address", None)
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
ref_data = {
|
|
"book_reference_id": book_reference_id,
|
|
"address": address,
|
|
"delete_image_ids": delete_image_ids,
|
|
}
|
|
save_relation_reference_data(instance, ref_data, request=request, is_correction=False)
|
|
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
if instance.auto_populate_title():
|
|
instance.save(update_fields=['title'])
|
|
|
|
return instance
|
|
|
|
|
|
|
|
class AbsoluteFileField(serializers.FileField):
|
|
|
|
def to_representation(self, value):
|
|
|
|
if not value:
|
|
|
|
return None
|
|
|
|
request = self.context.get("request")
|
|
|
|
url = value.url if hasattr(value, "url") else str(value)
|
|
|
|
if request:
|
|
|
|
return request.build_absolute_uri(url)
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisSectArchiveSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisSect
|
|
|
|
fields = ["id", "sect_type", "title", "description", "is_active", "order"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategoryArchiveParentSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCategory
|
|
|
|
fields = ["id", "title", "slug"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategoryArchiveSerializer(serializers.ModelSerializer):
|
|
|
|
parent_detail = AdminHadisCategoryArchiveParentSerializer(source="parent", read_only=True)
|
|
|
|
sect_detail = AdminHadisSectArchiveSerializer(source="sect", read_only=True)
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField()
|
|
|
|
xmind_file = AbsoluteFileField(required=False, allow_null=True)
|
|
|
|
remove_xmind_file = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
has_hadith = serializers.SerializerMethodField()
|
|
|
|
has_children = serializers.SerializerMethodField()
|
|
|
|
is_leaf = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
parent = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisCategory.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
sect = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=HadisSect.objects.all(),
|
|
|
|
required=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_has_hadith(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_hadith"):
|
|
|
|
return bool(obj.annotated_has_hadith)
|
|
|
|
return obj.hadis_set.exists()
|
|
|
|
|
|
|
|
def get_has_children(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_children"):
|
|
|
|
return bool(obj.annotated_has_children)
|
|
|
|
return obj.children.exists()
|
|
|
|
|
|
|
|
def get_is_leaf(self, obj):
|
|
|
|
if hasattr(obj, "annotated_has_children"):
|
|
|
|
return not bool(obj.annotated_has_children)
|
|
|
|
return not obj.children.exists()
|
|
|
|
|
|
|
|
def validate_parent(self, value):
|
|
|
|
if value and value.hadis_set.exists():
|
|
|
|
raise serializers.ValidationError(
|
|
|
|
_("Cannot set parent to a category that already contains hadiths/content items.")
|
|
|
|
)
|
|
|
|
return value
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisCategory
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"parent",
|
|
|
|
"parent_detail",
|
|
|
|
"sect",
|
|
|
|
"sect_detail",
|
|
|
|
"is_topic",
|
|
|
|
"source_type",
|
|
|
|
"title",
|
|
|
|
"description",
|
|
|
|
"order",
|
|
|
|
"xmind_file",
|
|
|
|
"remove_xmind_file",
|
|
|
|
"slug",
|
|
|
|
"share_link",
|
|
|
|
"xmind_share_link",
|
|
|
|
"has_hadith",
|
|
|
|
"has_children",
|
|
|
|
"is_leaf",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "share_link", "xmind_share_link"]
|
|
|
|
extra_kwargs = {
|
|
|
|
"description": {"required": False},
|
|
|
|
"source_type": {"required": False, "allow_null": True, "allow_blank": True},
|
|
|
|
}
|
|
|
|
|
|
|
|
def validate(self, attrs):
|
|
|
|
is_topic = attrs.get('is_topic', getattr(self.instance, 'is_topic', False))
|
|
|
|
source_type = attrs.get('source_type', getattr(self.instance, 'source_type', None))
|
|
|
|
if is_topic:
|
|
|
|
if not source_type:
|
|
|
|
raise serializers.ValidationError({"source_type": _("A topic must have a source type.")})
|
|
|
|
else:
|
|
|
|
attrs['source_type'] = None
|
|
|
|
|
|
|
|
parent = attrs.get('parent', getattr(self.instance, 'parent', None))
|
|
|
|
if parent and getattr(parent, 'is_topic', False):
|
|
|
|
raise serializers.ValidationError({"parent": _("Cannot set parent to a topic. Parent must be a category.")})
|
|
|
|
|
|
|
|
return super().validate(attrs)
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
for json_field in ["title", "description"]:
|
|
|
|
val = data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return super().to_internal_value(data)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _raise_drf_validation_error(exc):
|
|
|
|
if hasattr(exc, "message_dict"):
|
|
|
|
detail = {
|
|
|
|
("non_field_errors" if key == "__all__" else key): value
|
|
|
|
for key, value in exc.message_dict.items()
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
detail = {"non_field_errors": exc.messages}
|
|
|
|
raise serializers.ValidationError(detail)
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
validated_data.pop("remove_xmind_file", False)
|
|
|
|
try:
|
|
|
|
return super().create(validated_data)
|
|
|
|
except DjangoValidationError as exc:
|
|
|
|
self._raise_drf_validation_error(exc)
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisCategoryArchiveDetailSerializer(AdminHadisCategoryArchiveSerializer):
|
|
|
|
ancestors = serializers.SerializerMethodField()
|
|
|
|
children_type = serializers.SerializerMethodField()
|
|
|
|
child_categories = serializers.SerializerMethodField()
|
|
|
|
child_arguments = serializers.SerializerMethodField()
|
|
|
|
child_count = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
def get_ancestors(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return []
|
|
|
|
ancestors_qs = obj.get_ancestors(include_self=True).select_related("sect")
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": a.id,
|
|
|
|
"title": a.title,
|
|
|
|
"slug": a.slug,
|
|
|
|
"is_topic": a.is_topic,
|
|
|
|
"source_type": a.source_type,
|
|
|
|
"sect_type": a.sect.sect_type if a.sect else None,
|
|
|
|
"order": a.order,
|
|
|
|
"is_root": a.parent_id is None,
|
|
|
|
"level": getattr(a, a._mptt_meta.level_attr) if hasattr(a, "_mptt_meta") else 0,
|
|
|
|
}
|
|
|
|
for a in ancestors_qs
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_children_type(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return "none"
|
|
|
|
has_children = bool(getattr(obj, "annotated_has_children", obj.children.exists()))
|
|
|
|
if has_children:
|
|
|
|
return "categories"
|
|
|
|
has_hadith = bool(
|
|
|
|
getattr(
|
|
|
|
obj,
|
|
|
|
"annotated_has_hadith",
|
|
|
|
obj.hadis_set.exists() or (hasattr(obj, "hadis_items") and obj.hadis_items.exists())
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if has_hadith:
|
|
|
|
return "arguments"
|
|
|
|
return "none"
|
|
|
|
|
|
|
|
def get_child_categories(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return []
|
|
|
|
child_hadis_subquery = Hadis.objects.filter(Q(category=OuterRef("pk")) | Q(categories=OuterRef("pk")))
|
|
|
|
child_children_subquery = HadisCategory.objects.filter(parent=OuterRef("pk"))
|
|
|
|
children = obj.children.all().annotate(
|
|
|
|
annotated_has_hadith=Exists(child_hadis_subquery),
|
|
|
|
annotated_has_children=Exists(child_children_subquery),
|
|
|
|
).order_by("order", "id")
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": c.id,
|
|
|
|
"title": c.title,
|
|
|
|
"slug": c.slug,
|
|
|
|
"order": c.order,
|
|
|
|
"is_topic": c.is_topic,
|
|
|
|
"source_type": c.source_type,
|
|
|
|
"has_children": bool(c.annotated_has_children),
|
|
|
|
"has_hadith": bool(c.annotated_has_hadith),
|
|
|
|
}
|
|
|
|
for c in children
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_child_arguments(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return []
|
|
|
|
hadis_qs = Hadis.objects.filter(
|
|
|
|
Q(category=obj) | Q(categories=obj)
|
|
|
|
).distinct().select_related("hadis_status").order_by("number", "-id")[:100]
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": h.id,
|
|
|
|
"number": h.number,
|
|
|
|
"title": h.title,
|
|
|
|
"title_narrator": h.title_narrator,
|
|
|
|
"slug": h.slug,
|
|
|
|
"status": h.status,
|
|
|
|
"status_detail": {
|
|
|
|
"id": h.hadis_status.id,
|
|
|
|
"title": h.hadis_status.title,
|
|
|
|
"color": h.hadis_status.color,
|
|
|
|
"main_color_code": getattr(h.hadis_status, "main_color_code", None),
|
|
|
|
} if h.hadis_status else None,
|
|
|
|
}
|
|
|
|
for h in hadis_qs
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_child_count(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return 0
|
|
|
|
cat_count = obj.children.count()
|
|
|
|
if cat_count > 0:
|
|
|
|
return cat_count
|
|
|
|
return Hadis.objects.filter(Q(category=obj) | Q(categories=obj)).distinct().count()
|
|
|
|
|
|
|
|
class Meta(AdminHadisCategoryArchiveSerializer.Meta):
|
|
|
|
fields = AdminHadisCategoryArchiveSerializer.Meta.fields + [
|
|
|
|
"ancestors",
|
|
|
|
"children_type",
|
|
|
|
"child_categories",
|
|
|
|
"child_arguments",
|
|
|
|
"child_count",
|
|
|
|
]
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
remove_xmind_file = validated_data.pop("remove_xmind_file", False)
|
|
|
|
if remove_xmind_file and instance.xmind_file:
|
|
|
|
instance.xmind_file.delete(save=False)
|
|
|
|
instance.xmind_file = None
|
|
|
|
try:
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
except DjangoValidationError as exc:
|
|
|
|
self._raise_drf_validation_error(exc)
|
|
|
|
|
|
|
|
class AdminTransmitterReliabilitySerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = TransmitterReliability
|
|
|
|
fields = ["id", "title", "slug", "color", "main_color_code", "description"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminNarratorLayerSerializer(serializers.ModelSerializer):
|
|
|
|
name = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
hadis = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=Hadis.objects.all(),
|
|
|
|
required=True,
|
|
|
|
)
|
|
|
|
hadis_detail = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = NarratorLayer
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"hadis",
|
|
|
|
"hadis_detail",
|
|
|
|
"name",
|
|
|
|
"number",
|
|
|
|
"description",
|
|
|
|
"slug",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "hadis_detail", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def get_hadis_detail(self, obj):
|
|
|
|
if not obj.hadis:
|
|
|
|
return None
|
|
|
|
return {
|
|
|
|
"id": obj.hadis.id,
|
|
|
|
"number": obj.hadis.number,
|
|
|
|
"slug": obj.hadis.slug,
|
|
|
|
"title": obj.hadis.title,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class AdminOpinionStatusSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = OpinionStatus
|
|
|
|
fields = ["id", "title", "slug", "color", "main_color_code", "description"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminYearValueSerializer(serializers.ModelSerializer):
|
|
|
|
translations = serializers.JSONField(required=False, default=list)
|
|
|
|
values_list = serializers.JSONField(required=False, default=list)
|
|
|
|
narrators_count = serializers.SerializerMethodField(read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = YearValue
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"category",
|
|
|
|
"raw_value",
|
|
|
|
"parse_type",
|
|
|
|
"value",
|
|
|
|
"value_min",
|
|
|
|
"value_max",
|
|
|
|
"values_list",
|
|
|
|
"translations",
|
|
|
|
"narrators_count",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "narrators_count", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def get_narrators_count(self, obj):
|
|
|
|
if hasattr(obj, "narrators_count_val"):
|
|
|
|
return obj.narrators_count_val
|
|
|
|
if obj.parse_type == "context" and obj.raw_value.startswith("#"):
|
|
|
|
return NarratorDate.objects.filter(raw_value=obj.raw_value).values("transmitter_id").distinct().count()
|
|
|
|
return obj.narrator_dates.count()
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
rep = super().to_representation(instance)
|
|
|
|
rep["canonical"] = rep.get("raw_value")
|
|
|
|
rep["date_type"] = rep.get("category")
|
|
|
|
rep["date_mode"] = rep.get("parse_type")
|
|
|
|
return rep
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
if not data.get("raw_value") and data.get("canonical"):
|
|
|
|
data["raw_value"] = data["canonical"]
|
|
|
|
if not data.get("category") and data.get("date_type"):
|
|
|
|
data["category"] = data["date_type"]
|
|
|
|
if not data.get("parse_type") and data.get("date_mode"):
|
|
|
|
data["parse_type"] = data["date_mode"]
|
|
|
|
for jf in ["translations", "values_list"]:
|
|
|
|
val = data.get(jf)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[jf] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
for nf in ["value", "value_min", "value_max"]:
|
|
|
|
if nf in data and data[nf] in ("", "null", "undefined", None):
|
|
|
|
data[nf] = None
|
|
|
|
if not data.get("category"):
|
|
|
|
data["category"] = "death"
|
|
|
|
return super().to_internal_value(data)
|
|
|
|
|
|
|
|
def validate(self, attrs):
|
|
|
|
from ..utils.date_engine import parse_canonical_date, build_translations_for_parsed
|
|
|
|
category = attrs.get("category", getattr(self.instance, "category", "death"))
|
|
|
|
raw_value = attrs.get("raw_value", getattr(self.instance, "raw_value", ""))
|
|
|
|
if raw_value is not None:
|
|
|
|
raw_str = str(raw_value).strip()
|
|
|
|
parsed = parse_canonical_date(raw_str, category=category)
|
|
|
|
if not parsed.is_valid:
|
|
|
|
raise serializers.ValidationError({"raw_value": parsed.error})
|
|
|
|
attrs["raw_value"] = parsed.raw_value
|
|
|
|
attrs["parse_type"] = parsed.parse_type
|
|
|
|
if parsed.parse_type != "context":
|
|
|
|
attrs["value"] = parsed.value
|
|
|
|
attrs["value_min"] = parsed.value_min
|
|
|
|
attrs["value_max"] = parsed.value_max
|
|
|
|
attrs["values_list"] = parsed.values_list
|
|
|
|
if not attrs.get("translations"):
|
|
|
|
attrs["translations"] = build_translations_for_parsed(parsed, category=category)
|
|
|
|
return attrs
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
raw_value = validated_data.get("raw_value", "")
|
|
|
|
category = validated_data.get("category", "death")
|
|
|
|
existing = YearValue.objects.filter(category=category, raw_value=raw_value).first()
|
|
|
|
if existing:
|
|
|
|
for k, v in validated_data.items():
|
|
|
|
setattr(existing, k, v)
|
|
|
|
existing.save()
|
|
|
|
instance = existing
|
|
|
|
else:
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
|
|
|
|
# Sync context tag across birth and death so #tag works seamlessly for both
|
|
|
|
if instance.parse_type == "context" and instance.raw_value.startswith("#"):
|
|
|
|
other_cat = "birth" if instance.category == "death" else "death"
|
|
|
|
YearValue.objects.update_or_create(
|
|
|
|
category=other_cat,
|
|
|
|
raw_value=instance.raw_value,
|
|
|
|
defaults={
|
|
|
|
"parse_type": "context",
|
|
|
|
"value": instance.value,
|
|
|
|
"value_min": instance.value_min,
|
|
|
|
"value_max": instance.value_max,
|
|
|
|
"values_list": instance.values_list,
|
|
|
|
"translations": instance.translations,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
if instance.parse_type == "context" and instance.raw_value.startswith("#"):
|
|
|
|
YearValue.objects.filter(raw_value=instance.raw_value, parse_type="context").exclude(id=instance.id).update(
|
|
|
|
value=instance.value,
|
|
|
|
value_min=instance.value_min,
|
|
|
|
value_max=instance.value_max,
|
|
|
|
values_list=instance.values_list,
|
|
|
|
translations=instance.translations,
|
|
|
|
)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
class AdminCountrySerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Country
|
|
|
|
fields = ["id", "title", "slug", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminCitySerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
country_detail = AdminCountrySerializer(source="country", read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = City
|
|
|
|
fields = ["id", "title", "slug", "country", "country_detail", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "country_detail", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookPublisherSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
city_detail = AdminCitySerializer(source="city", read_only=True)
|
|
|
|
country_detail = AdminCountrySerializer(source="country", read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookPublisher
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"title",
|
|
|
|
"slug",
|
|
|
|
"city",
|
|
|
|
"city_detail",
|
|
|
|
"country",
|
|
|
|
"country_detail",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "city_detail", "country_detail", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminKunyaSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Kunya
|
|
|
|
fields = ["id", "title", "slug", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterFeatureSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = TransmitterFeature
|
|
|
|
fields = ["id", "title", "slug", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterProfessionSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = TransmitterProfession
|
|
|
|
fields = ["id", "title", "slug", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminSahabiStatusSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = SahabiStatus
|
|
|
|
fields = ["id", "title", "slug", "description", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminRelationTypeSerializer(serializers.ModelSerializer):
|
|
|
|
code = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = RelationType
|
|
|
|
fields = ["id", "code", "title", "slug", "description", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
class AdminMadhhabSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Madhhab
|
|
|
|
fields = ["id", "title", "slug", "description", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "slug", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
def _build_admin_date_preview(obj, date_type: str, raw_val: str):
|
|
|
|
if not raw_val or not str(raw_val).strip():
|
|
|
|
return None
|
|
|
|
return {
|
|
|
|
"ru": obj.get_formatted_date(date_type, lang="ru"),
|
|
|
|
"en": obj.get_formatted_date(date_type, lang="en"),
|
|
|
|
"ar": obj.get_formatted_date(date_type, lang="ar"),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterListSerializer(serializers.ModelSerializer):
|
|
|
|
reliability_detail = AdminTransmitterReliabilitySerializer(source="reliability", read_only=True)
|
|
|
|
kunyas_detail = AdminKunyaSerializer(source="kunyas", many=True, read_only=True)
|
|
|
|
features_detail = AdminTransmitterFeatureSerializer(source="features_list", many=True, read_only=True)
|
|
|
|
professions_detail = AdminTransmitterProfessionSerializer(source="professions_list", many=True, read_only=True)
|
|
|
|
sahabi_status_detail = AdminSahabiStatusSerializer(source="sahabi_status", read_only=True)
|
|
|
|
madhhabs_detail = AdminMadhhabSerializer(source="madhhabs", many=True, read_only=True)
|
|
|
|
full_name = serializers.JSONField()
|
|
|
|
known_as = serializers.JSONField()
|
|
|
|
nickname = serializers.JSONField()
|
|
|
|
thumbnail = AbsoluteImageField(required=False, allow_null=True)
|
|
|
|
share_link = serializers.CharField(read_only=True)
|
|
|
|
birth_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
death_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
age_at_death_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Transmitters
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"full_name",
|
|
|
|
"slug",
|
|
|
|
"gender",
|
|
|
|
"birth_year_hijri",
|
|
|
|
"death_year_hijri",
|
|
|
|
"age_at_death",
|
|
|
|
"birth_year_preview",
|
|
|
|
"death_year_preview",
|
|
|
|
"age_at_death_preview",
|
|
|
|
"known_as",
|
|
|
|
"nickname",
|
|
|
|
"kunyas_detail",
|
|
|
|
"features_detail",
|
|
|
|
"professions_detail",
|
|
|
|
"sahabi_status",
|
|
|
|
"sahabi_status_detail",
|
|
|
|
"reliability",
|
|
|
|
"reliability_detail",
|
|
|
|
"madhhab",
|
|
|
|
"madhhabs_detail",
|
|
|
|
"generation",
|
|
|
|
"thumbnail",
|
|
|
|
"share_link",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "share_link", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def get_birth_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "birth", obj.birth_year_hijri)
|
|
|
|
|
|
|
|
def get_death_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "death", obj.death_year_hijri)
|
|
|
|
|
|
|
|
def get_age_at_death_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "age", obj.age_at_death)
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
|
|
|
|
reliability_detail = AdminTransmitterReliabilitySerializer(source="reliability", read_only=True)
|
|
|
|
full_name = serializers.JSONField()
|
|
|
|
kunya = serializers.JSONField(required=False, default=list)
|
|
|
|
kunyas = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=Kunya.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
kunyas_detail = AdminKunyaSerializer(source="kunyas", many=True, read_only=True)
|
|
|
|
features = serializers.JSONField(required=False, default=list)
|
|
|
|
features_list = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=TransmitterFeature.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
features_detail = AdminTransmitterFeatureSerializer(source="features_list", many=True, read_only=True)
|
|
|
|
profession = serializers.JSONField(required=False, default=list)
|
|
|
|
professions_list = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=TransmitterProfession.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
professions_detail = AdminTransmitterProfessionSerializer(source="professions_list", many=True, read_only=True)
|
|
|
|
sahabi_status = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=SahabiStatus.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
sahabi_status_detail = AdminSahabiStatusSerializer(source="sahabi_status", read_only=True)
|
|
|
|
madhhabs = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=Madhhab.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
madhhabs_detail = AdminMadhhabSerializer(source="madhhabs", many=True, read_only=True)
|
|
|
|
companion_type = serializers.JSONField(required=False, default=list)
|
|
|
|
known_as = serializers.JSONField()
|
|
|
|
nickname = serializers.JSONField()
|
|
|
|
origin = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
lived_in = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
died_in = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
origin_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
origin_city_detail = AdminCitySerializer(source="origin_city", read_only=True)
|
|
|
|
lived_in_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
lived_in_city_detail = AdminCitySerializer(source="lived_in_city", read_only=True)
|
|
|
|
died_in_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
died_in_city_detail = AdminCitySerializer(source="died_in_city", read_only=True)
|
|
|
|
description = serializers.JSONField()
|
|
|
|
thumbnail = AbsoluteImageField(required=False, allow_null=True)
|
|
|
|
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
reliability = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=TransmitterReliability.objects.all(),
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
father = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=Transmitters.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
father_detail = serializers.SerializerMethodField(read_only=True)
|
|
|
|
sons = serializers.SerializerMethodField(read_only=True)
|
|
|
|
relatives = serializers.SerializerMethodField(read_only=True)
|
|
|
|
relatives_data = serializers.JSONField(write_only=True, required=False)
|
|
|
|
sons_ids = serializers.ListField(
|
|
|
|
child=serializers.IntegerField(),
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
)
|
|
|
|
share_link = serializers.CharField(read_only=True)
|
|
|
|
birth_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
death_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
age_at_death_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Transmitters
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"full_name",
|
|
|
|
"kunya",
|
|
|
|
"kunyas",
|
|
|
|
"kunyas_detail",
|
|
|
|
"features",
|
|
|
|
"features_list",
|
|
|
|
"features_detail",
|
|
|
|
"profession",
|
|
|
|
"professions_list",
|
|
|
|
"professions_detail", "sahabi_status_detail", "madhhabs_detail",
|
|
|
|
"sahabi_status",
|
|
|
|
"sahabi_status_detail",
|
|
|
|
"companion_type",
|
|
|
|
"sahabi_status",
|
|
|
|
"known_as",
|
|
|
|
"nickname",
|
|
|
|
"gender",
|
|
|
|
"slug",
|
|
|
|
"legacy_number",
|
|
|
|
"legacy_id",
|
|
|
|
"birth_year_hijri",
|
|
|
|
"death_year_hijri",
|
|
|
|
"age_at_death",
|
|
|
|
"birth_year_preview",
|
|
|
|
"death_year_preview",
|
|
|
|
"age_at_death_preview",
|
|
|
|
"generation",
|
|
|
|
"reliability",
|
|
|
|
"reliability_detail",
|
|
|
|
"madhhab",
|
|
|
|
"madhhabs",
|
|
|
|
"madhhabs_detail",
|
|
|
|
"in_sahih_muslim",
|
|
|
|
"in_sahih_bukhari",
|
|
|
|
"origin",
|
|
|
|
"origin_city",
|
|
|
|
"origin_city_detail",
|
|
|
|
"lived_in",
|
|
|
|
"lived_in_city",
|
|
|
|
"lived_in_city_detail",
|
|
|
|
"died_in",
|
|
|
|
"died_in_city",
|
|
|
|
"died_in_city_detail",
|
|
|
|
"description",
|
|
|
|
"thumbnail",
|
|
|
|
"remove_thumbnail",
|
|
|
|
"father",
|
|
|
|
"father_detail",
|
|
|
|
"sons",
|
|
|
|
"sons_ids",
|
|
|
|
"relatives",
|
|
|
|
"relatives_data",
|
|
|
|
"share_link",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = [
|
|
|
|
"id", "slug", "share_link", "created_at", "updated_at",
|
|
|
|
"reliability_detail", "father_detail", "sons", "relatives",
|
|
|
|
"birth_year_preview", "death_year_preview", "age_at_death_preview",
|
|
|
|
"origin_city_detail", "lived_in_city_detail", "died_in_city_detail",
|
|
|
|
"kunyas_detail", "features_detail", "professions_detail",
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_birth_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "birth", obj.birth_year_hijri)
|
|
|
|
|
|
|
|
def get_death_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "death", obj.death_year_hijri)
|
|
|
|
|
|
|
|
def get_age_at_death_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "age", obj.age_at_death)
|
|
|
|
|
|
|
|
def get_father_detail(self, obj):
|
|
|
|
if obj.father:
|
|
|
|
name = obj.father.get_name('en') if hasattr(obj.father, 'get_name') else None
|
|
|
|
if not name and obj.father.full_name and isinstance(obj.father.full_name, list) and len(obj.father.full_name) > 0:
|
|
|
|
first = obj.father.full_name[0]
|
|
|
|
if isinstance(first, dict):
|
|
|
|
name = first.get('text', '—')
|
|
|
|
return {
|
|
|
|
"id": obj.father.id,
|
|
|
|
"full_name": obj.father.full_name,
|
|
|
|
"name": name or '—',
|
|
|
|
"slug": obj.father.slug,
|
|
|
|
}
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_sons(self, obj):
|
|
|
|
result = []
|
|
|
|
for son in obj.children_as_father.all():
|
|
|
|
name = son.get_name('en') if hasattr(son, 'get_name') else None
|
|
|
|
if not name and son.full_name and isinstance(son.full_name, list) and len(son.full_name) > 0:
|
|
|
|
first = son.full_name[0]
|
|
|
|
if isinstance(first, dict):
|
|
|
|
name = first.get('text', '—')
|
|
|
|
result.append({
|
|
|
|
"id": son.id,
|
|
|
|
"full_name": son.full_name,
|
|
|
|
"name": name or '—',
|
|
|
|
"slug": son.slug,
|
|
|
|
})
|
|
|
|
return result
|
|
|
|
def get_relatives(self, obj):
|
|
result = []
|
|
rel_types_cache = {}
|
|
for rel in obj.relatives.select_related("relation_type_ref", "narrator").all():
|
|
rel_ref = rel.relation_type_ref
|
|
if not rel_ref and rel.relation_type:
|
|
if rel.relation_type not in rel_types_cache:
|
|
rel_types_cache[rel.relation_type] = RelationType.objects.filter(
|
|
Q(code=rel.relation_type) | Q(slug=rel.relation_type)
|
|
).first()
|
|
rel_ref = rel_types_cache[rel.relation_type]
|
|
|
|
narrator_detail = None
|
|
if rel.narrator:
|
|
n_name = rel.narrator.get_name("en") if hasattr(rel.narrator, "get_name") else None
|
|
if not n_name and isinstance(rel.narrator.full_name, list) and rel.narrator.full_name:
|
|
first = rel.narrator.full_name[0]
|
|
if isinstance(first, dict):
|
|
n_name = first.get("text", "—")
|
|
narrator_detail = {
|
|
"id": rel.narrator.id,
|
|
"full_name": rel.narrator.full_name,
|
|
"name": n_name or rel.name or "—",
|
|
"slug": rel.narrator.slug,
|
|
"legacy_id": rel.narrator.legacy_id,
|
|
}
|
|
|
|
result.append({
|
|
"id": rel.id,
|
|
"relation_type_ref": rel_ref.id if rel_ref else None,
|
|
"relation_type": (rel_ref.code or rel_ref.slug) if rel_ref else rel.relation_type,
|
|
"relation_value": rel_ref.title if (rel_ref and rel_ref.title) else rel.relation_value,
|
|
"relation_type_detail": {
|
|
"id": rel_ref.id,
|
|
"code": rel_ref.code,
|
|
"slug": rel_ref.slug,
|
|
"title": rel_ref.title,
|
|
} if rel_ref else None,
|
|
"narrator": rel.narrator_id,
|
|
"narrator_detail": narrator_detail,
|
|
"name": rel.name,
|
|
})
|
|
return result
|
|
|
|
def _sync_relatives(self, instance, relatives_data):
|
|
if not isinstance(relatives_data, list):
|
|
return
|
|
from ..models.transmitter import RELATION_TYPE_MAP
|
|
instance.relatives.all().delete()
|
|
for item in relatives_data:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
rel_ref_id = item.get("relation_type_ref") or item.get("relation_type_id")
|
|
rel_type_str = str(item.get("relation_type") or "").strip()
|
|
narrator_id = item.get("narrator") or item.get("narrator_id")
|
|
custom_name = str(item.get("name") or "").strip()
|
|
|
|
rel_type_obj = None
|
|
if rel_ref_id not in (None, "", "null", "undefined"):
|
|
try:
|
|
rel_type_obj = RelationType.objects.filter(id=int(rel_ref_id)).first()
|
|
except (ValueError, TypeError):
|
|
pass
|
|
if not rel_type_obj and rel_type_str:
|
|
rel_type_obj = RelationType.objects.filter(
|
|
Q(code=rel_type_str) | Q(slug=rel_type_str)
|
|
).first()
|
|
|
|
narrator_obj = None
|
|
if narrator_id not in (None, "", "null", "undefined"):
|
|
try:
|
|
narrator_obj = Transmitters.objects.filter(id=int(narrator_id)).first()
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
if not rel_type_obj and not rel_type_str:
|
|
continue
|
|
if not narrator_obj and not custom_name:
|
|
continue
|
|
|
|
final_rel_type = (
|
|
(rel_type_obj.code or rel_type_obj.slug or str(rel_type_obj.id))
|
|
if rel_type_obj
|
|
else rel_type_str
|
|
)[:50]
|
|
final_rel_val = (
|
|
rel_type_obj.title
|
|
if (rel_type_obj and rel_type_obj.title)
|
|
else (item.get("relation_value") or RELATION_TYPE_MAP.get(final_rel_type, []))
|
|
)
|
|
|
|
TransmitterRelative.objects.create(
|
|
related_to=instance,
|
|
relation_type_ref=rel_type_obj,
|
|
relation_type=final_rel_type,
|
|
relation_value=final_rel_val,
|
|
narrator=narrator_obj,
|
|
name=custom_name[:255],
|
|
)
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
for json_field in [
|
|
|
|
"full_name",
|
|
|
|
"kunya",
|
|
|
|
"features",
|
|
|
|
"profession",
|
|
|
|
"known_as",
|
|
|
|
"nickname",
|
|
|
|
"origin",
|
|
|
|
"lived_in",
|
|
|
|
"died_in",
|
|
|
|
"description",
|
|
|
|
]:
|
|
|
|
val = data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Handle empty/null values for optional fields (prevent unique constraint violations on empty strings)
|
|
|
|
for field in [
|
|
|
|
"legacy_id",
|
|
|
|
"legacy_number",
|
|
|
|
"age_at_death",
|
|
|
|
"generation",
|
|
|
|
"birth_year_hijri",
|
|
|
|
"death_year_hijri",
|
|
|
|
"companion_type",
|
|
|
|
"father",
|
|
|
|
"origin_city",
|
|
|
|
"lived_in_city",
|
|
|
|
"died_in_city",
|
|
|
|
]:
|
|
|
|
if field in data and (data[field] == "" or data[field] is None or data[field] == "null" or data[field] == "undefined" or (isinstance(data[field], str) and not data[field].strip())):
|
|
|
|
data[field] = None
|
|
|
|
elif field in ("birth_year_hijri", "death_year_hijri", "age_at_death") and field in data and data[field] is not None:
|
|
|
|
data[field] = str(data[field]).strip()
|
|
|
|
|
|
|
|
if "relatives" in data and "relatives_data" not in data:
|
|
data["relatives_data"] = data["relatives"]
|
|
|
|
if "relatives_data" in data:
|
|
val = data["relatives_data"]
|
|
if isinstance(val, str):
|
|
try:
|
|
data["relatives_data"] = json.loads(val)
|
|
except ValueError:
|
|
pass
|
|
|
|
if "sons_ids" in data:
|
|
|
|
val = data["sons_ids"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data["sons_ids"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if "kunyas" in data:
|
|
|
|
val = data["kunyas"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data["kunyas"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if "features_list" in data:
|
|
|
|
val = data["features_list"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data["features_list"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if "professions_list" in data:
|
|
|
|
val = data["professions_list"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data["professions_list"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if "madhhabs" in data:
|
|
|
|
val = data["madhhabs"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data["madhhabs"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return super().to_internal_value(data)
|
|
|
|
|
|
|
|
def validate(self, attrs):
|
|
|
|
from ..utils.date_engine import parse_canonical_date
|
|
|
|
date_checks = [
|
|
|
|
("birth_year_hijri", "birth"),
|
|
|
|
("death_year_hijri", "death"),
|
|
|
|
("age_at_death", "age"),
|
|
|
|
]
|
|
|
|
errors = {}
|
|
|
|
for field_name, category in date_checks:
|
|
|
|
if field_name in attrs and attrs[field_name] is not None:
|
|
|
|
raw_val = str(attrs[field_name]).strip()
|
|
|
|
if raw_val:
|
|
|
|
parsed = parse_canonical_date(raw_val, category=category)
|
|
|
|
if not parsed.is_valid:
|
|
|
|
errors[field_name] = parsed.error
|
|
|
|
else:
|
|
|
|
attrs[field_name] = parsed.raw_value
|
|
|
|
if errors:
|
|
|
|
raise serializers.ValidationError(errors)
|
|
|
|
return super().validate(attrs)
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
validated_data.pop("remove_thumbnail", False)
|
|
|
|
sons_ids = validated_data.pop("sons_ids", None)
|
|
|
|
relatives_data = validated_data.pop("relatives_data", None)
|
|
|
|
kunyas_objs = validated_data.pop("kunyas", None)
|
|
|
|
features_objs = validated_data.pop("features_list", None)
|
|
|
|
professions_objs = validated_data.pop("professions_list", None)
|
|
|
|
madhhabs_objs = validated_data.pop("madhhabs", None)
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
if sons_ids is not None:
|
|
|
|
Transmitters.objects.filter(id__in=sons_ids).update(father=instance)
|
|
|
|
if relatives_data is not None:
|
|
|
|
self._sync_relatives(instance, relatives_data)
|
|
|
|
if kunyas_objs is not None:
|
|
|
|
instance.kunyas.set(kunyas_objs)
|
|
|
|
if kunyas_objs:
|
|
|
|
first_kunya = kunyas_objs[0]
|
|
|
|
instance.kunya = first_kunya.title or []
|
|
|
|
instance.save(update_fields=['kunya'])
|
|
|
|
else:
|
|
|
|
instance.kunya = []
|
|
|
|
instance.save(update_fields=['kunya'])
|
|
|
|
if features_objs is not None:
|
|
|
|
instance.features_list.set(features_objs)
|
|
|
|
if features_objs:
|
|
|
|
first_feat = features_objs[0]
|
|
|
|
instance.features = first_feat.title or []
|
|
|
|
instance.save(update_fields=['features'])
|
|
|
|
else:
|
|
|
|
instance.features = []
|
|
|
|
instance.save(update_fields=['features'])
|
|
|
|
if professions_objs is not None:
|
|
|
|
instance.professions_list.set(professions_objs)
|
|
|
|
if professions_objs:
|
|
|
|
first_prof = professions_objs[0]
|
|
|
|
instance.profession = first_prof.title or []
|
|
|
|
instance.save(update_fields=['profession'])
|
|
|
|
else:
|
|
|
|
instance.profession = []
|
|
|
|
instance.save(update_fields=['profession'])
|
|
|
|
if "sahabi_status" in validated_data:
|
|
|
|
st = validated_data.get("sahabi_status")
|
|
|
|
instance.companion_type = st.title if st else []
|
|
|
|
instance.save(update_fields=['companion_type'])
|
|
|
|
if madhhabs_objs is not None:
|
|
|
|
instance.madhhabs.set(madhhabs_objs)
|
|
|
|
for m in madhhabs_objs:
|
|
|
|
if m.slug == "sunni":
|
|
|
|
instance.madhhab = "sunni"
|
|
|
|
instance.save(update_fields=["madhhab"])
|
|
|
|
break
|
|
|
|
elif m.slug == "shia":
|
|
|
|
instance.madhhab = "shia"
|
|
|
|
instance.save(update_fields=["madhhab"])
|
|
|
|
break
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
remove_thumbnail = validated_data.pop("remove_thumbnail", False)
|
|
|
|
sons_ids = validated_data.pop("sons_ids", None)
|
|
|
|
relatives_data = validated_data.pop("relatives_data", None)
|
|
|
|
kunyas_objs = validated_data.pop("kunyas", None)
|
|
|
|
features_objs = validated_data.pop("features_list", None)
|
|
|
|
professions_objs = validated_data.pop("professions_list", None)
|
|
|
|
madhhabs_objs = validated_data.pop("madhhabs", None)
|
|
|
|
if remove_thumbnail and instance.thumbnail:
|
|
|
|
instance.thumbnail.delete(save=False)
|
|
|
|
instance.thumbnail = None
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
if sons_ids is not None:
|
|
|
|
instance.children_as_father.exclude(id__in=sons_ids).update(father=None)
|
|
|
|
Transmitters.objects.filter(id__in=sons_ids).update(father=instance)
|
|
|
|
if relatives_data is not None:
|
|
|
|
self._sync_relatives(instance, relatives_data)
|
|
|
|
if kunyas_objs is not None:
|
|
|
|
instance.kunyas.set(kunyas_objs)
|
|
|
|
if kunyas_objs:
|
|
|
|
first_kunya = kunyas_objs[0]
|
|
|
|
instance.kunya = first_kunya.title or []
|
|
|
|
instance.save(update_fields=['kunya'])
|
|
|
|
else:
|
|
|
|
instance.kunya = []
|
|
|
|
instance.save(update_fields=['kunya'])
|
|
|
|
if features_objs is not None:
|
|
|
|
instance.features_list.set(features_objs)
|
|
|
|
if features_objs:
|
|
|
|
first_feat = features_objs[0]
|
|
|
|
instance.features = first_feat.title or []
|
|
|
|
instance.save(update_fields=['features'])
|
|
|
|
else:
|
|
|
|
instance.features = []
|
|
|
|
instance.save(update_fields=['features'])
|
|
|
|
if professions_objs is not None:
|
|
|
|
instance.professions_list.set(professions_objs)
|
|
|
|
if professions_objs:
|
|
|
|
first_prof = professions_objs[0]
|
|
|
|
instance.profession = first_prof.title or []
|
|
|
|
instance.save(update_fields=['profession'])
|
|
|
|
else:
|
|
|
|
instance.profession = []
|
|
|
|
instance.save(update_fields=['profession'])
|
|
|
|
if "sahabi_status" in validated_data:
|
|
|
|
st = validated_data.get("sahabi_status")
|
|
|
|
instance.companion_type = st.title if st else []
|
|
|
|
instance.save(update_fields=['companion_type'])
|
|
|
|
if madhhabs_objs is not None:
|
|
|
|
instance.madhhabs.set(madhhabs_objs)
|
|
|
|
for m in madhhabs_objs:
|
|
|
|
if m.slug == "sunni":
|
|
|
|
instance.madhhab = "sunni"
|
|
|
|
instance.save(update_fields=["madhhab"])
|
|
|
|
break
|
|
|
|
elif m.slug == "shia":
|
|
|
|
instance.madhhab = "shia"
|
|
|
|
instance.save(update_fields=["madhhab"])
|
|
|
|
break
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookSubjectAreaSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = BookSubjectArea
|
|
|
|
fields = ["id", "title", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookTypeSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = BookType
|
|
|
|
fields = ["id", "title", "sect", "created_at", "updated_at"]
|
|
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookAuthorRutbaSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField()
|
|
|
|
description = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookAuthorRutba
|
|
|
|
fields = ["id", "title", "slug", "color", "main_color_code", "description"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookAuthorSerializer(serializers.ModelSerializer):
|
|
|
|
image = AbsoluteImageField(required=False, allow_null=True)
|
|
|
|
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
rutba_status = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookAuthorRutba.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
rutba_detail = AdminBookAuthorRutbaSerializer(source="rutba_status", read_only=True)
|
|
|
|
features_list = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=TransmitterFeature.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
features_detail = AdminTransmitterFeatureSerializer(source="features_list", many=True, read_only=True)
|
|
|
|
book_references = serializers.PrimaryKeyRelatedField(
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
)
|
|
|
|
birth_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
death_year_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
age_at_death_preview = serializers.SerializerMethodField(read_only=True)
|
|
|
|
origin = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
lived_in = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
died_in = serializers.JSONField(required=False, allow_null=True)
|
|
|
|
origin_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
origin_city_detail = AdminCitySerializer(source="origin_city", read_only=True)
|
|
|
|
lived_in_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
lived_in_city_detail = AdminCitySerializer(source="lived_in_city", read_only=True)
|
|
|
|
died_in_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
died_in_city_detail = AdminCitySerializer(source="died_in_city", read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookAuthor
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"name",
|
|
|
|
"slug",
|
|
|
|
"image",
|
|
|
|
"remove_image",
|
|
|
|
"birth_year_hijri",
|
|
|
|
"death_year_hijri",
|
|
|
|
"age_at_death",
|
|
|
|
"birth_year_preview",
|
|
|
|
"death_year_preview",
|
|
|
|
"age_at_death_preview",
|
|
|
|
"birth_year_miladi",
|
|
|
|
"death_year_miladi",
|
|
|
|
"related_narrator",
|
|
|
|
"known_as",
|
|
|
|
"scholarity",
|
|
|
|
"tabaqa",
|
|
|
|
"rutba",
|
|
|
|
"rutba_status",
|
|
|
|
"rutba_detail",
|
|
|
|
"features",
|
|
|
|
"features_list",
|
|
|
|
"features_detail",
|
|
|
|
"origin",
|
|
|
|
"origin_city",
|
|
|
|
"origin_city_detail",
|
|
|
|
"lived_in",
|
|
|
|
"lived_in_city",
|
|
|
|
"lived_in_city_detail",
|
|
|
|
"died_in",
|
|
|
|
"died_in_city",
|
|
|
|
"died_in_city_detail",
|
|
|
|
"book_references",
|
|
|
|
]
|
|
|
|
read_only_fields = [
|
|
|
|
"id",
|
|
|
|
"slug",
|
|
|
|
"birth_year_preview",
|
|
|
|
"death_year_preview",
|
|
|
|
"age_at_death_preview",
|
|
|
|
"origin_city_detail",
|
|
|
|
"lived_in_city_detail",
|
|
|
|
"died_in_city_detail",
|
|
|
|
"features_detail",
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_birth_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "birth", obj.birth_year_hijri)
|
|
|
|
|
|
|
|
def get_death_year_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "death", obj.death_year_hijri)
|
|
|
|
|
|
|
|
def get_age_at_death_preview(self, obj):
|
|
|
|
return _build_admin_date_preview(obj, "age", getattr(obj, "age_at_death", None))
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
mutable_data = safe_copy_data(data)
|
|
|
|
for json_field in ["name", "known_as", "scholarity", "tabaqa", "rutba", "features", "origin", "lived_in", "died_in", "book_references"]:
|
|
|
|
if json_field in mutable_data:
|
|
|
|
val = mutable_data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
mutable_data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
for field in [
|
|
|
|
"birth_year_hijri",
|
|
|
|
"death_year_hijri",
|
|
|
|
"age_at_death",
|
|
|
|
"birth_year_miladi",
|
|
|
|
"death_year_miladi",
|
|
|
|
"related_narrator",
|
|
|
|
"rutba_status",
|
|
|
|
"origin_city",
|
|
|
|
"lived_in_city",
|
|
|
|
"died_in_city",
|
|
|
|
]:
|
|
|
|
if field in mutable_data:
|
|
|
|
val = mutable_data.get(field)
|
|
|
|
if val == "" or val is None or val == "null" or val == "undefined" or val == "none" or (isinstance(val, str) and not val.strip()):
|
|
|
|
mutable_data[field] = None
|
|
|
|
elif field in ("birth_year_hijri", "death_year_hijri", "age_at_death") and val is not None:
|
|
|
|
mutable_data[field] = str(val).strip()
|
|
|
|
if "features_list" in mutable_data:
|
|
|
|
val = mutable_data["features_list"]
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
mutable_data["features_list"] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return super().to_internal_value(mutable_data)
|
|
|
|
|
|
|
|
def validate(self, attrs):
|
|
|
|
from ..utils.date_engine import parse_canonical_date
|
|
|
|
date_checks = [
|
|
|
|
("birth_year_hijri", "birth"),
|
|
|
|
("death_year_hijri", "death"),
|
|
|
|
("age_at_death", "age"),
|
|
|
|
]
|
|
|
|
errors = {}
|
|
|
|
for field_name, category in date_checks:
|
|
|
|
if field_name in attrs and attrs[field_name] is not None:
|
|
|
|
raw_val = str(attrs[field_name]).strip()
|
|
|
|
if raw_val:
|
|
|
|
parsed = parse_canonical_date(raw_val, category=category)
|
|
|
|
if not parsed.is_valid:
|
|
|
|
errors[field_name] = parsed.error
|
|
|
|
else:
|
|
|
|
attrs[field_name] = parsed.raw_value
|
|
|
|
if errors:
|
|
|
|
raise serializers.ValidationError(errors)
|
|
|
|
return super().validate(attrs)
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
validated_data.pop("remove_image", False)
|
|
|
|
book_references = validated_data.pop("book_references", None)
|
|
|
|
features_objs = validated_data.pop("features_list", None)
|
|
|
|
author = super().create(validated_data)
|
|
|
|
if book_references:
|
|
|
|
for book in book_references:
|
|
|
|
book.author = author
|
|
|
|
book.save(update_fields=["author"])
|
|
|
|
if features_objs is not None:
|
|
|
|
author.features_list.set(features_objs)
|
|
|
|
if features_objs:
|
|
|
|
first_feat = features_objs[0]
|
|
|
|
author.features = first_feat.title or []
|
|
|
|
author.save(update_fields=['features'])
|
|
|
|
else:
|
|
|
|
author.features = []
|
|
|
|
author.save(update_fields=['features'])
|
|
|
|
return author
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
remove_image = validated_data.pop("remove_image", False)
|
|
|
|
book_references = validated_data.pop("book_references", None)
|
|
|
|
features_objs = validated_data.pop("features_list", None)
|
|
|
|
if remove_image and instance.image:
|
|
|
|
instance.image.delete(save=False)
|
|
|
|
instance.image = None
|
|
|
|
author = super().update(instance, validated_data)
|
|
|
|
if book_references is not None:
|
|
|
|
current_ids = set(author.book_references.values_list('id', flat=True))
|
|
|
|
new_ids = {b.id for b in book_references}
|
|
|
|
to_remove = current_ids - new_ids
|
|
|
|
if to_remove:
|
|
|
|
BookReference.objects.filter(id__in=to_remove, author=author).update(author=None)
|
|
|
|
to_add = new_ids - current_ids
|
|
|
|
if to_add:
|
|
|
|
BookReference.objects.filter(id__in=to_add).update(author=author)
|
|
|
|
if features_objs is not None:
|
|
|
|
author.features_list.set(features_objs)
|
|
|
|
if features_objs:
|
|
|
|
first_feat = features_objs[0]
|
|
|
|
author.features = first_feat.title or []
|
|
|
|
author.save(update_fields=['features'])
|
|
|
|
else:
|
|
|
|
author.features = []
|
|
|
|
author.save(update_fields=['features'])
|
|
|
|
return author
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookAttributeSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = BookAttribute
|
|
|
|
fields = ["id", "book_reference", "title", "value"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookReferenceImageSerializer(serializers.ModelSerializer):
|
|
|
|
image = AbsoluteImageField(required=True)
|
|
|
|
volume = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookReferenceImage
|
|
|
|
fields = ["id", "book_reference", "image", "volume", "order", "description"]
|
|
|
|
|
|
|
|
def get_volume(self, obj):
|
|
|
|
return obj.book_volume.title if getattr(obj, 'book_volume', None) else ""
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisReferenceImageSerializer(serializers.ModelSerializer):
|
|
|
|
thumbnail = AbsoluteImageField(required=True)
|
|
|
|
hadis_id = serializers.IntegerField(source="reference.hadis_id", read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ReferenceImage
|
|
|
|
fields = ["id", "reference", "hadis_id", "thumbnail", "priority"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookReferenceDocumentSerializer(serializers.ModelSerializer):
|
|
|
|
file = AbsoluteFileField(required=True)
|
|
|
|
volume = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookReferenceDocument
|
|
|
|
fields = ["id", "book_reference", "file", "volume", "title", "order"]
|
|
|
|
|
|
|
|
def get_volume(self, obj):
|
|
|
|
return obj.book_volume.title if getattr(obj, 'book_volume', None) else ""
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookTagSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField(default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookTag
|
|
|
|
fields = ["id", "title", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookReferenceListSerializer(serializers.ModelSerializer):
|
|
|
|
author_id = serializers.IntegerField(source="author.id", read_only=True, allow_null=True)
|
|
|
|
author_name = serializers.JSONField(source="author.name", read_only=True, allow_null=True)
|
|
|
|
tag_id = serializers.IntegerField(source="tag.id", read_only=True, allow_null=True)
|
|
|
|
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
|
|
|
|
volume = serializers.SerializerMethodField()
|
|
|
|
publisher = serializers.SerializerMethodField()
|
|
|
|
year_of_publication = serializers.SerializerMethodField()
|
|
|
|
hadis_count = serializers.SerializerMethodField()
|
|
|
|
thumbnail = serializers.SerializerMethodField()
|
|
|
|
image = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookReference
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"title",
|
|
|
|
"slug",
|
|
|
|
"has_editions",
|
|
|
|
"priority",
|
|
|
|
"author_id",
|
|
|
|
"author_name",
|
|
|
|
"tag_id",
|
|
|
|
"tag_detail",
|
|
|
|
"volume",
|
|
|
|
"publisher",
|
|
|
|
"year_of_publication",
|
|
|
|
"rate",
|
|
|
|
"hadis_count",
|
|
|
|
"thumbnail",
|
|
|
|
"image",
|
|
|
|
"created_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_hadis_count(self, obj):
|
|
|
|
if hasattr(obj, "annotated_hadis_count"):
|
|
|
|
return obj.annotated_hadis_count
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return 0
|
|
|
|
return Hadis.objects.filter(references__book_reference=obj).distinct().count()
|
|
|
|
|
|
|
|
def get_thumbnail(self, obj):
|
|
|
|
first_img = obj.images.filter(book_volume__isnull=True).order_by("order", "id").first()
|
|
|
|
if first_img and first_img.image:
|
|
|
|
request = self.context.get("request")
|
|
|
|
if request:
|
|
|
|
return absolute_https_url(first_img.image.url, request)
|
|
|
|
return absolute_https_url(first_img.image.url)
|
|
|
|
first_vol = obj.volumes.filter(image__isnull=False).exclude(image="").first()
|
|
|
|
if first_vol and first_vol.image:
|
|
|
|
request = self.context.get("request")
|
|
|
|
if request:
|
|
|
|
return absolute_https_url(first_vol.image.url, request)
|
|
|
|
return absolute_https_url(first_vol.image.url)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_image(self, obj):
|
|
|
|
return self.get_thumbnail(obj)
|
|
|
|
|
|
|
|
def get_volume(self, obj):
|
|
|
|
vols = list(obj.volumes.all())
|
|
|
|
return vols[0].title if vols else ""
|
|
|
|
|
|
|
|
def get_publisher(self, obj):
|
|
|
|
editions = list(obj.editions.all())
|
|
|
|
return editions[0].publisher if editions else []
|
|
|
|
|
|
|
|
def get_year_of_publication(self, obj):
|
|
|
|
editions = list(obj.editions.all())
|
|
|
|
return editions[0].year_of_publication if editions else ""
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
|
|
|
|
subject_area_detail = AdminBookSubjectAreaSerializer(source="subject_area", many=True, read_only=True)
|
|
|
|
type_detail = AdminBookTypeSerializer(source="type", read_only=True)
|
|
|
|
author_detail = AdminBookAuthorSerializer(source="author", read_only=True)
|
|
|
|
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
|
|
|
|
|
|
|
|
subject_area = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookSubjectArea.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
type = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookType.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
author = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookAuthor.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
tag = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookTag.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
source_url = serializers.SerializerMethodField()
|
|
|
|
isbn = serializers.SerializerMethodField()
|
|
|
|
volume = serializers.SerializerMethodField()
|
|
|
|
number_of_volumes = serializers.SerializerMethodField()
|
|
|
|
year_of_publication = serializers.SerializerMethodField()
|
|
|
|
publisher = serializers.SerializerMethodField()
|
|
|
|
city_of_publication = serializers.SerializerMethodField()
|
|
|
|
city_of_publication_city = serializers.SerializerMethodField()
|
|
|
|
city_of_publication_city_detail = serializers.SerializerMethodField()
|
|
|
|
country_of_publication = serializers.SerializerMethodField()
|
|
|
|
edition_number = serializers.SerializerMethodField()
|
|
|
|
hadis_count = serializers.SerializerMethodField()
|
|
|
|
child_arguments = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
thumbnail = serializers.SerializerMethodField()
|
|
|
|
image = serializers.SerializerMethodField()
|
|
|
|
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
|
|
|
|
def get_thumbnail(self, obj):
|
|
|
|
first_img = obj.images.filter(book_volume__isnull=True).order_by("order", "id").first()
|
|
|
|
if first_img and first_img.image:
|
|
|
|
request = self.context.get("request")
|
|
|
|
if request:
|
|
|
|
return absolute_https_url(first_img.image.url, request)
|
|
|
|
return absolute_https_url(first_img.image.url)
|
|
|
|
first_vol = obj.volumes.filter(image__isnull=False).exclude(image="").first()
|
|
|
|
if first_vol and first_vol.image:
|
|
|
|
request = self.context.get("request")
|
|
|
|
if request:
|
|
|
|
return absolute_https_url(first_vol.image.url, request)
|
|
|
|
return absolute_https_url(first_vol.image.url)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_image(self, obj):
|
|
|
|
return self.get_thumbnail(obj)
|
|
|
|
|
|
|
|
def get_source_url(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.source_url if ed else ""
|
|
|
|
|
|
|
|
def get_isbn(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.isbn if ed else ""
|
|
|
|
|
|
|
|
def get_volume(self, obj):
|
|
|
|
vol = obj.volumes.first()
|
|
|
|
return vol.title if vol else ""
|
|
|
|
|
|
|
|
def get_number_of_volumes(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.number_of_volumes if ed else None
|
|
|
|
|
|
|
|
def get_year_of_publication(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.year_of_publication if ed else ""
|
|
|
|
|
|
|
|
def get_publisher(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.publisher if ed else []
|
|
|
|
|
|
|
|
def get_city_of_publication(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.city_of_publication if ed else []
|
|
|
|
|
|
|
|
def get_city_of_publication_city(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.city_of_publication_city_id if ed else None
|
|
|
|
|
|
|
|
def get_city_of_publication_city_detail(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
if ed and ed.city_of_publication_city:
|
|
|
|
return AdminCitySerializer(ed.city_of_publication_city).data
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_country_of_publication(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.country_of_publication if ed else []
|
|
|
|
|
|
|
|
def get_edition_number(self, obj):
|
|
|
|
ed = obj.editions.first()
|
|
|
|
return ed.edition_number if ed else ""
|
|
|
|
|
|
|
|
def get_hadis_count(self, obj):
|
|
|
|
if hasattr(obj, "annotated_hadis_count"):
|
|
|
|
return obj.annotated_hadis_count
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return 0
|
|
|
|
return Hadis.objects.filter(references__book_reference=obj).distinct().count()
|
|
|
|
|
|
|
|
def get_child_arguments(self, obj):
|
|
|
|
if not obj or not obj.pk:
|
|
|
|
return []
|
|
|
|
hadises = (
|
|
|
|
Hadis.objects.filter(references__book_reference=obj)
|
|
|
|
.select_related("hadis_status", "category")
|
|
|
|
.distinct()
|
|
|
|
.order_by("number", "id")
|
|
|
|
)
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": h.id,
|
|
|
|
"number": h.number,
|
|
|
|
"title": h.title,
|
|
|
|
"title_narrator": h.title_narrator,
|
|
|
|
"slug": h.slug,
|
|
|
|
"status": h.status,
|
|
|
|
"source_type": h.category.source_type if h.category else "hadith",
|
|
|
|
"status_detail": {
|
|
|
|
"id": h.hadis_status.id,
|
|
|
|
"title": h.hadis_status.title,
|
|
|
|
"color": h.hadis_status.color,
|
|
|
|
"main_color_code": getattr(h.hadis_status, "main_color_code", None),
|
|
|
|
} if h.hadis_status else None,
|
|
|
|
}
|
|
|
|
for h in hadises
|
|
|
|
]
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookReference
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"legacy_id",
|
|
|
|
"source_url",
|
|
|
|
"title",
|
|
|
|
"description",
|
|
|
|
"language",
|
|
|
|
"has_editions",
|
|
|
|
"isbn",
|
|
|
|
"volume",
|
|
|
|
"number_of_volumes",
|
|
|
|
"year_of_publication",
|
|
|
|
"number_page",
|
|
|
|
"slug",
|
|
|
|
"publisher",
|
|
|
|
"subject_area",
|
|
|
|
"subject_area_detail",
|
|
|
|
"type",
|
|
|
|
"type_detail",
|
|
|
|
"tag",
|
|
|
|
"tag_detail",
|
|
|
|
"order",
|
|
|
|
"priority",
|
|
|
|
"city_of_publication",
|
|
|
|
"city_of_publication_city",
|
|
|
|
"city_of_publication_city_detail",
|
|
|
|
"country_of_publication",
|
|
|
|
"edition_number",
|
|
|
|
"rate",
|
|
|
|
"author",
|
|
|
|
"author_detail",
|
|
|
|
"hadis_count",
|
|
|
|
"child_arguments",
|
|
|
|
"thumbnail",
|
|
|
|
"image",
|
|
|
|
"remove_thumbnail",
|
|
|
|
"remove_image",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
"share_link",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "slug", "hadis_count", "child_arguments", "created_at", "updated_at", "share_link", "city_of_publication_city", "city_of_publication_city_detail"]
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
mutable_data = safe_copy_data(data)
|
|
|
|
for json_field in ["title", "description", "language", "publisher", "city_of_publication", "country_of_publication"]:
|
|
|
|
if json_field in mutable_data:
|
|
|
|
val = mutable_data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
mutable_data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
for list_field in ["subject_area", "authors"]:
|
|
|
|
if list_field in mutable_data:
|
|
|
|
val = mutable_data.get(list_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
parsed = json.loads(val)
|
|
|
|
mutable_data[list_field] = parsed if isinstance(parsed, list) else [parsed]
|
|
|
|
except ValueError:
|
|
|
|
if val.isdigit():
|
|
|
|
mutable_data[list_field] = [int(val)]
|
|
|
|
elif val.strip():
|
|
|
|
mutable_data[list_field] = [int(v.strip()) for v in val.split(",") if v.strip().isdigit()]
|
|
|
|
else:
|
|
|
|
mutable_data[list_field] = []
|
|
|
|
elif isinstance(val, int):
|
|
|
|
mutable_data[list_field] = [val]
|
|
|
|
elif isinstance(val, list):
|
|
|
|
cleaned_list = []
|
|
|
|
for item in val:
|
|
|
|
if isinstance(item, str):
|
|
|
|
try:
|
|
|
|
item_parsed = json.loads(item)
|
|
|
|
if isinstance(item_parsed, list):
|
|
|
|
cleaned_list.extend(item_parsed)
|
|
|
|
else:
|
|
|
|
cleaned_list.append(item_parsed)
|
|
|
|
except ValueError:
|
|
|
|
cleaned_list.append(int(item) if item.isdigit() else item)
|
|
|
|
else:
|
|
|
|
cleaned_list.append(item)
|
|
|
|
mutable_data[list_field] = cleaned_list
|
|
|
|
|
|
|
|
if "authors" in mutable_data and ("author" not in mutable_data or mutable_data.get("author") is None):
|
|
|
|
auth_val = mutable_data.get("authors")
|
|
|
|
if isinstance(auth_val, list):
|
|
|
|
mutable_data["author"] = auth_val[0] if len(auth_val) > 0 else None
|
|
|
|
elif auth_val and str(auth_val).isdigit():
|
|
|
|
mutable_data["author"] = int(auth_val)
|
|
|
|
|
|
|
|
for int_field in ["number_page", "order", "type", "tag", "author"]:
|
|
|
|
if int_field in mutable_data:
|
|
|
|
val = mutable_data.get(int_field)
|
|
|
|
if val == "" or val == "null" or val == "undefined":
|
|
|
|
mutable_data[int_field] = None
|
|
|
|
|
|
|
|
if "rate" in mutable_data:
|
|
|
|
val = mutable_data.get("rate")
|
|
|
|
if val == "" or val == "null" or val == "undefined":
|
|
|
|
mutable_data["rate"] = None
|
|
|
|
|
|
|
|
if "has_editions" in mutable_data:
|
|
|
|
val = mutable_data.get("has_editions")
|
|
|
|
if isinstance(val, str):
|
|
|
|
mutable_data["has_editions"] = val.lower() in ["true", "1"]
|
|
|
|
|
|
|
|
return super().to_internal_value(mutable_data)
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
ret = super().to_representation(instance)
|
|
|
|
if instance.author_id:
|
|
|
|
ret["authors"] = [instance.author_id]
|
|
|
|
if ret.get("author_detail"):
|
|
|
|
ret["authors_detail"] = [ret["author_detail"]]
|
|
|
|
else:
|
|
|
|
ret["authors"] = []
|
|
|
|
ret["authors_detail"] = []
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
subject_area = validated_data.pop("subject_area", [])
|
|
|
|
validated_data.pop("remove_thumbnail", False)
|
|
|
|
validated_data.pop("remove_image", False)
|
|
|
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
thumbnail = None
|
|
|
|
if request and hasattr(request, "FILES"):
|
|
|
|
thumbnail = request.FILES.get("thumbnail") or request.FILES.get("image")
|
|
|
|
|
|
|
|
book = super().create(validated_data)
|
|
|
|
if subject_area:
|
|
|
|
book.subject_area.set(subject_area)
|
|
|
|
|
|
|
|
if thumbnail:
|
|
|
|
BookReferenceImage.objects.create(
|
|
|
|
book_reference=book,
|
|
|
|
book_volume=None,
|
|
|
|
image=thumbnail,
|
|
|
|
order=0,
|
|
|
|
)
|
|
|
|
return book
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
subject_area = validated_data.pop("subject_area", None)
|
|
|
|
remove_thumbnail = validated_data.pop("remove_thumbnail", False) or validated_data.pop("remove_image", False)
|
|
|
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
thumbnail = None
|
|
|
|
if request and hasattr(request, "FILES"):
|
|
|
|
thumbnail = request.FILES.get("thumbnail") or request.FILES.get("image")
|
|
|
|
|
|
|
|
book = super().update(instance, validated_data)
|
|
|
|
if subject_area is not None:
|
|
|
|
book.subject_area.set(subject_area)
|
|
|
|
|
|
|
|
if remove_thumbnail:
|
|
|
|
instance.images.filter(book_volume__isnull=True, order=0).delete()
|
|
|
|
|
|
|
|
if thumbnail:
|
|
|
|
main_img = instance.images.filter(book_volume__isnull=True, order=0).first()
|
|
|
|
if main_img:
|
|
|
|
main_img.image = thumbnail
|
|
|
|
main_img.save()
|
|
|
|
else:
|
|
|
|
BookReferenceImage.objects.create(
|
|
|
|
book_reference=instance,
|
|
|
|
book_volume=None,
|
|
|
|
image=thumbnail,
|
|
|
|
order=0,
|
|
|
|
)
|
|
|
|
return book
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Category Quran Verse Admin Serializers
|
|
|
|
from ..models.category import CategoryQuranVerse, QuranSecondaryTranslation
|
|
|
|
|
|
|
|
class AdminQuranSecondaryTranslationSerializer(serializers.ModelSerializer):
|
|
|
|
id = serializers.IntegerField(required=False)
|
|
|
|
language_code = serializers.CharField(max_length=10, default="ru")
|
|
|
|
translation_text = serializers.CharField(allow_blank=True, default="")
|
|
|
|
translator = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = QuranSecondaryTranslation
|
|
|
|
fields = ["id", "language_code", "translation_text", "translator"]
|
|
|
|
|
|
|
|
class AdminCategoryQuranVerseSerializer(serializers.ModelSerializer):
|
|
|
|
category_detail = AdminHadisCategorySerializer(source="category", read_only=True)
|
|
|
|
surah_name = serializers.JSONField(default=list)
|
|
|
|
translation = serializers.JSONField(default=list)
|
|
|
|
translator = serializers.JSONField(default=list)
|
|
|
|
secondary_translations = AdminQuranSecondaryTranslationSerializer(many=True, required=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = CategoryQuranVerse
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"category",
|
|
|
|
"category_detail",
|
|
|
|
"surah_name",
|
|
|
|
"ayah_number",
|
|
|
|
"arabic_text",
|
|
|
|
"translation",
|
|
|
|
"translator",
|
|
|
|
"secondary_translations",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "created_at", "updated_at"]
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
from django.db import transaction
|
|
|
|
with transaction.atomic():
|
|
|
|
secondary_data = validated_data.pop("secondary_translations", [])
|
|
|
|
verse = CategoryQuranVerse.objects.create(**validated_data)
|
|
|
|
for sec in secondary_data:
|
|
|
|
QuranSecondaryTranslation.objects.create(
|
|
|
|
quran_verse=verse,
|
|
|
|
language_code=sec.get("language_code", "ru"),
|
|
|
|
translation_text=sec.get("translation_text", ""),
|
|
|
|
translator=sec.get("translator", ""),
|
|
|
|
)
|
|
|
|
return verse
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
from django.db import transaction
|
|
|
|
with transaction.atomic():
|
|
|
|
secondary_data = validated_data.pop("secondary_translations", None)
|
|
|
|
instance.surah_name = validated_data.get("surah_name", instance.surah_name)
|
|
|
|
instance.ayah_number = validated_data.get("ayah_number", instance.ayah_number)
|
|
|
|
instance.arabic_text = validated_data.get("arabic_text", instance.arabic_text)
|
|
|
|
instance.translation = validated_data.get("translation", instance.translation)
|
|
|
|
instance.translator = validated_data.get("translator", instance.translator)
|
|
|
|
instance.category = validated_data.get("category", instance.category)
|
|
|
|
instance.save()
|
|
|
|
|
|
|
|
if secondary_data is not None:
|
|
|
|
keep_ids = [item.get("id") for item in secondary_data if item.get("id")]
|
|
|
|
instance.secondary_translations.exclude(id__in=keep_ids).delete()
|
|
|
|
for sec in secondary_data:
|
|
|
|
sec_id = sec.get("id")
|
|
|
|
if sec_id:
|
|
|
|
QuranSecondaryTranslation.objects.filter(id=sec_id, quran_verse=instance).update(
|
|
|
|
language_code=sec.get("language_code", "ru"),
|
|
|
|
translation_text=sec.get("translation_text", ""),
|
|
|
|
translator=sec.get("translator", ""),
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
QuranSecondaryTranslation.objects.create(
|
|
|
|
quran_verse=instance,
|
|
|
|
language_code=sec.get("language_code", "ru"),
|
|
|
|
translation_text=sec.get("translation_text", ""),
|
|
|
|
translator=sec.get("translator", ""),
|
|
|
|
)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
class AdminHadisTransmitterSerializer(serializers.ModelSerializer):
|
|
|
|
transmitter_detail = AdminTransmitterListSerializer(source="transmitter", read_only=True)
|
|
|
|
uncertain_transmitters = serializers.PrimaryKeyRelatedField(
|
|
|
|
many=True,
|
|
|
|
queryset=Transmitters.objects.all(),
|
|
|
|
required=False
|
|
|
|
)
|
|
|
|
uncertain_transmitters_detail = AdminTransmitterListSerializer(
|
|
|
|
source="uncertain_transmitters",
|
|
|
|
many=True,
|
|
|
|
read_only=True
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = HadisTransmitter
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"hadis",
|
|
|
|
"transmitter",
|
|
|
|
"transmitter_detail",
|
|
|
|
"is_uncertain",
|
|
|
|
"uncertain_transmitters",
|
|
|
|
"uncertain_transmitters_detail",
|
|
|
|
"chain_index",
|
|
|
|
"narrator_layer",
|
|
|
|
"status",
|
|
|
|
"order",
|
|
|
|
"is_gap",
|
|
|
|
]
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
uncertain_transmitters = validated_data.pop("uncertain_transmitters", None)
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
if uncertain_transmitters is not None:
|
|
|
|
instance.uncertain_transmitters.set(uncertain_transmitters)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
uncertain_transmitters = validated_data.pop("uncertain_transmitters", None)
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
if uncertain_transmitters is not None:
|
|
|
|
instance.uncertain_transmitters.set(uncertain_transmitters)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
class AdminScholarlyOpinionSerializer(serializers.ModelSerializer):
|
|
|
|
opinion_text = serializers.JSONField(required=False, default=list)
|
|
|
|
status_detail = AdminOpinionStatusSerializer(source="status", read_only=True)
|
|
|
|
transmitters_count = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = ScholarlyOpinion
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"opinion_text",
|
|
|
|
"status",
|
|
|
|
"status_detail",
|
|
|
|
"transmitters_count",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_transmitters_count(self, obj):
|
|
|
|
if hasattr(obj, "transmitters_count_val"):
|
|
|
|
return obj.transmitters_count_val
|
|
|
|
return obj.transmitter_opinions.values("transmitter_id").distinct().count()
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterOpinionSerializer(serializers.ModelSerializer):
|
|
|
|
scholar_name = serializers.JSONField(required=False, default=list)
|
|
|
|
opinion_text = serializers.JSONField(required=False, default=list)
|
|
|
|
opinion_detail = AdminScholarlyOpinionSerializer(source="opinion", read_only=True)
|
|
|
|
status_detail = AdminTransmitterReliabilitySerializer(source="status", read_only=True)
|
|
|
|
scholar_detail = AdminBookAuthorSerializer(source="scholar", read_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = TransmitterOpinion
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"transmitter",
|
|
|
|
"scholar",
|
|
|
|
"scholar_detail",
|
|
|
|
"opinion",
|
|
|
|
"opinion_detail",
|
|
|
|
"scholar_name",
|
|
|
|
"opinion_text",
|
|
|
|
"status",
|
|
|
|
"status_detail",
|
|
|
|
"order",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
data = super().to_representation(instance)
|
|
|
|
if instance.opinion_id and instance.opinion:
|
|
|
|
data["opinion_text"] = instance.opinion.opinion_text
|
|
|
|
data["status"] = instance.opinion.status_id
|
|
|
|
if instance.opinion.status:
|
|
|
|
data["status_detail"] = AdminOpinionStatusSerializer(instance.opinion.status, context=self.context).data
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField(required=False, default=list)
|
|
|
|
text = serializers.CharField(required=False, allow_blank=True, default="")
|
|
|
|
translation = serializers.JSONField(required=False, default=list)
|
|
|
|
references = AdminOriginalTextReferenceSerializer(many=True, read_only=True)
|
|
|
|
|
|
|
|
# Writable relation fields
|
|
|
|
book_reference_id = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
address = serializers.JSONField(
|
|
|
|
required=False,
|
|
|
|
write_only=True,
|
|
|
|
allow_null=True
|
|
|
|
)
|
|
|
|
delete_image_ids = serializers.ListField(
|
|
|
|
child=serializers.IntegerField(),
|
|
|
|
required=False,
|
|
|
|
write_only=True
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = TransmitterOriginalText
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"transmitter",
|
|
|
|
"slug",
|
|
|
|
"title",
|
|
|
|
"text",
|
|
|
|
"translation",
|
|
|
|
"share_link",
|
|
|
|
"embedded_in",
|
|
|
|
"order",
|
|
|
|
"references",
|
|
|
|
"book_reference_id",
|
|
|
|
"address",
|
|
|
|
"delete_image_ids",
|
|
|
|
]
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
import json
|
|
|
|
data = safe_copy_data(data)
|
|
|
|
|
|
|
|
# Handle backward compatibility: if text is passed as a JSON list, extract primary text
|
|
|
|
text_val = data.get("text")
|
|
|
|
if isinstance(text_val, str) and (text_val.strip().startswith("[") or text_val.strip().startswith("{")):
|
|
|
|
try:
|
|
|
|
parsed = json.loads(text_val)
|
|
|
|
if isinstance(parsed, list):
|
|
|
|
p_txt = ""
|
|
|
|
for it in parsed:
|
|
|
|
if isinstance(it, dict) and it.get("language_code") == "ar":
|
|
|
|
p_txt = it.get("text") or it.get("title") or ""
|
|
|
|
break
|
|
|
|
if not p_txt and parsed:
|
|
|
|
p_txt = parsed[0].get("text") or parsed[0].get("title") or ""
|
|
|
|
data["text"] = p_txt
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
elif isinstance(text_val, list):
|
|
|
|
p_txt = ""
|
|
|
|
for it in text_val:
|
|
|
|
if isinstance(it, dict) and it.get("language_code") == "ar":
|
|
|
|
p_txt = it.get("text") or it.get("title") or ""
|
|
|
|
break
|
|
|
|
if not p_txt and text_val:
|
|
|
|
p_txt = text_val[0].get("text") or text_val[0].get("title") or ""
|
|
|
|
data["text"] = p_txt
|
|
|
|
|
|
|
|
for json_field in ["title", "translation", "address"]:
|
|
|
|
val = data.get(json_field)
|
|
|
|
if isinstance(val, str):
|
|
|
|
try:
|
|
|
|
data[json_field] = json.loads(val)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
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 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
|
|
|
|
|
|
|
|
addr_val = ref.address if ref and ref.address else ""
|
|
|
|
if isinstance(addr_val, str) and (addr_val.strip().startswith("[") or addr_val.strip().startswith("{")):
|
|
|
|
try:
|
|
|
|
ret["address"] = json.loads(addr_val)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
ret["address"] = addr_val
|
|
|
|
else:
|
|
|
|
ret["address"] = addr_val
|
|
|
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
ret["images"] = [
|
|
|
|
{
|
|
|
|
"id": img.id,
|
|
|
|
"image": absolute_https_url(img.image.url, request) if request and img.image else absolute_https_url(img.image.url)
|
|
|
|
}
|
|
|
|
for img in (ref.images.all() if ref else [])
|
|
|
|
]
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
request = self.context.get("request")
|
|
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
|
|
address = validated_data.pop("address", None)
|
|
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
|
|
|
|
# Pre-fill title from book reference if title is empty
|
|
|
|
title_val = validated_data.get("title")
|
|
|
|
has_title = isinstance(title_val, list) and any(
|
|
|
|
isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip()
|
|
|
|
for item in title_val
|
|
|
|
)
|
|
|
|
if not has_title and book_reference_id:
|
|
|
|
try:
|
|
|
|
b_ref = BookReference.objects.filter(id=book_reference_id.id if hasattr(book_reference_id, 'id') else book_reference_id).first()
|
|
|
|
if b_ref and b_ref.title:
|
|
|
|
validated_data["title"] = b_ref.title
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
|
|
|
instance = super().create(validated_data)
|
|
|
|
|
|
|
|
ref_data = {
|
|
|
|
"book_reference_id": book_reference_id,
|
|
|
|
"address": address,
|
|
|
|
"delete_image_ids": delete_image_ids,
|
|
|
|
}
|
|
|
|
save_relation_reference_data(instance, ref_data, request=request, ref_type="original_text")
|
|
|
|
|
|
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
|
|
if instance.auto_populate_title():
|
|
|
|
instance.save(update_fields=['title', 'slug', 'share_link'])
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
request = self.context.get("request")
|
|
|
|
book_reference_id = validated_data.pop("book_reference_id", None)
|
|
|
|
address = validated_data.pop("address", None)
|
|
|
|
delete_image_ids = validated_data.pop("delete_image_ids", [])
|
|
|
|
|
|
|
|
# Pre-fill title from book reference if title is empty
|
|
|
|
title_val = validated_data.get("title")
|
|
|
|
has_title = isinstance(title_val, list) and any(
|
|
|
|
isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip()
|
|
|
|
for item in title_val
|
|
|
|
)
|
|
|
|
if not has_title and book_reference_id:
|
|
|
|
try:
|
|
|
|
b_ref = BookReference.objects.filter(id=book_reference_id.id if hasattr(book_reference_id, 'id') else book_reference_id).first()
|
|
|
|
if b_ref and b_ref.title:
|
|
|
|
validated_data["title"] = b_ref.title
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
|
|
|
instance = super().update(instance, validated_data)
|
|
|
|
|
|
|
|
ref_data = {
|
|
|
|
"book_reference_id": book_reference_id,
|
|
|
|
"address": address,
|
|
|
|
"delete_image_ids": delete_image_ids,
|
|
|
|
}
|
|
|
|
save_relation_reference_data(instance, ref_data, request=request, ref_type="original_text")
|
|
|
|
|
|
|
|
if not instance.title or (isinstance(instance.title, list) and not any(isinstance(item, dict) and (item.get('text', '') or item.get('title', '')).strip() for item in instance.title)):
|
|
|
|
if instance.auto_populate_title():
|
|
|
|
instance.save(update_fields=['title', 'slug', 'share_link'])
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookVolumeSerializer(serializers.ModelSerializer):
|
|
|
|
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
remove_file = serializers.BooleanField(write_only=True, required=False, default=False)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookVolume
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"book_reference",
|
|
|
|
"edition",
|
|
|
|
"title",
|
|
|
|
"image",
|
|
|
|
"file",
|
|
|
|
"remove_image",
|
|
|
|
"remove_file",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
validated_data.pop("remove_image", False)
|
|
|
|
validated_data.pop("remove_file", False)
|
|
|
|
return super().create(validated_data)
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
remove_image = validated_data.pop("remove_image", False)
|
|
|
|
remove_file = validated_data.pop("remove_file", False)
|
|
|
|
if remove_image and instance.image:
|
|
|
|
instance.image.delete(save=False)
|
|
|
|
instance.image = None
|
|
|
|
if remove_file and instance.file:
|
|
|
|
instance.file.delete(save=False)
|
|
|
|
instance.file = None
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookResearcherSerializer(serializers.ModelSerializer):
|
|
|
|
id = serializers.IntegerField(required=False)
|
|
|
|
name = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookResearcher
|
|
|
|
fields = ["id", "name", "slug", "author"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookEditorSerializer(serializers.ModelSerializer):
|
|
|
|
id = serializers.IntegerField(required=False)
|
|
|
|
name = serializers.JSONField(required=False, default=list)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookEditor
|
|
|
|
fields = ["id", "name", "slug", "author"]
|
|
|
|
|
|
|
|
|
|
|
|
class AdminBookEditionSerializer(serializers.ModelSerializer):
|
|
|
|
title = serializers.JSONField(required=False, default=list)
|
|
|
|
books = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookReference.objects.all(),
|
|
|
|
many=True,
|
|
|
|
required=False,
|
|
|
|
)
|
|
|
|
publisher = serializers.JSONField(required=False, default=list)
|
|
|
|
city_of_publication = serializers.JSONField(required=False, default=list)
|
|
|
|
publisher_item = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookPublisher.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
publisher_item_detail = AdminBookPublisherSerializer(source="publisher_item", read_only=True)
|
|
|
|
city_of_publication_city = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=City.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
city_of_publication_city_detail = AdminCitySerializer(source="city_of_publication_city", read_only=True)
|
|
|
|
country_of_publication = serializers.JSONField(required=False, default=list)
|
|
|
|
country_of_publication_country = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=Country.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
country_of_publication_country_detail = AdminCountrySerializer(source="country_of_publication_country", read_only=True)
|
|
|
|
notes = serializers.JSONField(required=False, default=list)
|
|
|
|
tag = serializers.PrimaryKeyRelatedField(
|
|
|
|
queryset=BookTag.objects.all(),
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
)
|
|
|
|
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
|
|
|
|
volumes_detail = AdminBookVolumeSerializer(source="volumes", many=True, read_only=True)
|
|
|
|
number_of_volumes = serializers.SerializerMethodField()
|
|
|
|
books_detail = serializers.SerializerMethodField()
|
|
|
|
researchers = AdminBookResearcherSerializer(many=True, required=False)
|
|
|
|
editors = AdminBookEditorSerializer(many=True, required=False)
|
|
|
|
researcher_author_id = serializers.IntegerField(write_only=True, required=False, allow_null=True)
|
|
|
|
editor_author_id = serializers.IntegerField(write_only=True, required=False, allow_null=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BookEdition
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"title",
|
|
|
|
"book_reference",
|
|
|
|
"books",
|
|
|
|
"books_detail",
|
|
|
|
"publisher",
|
|
|
|
"publisher_item",
|
|
|
|
"publisher_item_detail",
|
|
|
|
"city_of_publication",
|
|
|
|
"city_of_publication_city",
|
|
|
|
"city_of_publication_city_detail",
|
|
|
|
"country_of_publication",
|
|
|
|
"country_of_publication_country",
|
|
|
|
"country_of_publication_country_detail",
|
|
|
|
"edition_number",
|
|
|
|
"year_of_publication",
|
|
|
|
"number_of_volumes",
|
|
|
|
"source_url",
|
|
|
|
"isbn",
|
|
|
|
"notes",
|
|
|
|
"tag",
|
|
|
|
"tag_detail",
|
|
|
|
"volumes_detail",
|
|
|
|
"researchers",
|
|
|
|
"editors",
|
|
|
|
"researcher_author_id",
|
|
|
|
"editor_author_id",
|
|
|
|
"created_at",
|
|
|
|
"updated_at",
|
|
|
|
]
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
ret = super().to_representation(instance)
|
|
|
|
# Use prefetched relations (no extra queries per row)
|
|
researchers = list(instance.researchers.all())
|
|
editors = list(instance.editors.all())
|
|
res = researchers[0] if researchers else None
|
|
ret["researcher_author_id"] = res.author_id if res and res.author_id else None
|
|
ed_obj = editors[0] if editors else None
|
|
ret["editor_author_id"] = ed_obj.author_id if ed_obj and ed_obj.author_id else None
|
|
ret["books"] = [b.id for b in instance.books.all()]
|
|
return ret
|
|
|
|
def get_number_of_volumes(self, obj):
|
|
return len(obj.volumes.all())
|
|
|
|
def get_books_detail(self, obj):
|
|
return [
|
|
{
|
|
"id": b.id,
|
|
"title": b.title,
|
|
"slug": b.slug,
|
|
}
|
|
for b in obj.books.all()
|
|
]
|
|
|
|
def _handle_author_links(self, edition, validated_data):
|
|
|
|
request = self.context.get("request")
|
|
|
|
data = request.data if request else {}
|
|
|
|
|
|
|
|
if "researcher_author_id" in data or "researcher_author_id" in validated_data:
|
|
|
|
res_val = validated_data.get("researcher_author_id") if "researcher_author_id" in validated_data else data.get("researcher_author_id")
|
|
|
|
edition.researchers.all().delete()
|
|
|
|
if res_val and res_val not in ["none", "", "null", None]:
|
|
|
|
try:
|
|
|
|
author_id = int(res_val)
|
|
|
|
author_obj = BookAuthor.objects.filter(id=author_id).first()
|
|
|
|
if author_obj:
|
|
|
|
BookResearcher.objects.create(
|
|
|
|
book_edition=edition,
|
|
|
|
author=author_obj,
|
|
|
|
name=author_obj.name,
|
|
|
|
slug=author_obj.slug
|
|
|
|
)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
if "editor_author_id" in data or "editor_author_id" in validated_data:
|
|
|
|
ed_val = validated_data.get("editor_author_id") if "editor_author_id" in validated_data else data.get("editor_author_id")
|
|
|
|
edition.editors.all().delete()
|
|
|
|
if ed_val and ed_val not in ["none", "", "null", None]:
|
|
|
|
try:
|
|
|
|
author_id = int(ed_val)
|
|
|
|
author_obj = BookAuthor.objects.filter(id=author_id).first()
|
|
|
|
if author_obj:
|
|
|
|
BookEditor.objects.create(
|
|
|
|
book_edition=edition,
|
|
|
|
author=author_obj,
|
|
|
|
name=author_obj.name,
|
|
|
|
slug=author_obj.slug
|
|
|
|
)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
books_data = validated_data.pop("books", None)
|
|
|
|
researchers_data = validated_data.pop("researchers", [])
|
|
|
|
editors_data = validated_data.pop("editors", [])
|
|
|
|
has_res_id = "researcher_author_id" in validated_data
|
|
|
|
has_ed_id = "editor_author_id" in validated_data
|
|
|
|
res_author_id = validated_data.pop("researcher_author_id", None)
|
|
|
|
ed_author_id = validated_data.pop("editor_author_id", None)
|
|
|
|
edition = super().create(validated_data)
|
|
|
|
for res_data in researchers_data:
|
|
|
|
BookResearcher.objects.create(book_edition=edition, **res_data)
|
|
|
|
for ed_data in editors_data:
|
|
|
|
BookEditor.objects.create(book_edition=edition, **ed_data)
|
|
|
|
author_links_dict = {}
|
|
|
|
if has_res_id:
|
|
|
|
author_links_dict["researcher_author_id"] = res_author_id
|
|
|
|
if has_ed_id:
|
|
|
|
author_links_dict["editor_author_id"] = ed_author_id
|
|
|
|
self._handle_author_links(edition, author_links_dict)
|
|
|
|
if books_data is not None:
|
|
|
|
edition.books.set(books_data)
|
|
|
|
if edition.book_reference and not edition.books.filter(id=edition.book_reference_id).exists():
|
|
|
|
edition.books.add(edition.book_reference)
|
|
|
|
return edition
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
books_data = validated_data.pop("books", None)
|
|
|
|
researchers_data = validated_data.pop("researchers", None)
|
|
|
|
editors_data = validated_data.pop("editors", None)
|
|
|
|
has_res_id = "researcher_author_id" in validated_data
|
|
|
|
has_ed_id = "editor_author_id" in validated_data
|
|
|
|
res_author_id = validated_data.pop("researcher_author_id", None)
|
|
|
|
ed_author_id = validated_data.pop("editor_author_id", None)
|
|
|
|
edition = super().update(instance, validated_data)
|
|
|
|
if researchers_data is not None:
|
|
|
|
# Delete researchers no longer in the request payload
|
|
|
|
keep_ids = [r.get("id") for r in researchers_data if r.get("id")]
|
|
|
|
instance.researchers.exclude(id__in=keep_ids).delete()
|
|
|
|
for res_data in researchers_data:
|
|
|
|
res_id = res_data.get("id")
|
|
|
|
if res_id:
|
|
|
|
# Update existing researcher
|
|
|
|
res_data_clean = {k: v for k, v in res_data.items() if k != "id"}
|
|
|
|
BookResearcher.objects.filter(id=res_id, book_edition=instance).update(**res_data_clean)
|
|
|
|
res_obj = BookResearcher.objects.filter(id=res_id, book_edition=instance).first()
|
|
|
|
if res_obj:
|
|
|
|
res_obj.save()
|
|
|
|
else:
|
|
|
|
# Create new researcher
|
|
|
|
BookResearcher.objects.create(book_edition=instance, **res_data)
|
|
|
|
|
|
|
|
if editors_data is not None:
|
|
|
|
# Delete editors no longer in the request payload
|
|
|
|
keep_ids = [e.get("id") for e in editors_data if e.get("id")]
|
|
|
|
instance.editors.exclude(id__in=keep_ids).delete()
|
|
|
|
for ed_data in editors_data:
|
|
|
|
ed_id = ed_data.get("id")
|
|
|
|
if ed_id:
|
|
|
|
# Update existing editor
|
|
|
|
ed_data_clean = {k: v for k, v in ed_data.items() if k != "id"}
|
|
|
|
BookEditor.objects.filter(id=ed_id, book_edition=instance).update(**ed_data_clean)
|
|
|
|
ed_obj = BookEditor.objects.filter(id=ed_id, book_edition=instance).first()
|
|
|
|
if ed_obj:
|
|
|
|
ed_obj.save()
|
|
|
|
else:
|
|
|
|
# Create new editor
|
|
|
|
BookEditor.objects.create(book_edition=instance, **ed_data)
|
|
|
|
|
|
|
|
if books_data is not None:
|
|
|
|
edition.books.set(books_data)
|
|
|
|
if edition.book_reference and not edition.books.filter(id=edition.book_reference_id).exists():
|
|
|
|
edition.books.add(edition.book_reference)
|
|
|
|
elif not edition.book_reference and edition.books.exists():
|
|
|
|
first_b = edition.books.first()
|
|
|
|
if first_b:
|
|
|
|
edition.book_reference = first_b
|
|
|
|
edition.save(update_fields=['book_reference'])
|
|
|
|
|
|
|
|
author_links_dict = {}
|
|
|
|
if has_res_id:
|
|
|
|
author_links_dict["researcher_author_id"] = res_author_id
|
|
|
|
if has_ed_id:
|
|
|
|
author_links_dict["editor_author_id"] = ed_author_id
|
|
|
|
self._handle_author_links(edition, author_links_dict)
|
|
|
|
return edition
|
|
|
|
|
|
|
|
|
|
|
|
class AdminContentReleaseSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = ContentRelease
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"version_name",
|
|
|
|
"published_at",
|
|
|
|
"description",
|
|
|
|
"is_active",
|
|
|
|
]
|
|
|
|
read_only_fields = ["id", "published_at"]
|
|
|
|
|
|
|