Browse Source

feat(hadis): support dynamic transmitter relatives with RelationType in admin API

master
Mohsen Taba 6 days ago
parent
commit
cfea75ac9d
  1. 24
      apps/hadis/models/transmitter.py
  2. 18
      apps/hadis/serializers/hadis.py
  3. 135
      apps/hadis/serializers/serializers_admin.py
  4. 2
      apps/hadis/views_admin.py

24
apps/hadis/models/transmitter.py

@ -1304,14 +1304,34 @@ class TransmitterRelative(models.Model):
def save(self, *args, **kwargs):
if self.relation_type_ref and not self.relation_type:
self.relation_type = self.relation_type_ref.code or ''
self.relation_type = (self.relation_type_ref.code or self.relation_type_ref.slug or str(self.relation_type_ref.id))[:50]
if self.relation_type_ref and self.relation_type_ref.title:
self.relation_value = self.relation_type_ref.title
elif self.relation_type and not self.relation_type_ref:
from .transmitter import RelationType
rel_ref = RelationType.objects.filter(code=self.relation_type).first()
rel_ref = RelationType.objects.filter(models.Q(code=self.relation_type) | models.Q(slug=self.relation_type)).first()
if rel_ref:
self.relation_type_ref = rel_ref
if self.relation_type and not self.relation_value:
self.relation_value = RELATION_TYPE_MAP.get(self.relation_type, [])
if self.narrator and not self.name:
name_val = ""
if isinstance(self.narrator.full_name, list) and self.narrator.full_name:
for lang in ("ar", "ru", "en"):
for item in self.narrator.full_name:
if isinstance(item, dict) and item.get("language_code") == lang and item.get("text"):
name_val = item["text"].strip()
if name_val:
break
if name_val:
break
if not name_val:
for item in self.narrator.full_name:
if isinstance(item, dict) and item.get("text"):
name_val = item["text"].strip()
if name_val:
break
self.name = (name_val or self.narrator.slug or str(self.narrator.id))[:255]
super().save(*args, **kwargs)
def __str__(self):

18
apps/hadis/serializers/hadis.py

@ -828,11 +828,12 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
def get_relatives(self, obj):
request = self.context.get('request')
relatives_qs = obj.relatives.all()
relatives_qs = obj.relatives.select_related('relation_type_ref', 'narrator').all()
grouped = {}
for rel in relatives_qs:
rel_type = rel.relation_type
rel_type = (rel.relation_type_ref.code or rel.relation_type_ref.slug or rel.relation_type) if rel.relation_type_ref else rel.relation_type
rel_val_src = (rel.relation_type_ref.title if (rel.relation_type_ref and rel.relation_type_ref.title) else rel.relation_value)
slug = rel.narrator.slug if rel.narrator else None
# Use narrator's localized name if exists and name is empty, otherwise use the direct name
@ -847,8 +848,8 @@ class TransmitterDetailSerializer(serializers.ModelSerializer):
if rel_type not in grouped:
grouped[rel_type] = {
"relation_value": get_localized_text(rel.relation_value, request) if rel.relation_value else None,
"arabic_relation_value": get_arabic_localized_text(rel.relation_value) if rel.relation_value else None,
"relation_value": get_localized_text(rel_val_src, request) if rel_val_src else None,
"arabic_relation_value": get_arabic_localized_text(rel_val_src) if rel_val_src else None,
"items": []
}
grouped[rel_type]["items"].append(item)
@ -941,11 +942,12 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
}
def _get_relatives(self, obj, request):
relatives_qs = obj.relatives.all()
relatives_qs = obj.relatives.select_related('relation_type_ref', 'narrator').all()
grouped = {}
for rel in relatives_qs:
rel_type = rel.relation_type
rel_type = (rel.relation_type_ref.code or rel.relation_type_ref.slug or rel.relation_type) if rel.relation_type_ref else rel.relation_type
rel_val_src = (rel.relation_type_ref.title if (rel.relation_type_ref and rel.relation_type_ref.title) else rel.relation_value)
slug = rel.narrator.slug if rel.narrator else None
name = rel.name
@ -959,8 +961,8 @@ class TransmitterSyncSerializer(serializers.ModelSerializer):
if rel_type not in grouped:
grouped[rel_type] = {
"relation_value": get_localized_text(rel.relation_value, request) if rel.relation_value else None,
"arabic_relation_value": get_arabic_localized_text(rel.relation_value) if rel.relation_value else None,
"relation_value": get_localized_text(rel_val_src, request) if rel_val_src else None,
"arabic_relation_value": get_arabic_localized_text(rel_val_src) if rel_val_src else None,
"items": []
}
grouped[rel_type]["items"].append(item)

135
apps/hadis/serializers/serializers_admin.py

@ -20,7 +20,7 @@ from ..models import (
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookPublisher, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText,
Transmitters, TransmitterReliability, TransmitterRelative, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText,
City, Country, Kunya, TransmitterFeature, TransmitterProfession, SahabiStatus, Madhhab, RelationType, YearValue, NarratorDate,
@ -4521,6 +4521,10 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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(),
@ -4649,6 +4653,10 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
"sons_ids",
"relatives",
"relatives_data",
"share_link",
"created_at",
@ -4661,7 +4669,7 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
"id", "slug", "share_link", "created_at", "updated_at",
"reliability_detail", "father_detail", "sons",
"reliability_detail", "father_detail", "sons", "relatives",
"birth_year_preview", "death_year_preview", "age_at_death_preview",
@ -4751,6 +4759,106 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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):
@ -4835,6 +4943,17 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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"]
@ -4967,6 +5086,8 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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)
@ -4981,6 +5102,10 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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)
@ -5075,6 +5200,8 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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)
@ -5097,6 +5224,10 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer):
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)

2
apps/hadis/views_admin.py

@ -969,6 +969,8 @@ class AdminTransmitterViewSet(ModelViewSet):
def get_queryset(self):
queryset = Transmitters.objects.select_related("reliability").prefetch_related("narrator_dates__year_value")
if getattr(self, "action", None) != "list":
queryset = queryset.prefetch_related("relatives__relation_type_ref", "relatives__narrator")

Loading…
Cancel
Save