diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 0523a54..881d513 100644 --- a/apps/hadis/models/transmitter.py +++ b/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): diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 1631dd1..75e5986 100644 --- a/apps/hadis/serializers/hadis.py +++ b/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) diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 0396a73..3552081 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/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) diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index 8f311b8..9fcfa89 100644 --- a/apps/hadis/views_admin.py +++ b/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")