From 6fd1a45afbdb68f94a229d58de1ca4266169f999 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 27 Oct 2026 10:38:02 +0330 Subject: [PATCH] feat(hadis): expose new narrator, author, edition fields and status descriptions in public serializers and views --- apps/hadis/models/reference.py | 8 + apps/hadis/serializers/hadis.py | 455 +++++++++++++++++-------- apps/hadis/serializers/reference_v2.py | 282 +++++++++------ apps/hadis/views/category.py | 52 ++- apps/hadis/views/hadis.py | 9 +- apps/hadis/views/reference_v2.py | 22 +- apps/hadis/views/transmitter.py | 29 +- 7 files changed, 585 insertions(+), 272 deletions(-) diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index a5332fe..8c71062 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -294,6 +294,10 @@ class BookPublisher(LowercaseSlugMixin, models.Model): return self.title[0].get('text', '') or self.title[0].get('title', '') return None + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.editions.all().update(publisher=self.title or []) + class BookEdition(models.Model): """ @@ -369,8 +373,12 @@ class BookEdition(models.Model): return f"{self.book_reference} - {self.edition_number or 'Edition ' + str(self.id)}" def save(self, *args, **kwargs): + if self.publisher_item_id and self.publisher_item: + self.publisher = self.publisher_item.title or [] if self.city_of_publication_city_id and self.city_of_publication_city: self.city_of_publication = self.city_of_publication_city.title or [] + if self.country_of_publication_country_id and self.country_of_publication_country: + self.country_of_publication = self.country_of_publication_country.title or [] super().save(*args, **kwargs) if self.book_reference and not self.books.filter(id=self.book_reference_id).exists(): self.books.add(self.book_reference) diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 75e5986..938f9b6 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -244,6 +244,7 @@ class HadisSyncSerializer(serializers.ModelSerializer): 'slug': t.reliability.slug, 'color': t.reliability.color, 'main_color_code': t.reliability.main_color_code, + 'description': get_localized_text(t.reliability.description, request), } uncertain_list = [] @@ -257,6 +258,7 @@ class HadisSyncSerializer(serializers.ModelSerializer): 'slug': ut.reliability.slug, 'color': ut.reliability.color, 'main_color_code': ut.reliability.main_color_code, + 'description': get_localized_text(ut.reliability.description, request), } uncertain_list.append({ 'id': ut.id, @@ -476,6 +478,8 @@ class TransmitterSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') + kunya = LocalizedField() + arabic_kunya = ArabicLocalizedField(source='kunya') known_as = LocalizedField() arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() @@ -495,7 +499,7 @@ class TransmitterSerializer(serializers.ModelSerializer): 'id', 'full_name', 'arabic_full_name', 'slug', 'birth_year_hijri', 'death_year_hijri', 'age_at_death', 'birth_year_raw', 'death_year_raw', 'age_at_death_raw', - 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', + 'kunya', 'arabic_kunya', 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability', 'madhhab', 'generation', 'share_link', 'gender' ] @@ -526,6 +530,8 @@ class TransmitterShortSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') + kunya = LocalizedField() + arabic_kunya = ArabicLocalizedField(source='kunya') known_as = LocalizedField() arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() @@ -541,7 +547,7 @@ class TransmitterShortSerializer(serializers.ModelSerializer): fields = [ 'id', 'full_name', 'arabic_full_name', 'slug', 'birth_year_hijri', 'death_year_hijri', 'birth_year_raw', 'death_year_raw', - 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability', 'gender' + 'kunya', 'arabic_kunya', 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability', 'gender' ] def get_birth_year_hijri(self, obj): @@ -691,6 +697,105 @@ class TransmitterOriginalTextSerializer(serializers.ModelSerializer): return images_list +def _serialize_transmitter_m2m_items(items, request, include_description=False): + result = [] + for item in items: + entry = { + 'id': item.id, + 'slug': getattr(item, 'slug', None), + 'title': get_localized_text(item.title, request), + 'arabic_title': get_arabic_localized_text(item.title), + } + if include_description: + entry['description'] = get_localized_text(getattr(item, 'description', None), request) + result.append(entry) + return result + + +def _resolve_transmitter_m2m_text(items, fallback_json, request, arabic=False, secondary_fallback_json=None): + parts = [] + for item in items: + txt = get_arabic_localized_text(item.title) if arabic else get_localized_text(item.title, request) + if txt and str(txt).strip(): + parts.append(str(txt).strip()) + if parts: + sep = '، ' if arabic else ', ' + return sep.join(parts) + if fallback_json: + val = get_arabic_localized_text(fallback_json) if arabic else get_localized_text(fallback_json, request) + if val: + return val + if secondary_fallback_json: + val = get_arabic_localized_text(secondary_fallback_json) if arabic else get_localized_text(secondary_fallback_json, request) + if val: + return val + return None + + +def _resolve_transmitter_city_text(city_obj, fallback_json, request, arabic=False): + if city_obj and getattr(city_obj, 'title', None): + val = get_arabic_localized_text(city_obj.title) if arabic else get_localized_text(city_obj.title, request) + if val: + return val + if fallback_json: + return get_arabic_localized_text(fallback_json) if arabic else get_localized_text(fallback_json, request) + return None + + +def _serialize_transmitter_relatives(obj, request): + from django.db.models import Q + from ..models.transmitter import RelationType + + relatives_qs = obj.relatives.select_related('relation_type_ref', 'narrator').all() + + grouped = {} + for rel in relatives_qs: + rt_obj = rel.relation_type_ref + if not rt_obj and rel.relation_type: + rt_obj = RelationType.objects.filter( + Q(code__iexact=rel.relation_type) | Q(slug__iexact=rel.relation_type) + ).first() + + rel_type = (rt_obj.code or rt_obj.slug or rel.relation_type) if rt_obj else rel.relation_type + rel_val_src = (rt_obj.title if (rt_obj and rt_obj.title) else rel.relation_value) + slug = rel.narrator.slug if rel.narrator else None + + name = rel.name + if rel.narrator and not name: + name = get_localized_text(rel.narrator.full_name, request) + arabic_name = ( + get_arabic_localized_text(rel.narrator.full_name) + if rel.narrator + else rel.name + ) or name + + item = { + "id": rel.narrator_id, + "name": name, + "arabic_name": arabic_name, + "slug": slug, + } + + if rel_type not in grouped: + grouped[rel_type] = { + "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) + + result = [] + for rel_type, data in grouped.items(): + result.append({ + "relation_type": rel_type, + "relation_value": data["relation_value"], + "arabic_relation_value": data["arabic_relation_value"], + "members": data["items"] + }) + + return result + + class TransmitterDetailSerializer(serializers.ModelSerializer): """ Serializer for Details of Transmitters """ @@ -700,23 +805,31 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() arabic_nickname = ArabicLocalizedField(source='nickname') - kunya = LocalizedField() - arabic_kunya = ArabicLocalizedField(source='kunya') - origin = LocalizedField() - arabic_origin = ArabicLocalizedField(source='origin') - + kunya = serializers.SerializerMethodField() + arabic_kunya = serializers.SerializerMethodField() + kunyas = serializers.SerializerMethodField() + origin = serializers.SerializerMethodField() + arabic_origin = serializers.SerializerMethodField() + mawla_status = LocalizedField(source='freed_slave_of') arabic_mawla_status = ArabicLocalizedField(source='freed_slave_of') - profession = LocalizedField(source='occupation') - arabic_profession = ArabicLocalizedField(source='occupation') - physical_traits = LocalizedField(source='features') - arabic_physical_traits = ArabicLocalizedField(source='features') - - lived_in = LocalizedField() - died_in = LocalizedField() + profession = serializers.SerializerMethodField() + arabic_profession = serializers.SerializerMethodField() + professions = serializers.SerializerMethodField() + physical_traits = serializers.SerializerMethodField() + arabic_physical_traits = serializers.SerializerMethodField() + features = serializers.SerializerMethodField() + + lived_in = serializers.SerializerMethodField() + arabic_lived_in = serializers.SerializerMethodField() + died_in = serializers.SerializerMethodField() + arabic_died_in = serializers.SerializerMethodField() description = LocalizedField() - companion_type = LocalizedField() - arabic_companion_type = ArabicLocalizedField(source='companion_type') + companion_type = serializers.SerializerMethodField() + arabic_companion_type = serializers.SerializerMethodField() + sahabi_status = serializers.SerializerMethodField() + madhhab = serializers.SerializerMethodField() + madhhabs = serializers.SerializerMethodField() birth_year_hijri = serializers.SerializerMethodField() death_year_hijri = serializers.SerializerMethodField() age_at_death = serializers.SerializerMethodField() @@ -732,18 +845,111 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): class Meta: model = Transmitters fields = [ - 'id','full_name', 'arabic_full_name', 'kunya', 'arabic_kunya', 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', - 'origin', 'arabic_origin', 'mawla_status', 'arabic_mawla_status', 'profession', 'arabic_profession', 'physical_traits', 'arabic_physical_traits', - 'lived_in','died_in','birth_year_hijri', - 'death_year_hijri','age_at_death', + 'id', 'full_name', 'arabic_full_name', 'kunya', 'arabic_kunya', 'kunyas', + 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', + 'origin', 'arabic_origin', 'mawla_status', 'arabic_mawla_status', + 'profession', 'arabic_profession', 'professions', + 'physical_traits', 'arabic_physical_traits', 'features', + 'lived_in', 'arabic_lived_in', 'died_in', 'arabic_died_in', + 'birth_year_hijri', 'death_year_hijri', 'age_at_death', 'birth_year_raw', 'death_year_raw', 'age_at_death_raw', 'reliability', - 'madhhab',"in_sahih_muslim","in_sahih_bukhari", - "description",'generation','share_link', - 'tadlis', 'ikhtilat', 'companion_type', 'arabic_companion_type', 'relatives', + 'madhhab', 'madhhabs', "in_sahih_muslim", "in_sahih_bukhari", + "description", 'generation', 'share_link', + 'tadlis', 'ikhtilat', 'companion_type', 'arabic_companion_type', 'sahabi_status', 'relatives', 'father', 'sons', 'gender' ] + def get_kunya(self, obj): + return _resolve_transmitter_m2m_text(obj.kunyas.all(), obj.kunya, self.context.get('request'), arabic=False) + + def get_arabic_kunya(self, obj): + return _resolve_transmitter_m2m_text(obj.kunyas.all(), obj.kunya, self.context.get('request'), arabic=True) + + def get_kunyas(self, obj): + return _serialize_transmitter_m2m_items(obj.kunyas.all(), self.context.get('request')) + + def get_profession(self, obj): + return _resolve_transmitter_m2m_text( + obj.professions_list.all(), obj.profession, self.context.get('request'), + arabic=False, secondary_fallback_json=obj.occupation + ) + + def get_arabic_profession(self, obj): + return _resolve_transmitter_m2m_text( + obj.professions_list.all(), obj.profession, self.context.get('request'), + arabic=True, secondary_fallback_json=obj.occupation + ) + + def get_professions(self, obj): + return _serialize_transmitter_m2m_items(obj.professions_list.all(), self.context.get('request')) + + def get_physical_traits(self, obj): + return _resolve_transmitter_m2m_text(obj.features_list.all(), obj.features, self.context.get('request'), arabic=False) + + def get_arabic_physical_traits(self, obj): + return _resolve_transmitter_m2m_text(obj.features_list.all(), obj.features, self.context.get('request'), arabic=True) + + def get_features(self, obj): + return _serialize_transmitter_m2m_items(obj.features_list.all(), self.context.get('request')) + + def get_origin(self, obj): + return _resolve_transmitter_city_text(obj.origin_city, obj.origin, self.context.get('request'), arabic=False) + + def get_arabic_origin(self, obj): + return _resolve_transmitter_city_text(obj.origin_city, obj.origin, self.context.get('request'), arabic=True) + + def get_lived_in(self, obj): + return _resolve_transmitter_city_text(obj.lived_in_city, obj.lived_in, self.context.get('request'), arabic=False) + + def get_arabic_lived_in(self, obj): + return _resolve_transmitter_city_text(obj.lived_in_city, obj.lived_in, self.context.get('request'), arabic=True) + + def get_died_in(self, obj): + return _resolve_transmitter_city_text(obj.died_in_city, obj.died_in, self.context.get('request'), arabic=False) + + def get_arabic_died_in(self, obj): + return _resolve_transmitter_city_text(obj.died_in_city, obj.died_in, self.context.get('request'), arabic=True) + + def get_companion_type(self, obj): + request = self.context.get('request') + if obj.sahabi_status and obj.sahabi_status.title: + val = get_localized_text(obj.sahabi_status.title, request) + if val: + return val + return get_localized_text(obj.companion_type, request) + + def get_arabic_companion_type(self, obj): + if obj.sahabi_status and obj.sahabi_status.title: + val = get_arabic_localized_text(obj.sahabi_status.title) + if val: + return val + return get_arabic_localized_text(obj.companion_type) + + def get_sahabi_status(self, obj): + if obj.sahabi_status: + request = self.context.get('request') + return { + 'id': obj.sahabi_status.id, + 'slug': obj.sahabi_status.slug, + 'title': get_localized_text(obj.sahabi_status.title, request), + 'arabic_title': get_arabic_localized_text(obj.sahabi_status.title), + 'description': get_localized_text(obj.sahabi_status.description, request), + } + return None + + def get_madhhab(self, obj): + if obj.madhhab: + return obj.madhhab + request = self.context.get('request') + m_items = list(obj.madhhabs.all()) + if m_items: + return get_localized_text(m_items[0].title, request) or m_items[0].slug + return None + + def get_madhhabs(self, obj): + return _serialize_transmitter_m2m_items(obj.madhhabs.all(), self.context.get('request'), include_description=True) + def get_birth_year_hijri(self, obj): return format_transmitter_date(obj, 'birth', self.context.get('request')) @@ -827,43 +1033,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): return None def get_relatives(self, obj): - request = self.context.get('request') - relatives_qs = obj.relatives.select_related('relation_type_ref', 'narrator').all() - - grouped = {} - for rel in relatives_qs: - 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 - name = rel.name - if rel.narrator and not name: - name = get_localized_text(rel.narrator.full_name, request) - - item = { - "name": name, - "slug": slug - } - - if rel_type not in grouped: - grouped[rel_type] = { - "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) - - result = [] - for rel_type, data in grouped.items(): - result.append({ - "relation_type": rel_type, - "relation_value": data["relation_value"], - "arabic_relation_value": data["arabic_relation_value"], - "members": data["items"] - }) - - return result + return _serialize_transmitter_relatives(obj, self.context.get('request')) class TransmittersFiltersSerializer(serializers.ModelSerializer): @@ -890,36 +1060,68 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): """Get biographical information (flattened)""" request = self.context.get('request') # ← FIX: Define request if obj.reliability: - r= { + r = { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, request), + 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, 'description': get_localized_text(obj.reliability.description, request), } - else : - r= None - + else: + r = None + + sahabi_status_data = None + if obj.sahabi_status: + sahabi_status_data = { + 'id': obj.sahabi_status.id, + 'slug': obj.sahabi_status.slug, + 'title': get_localized_text(obj.sahabi_status.title, request), + 'arabic_title': get_arabic_localized_text(obj.sahabi_status.title), + 'description': get_localized_text(obj.sahabi_status.description, request), + } + + companion_val = ( + get_localized_text(obj.sahabi_status.title, request) + if (obj.sahabi_status and obj.sahabi_status.title) + else get_localized_text(obj.companion_type, request) + ) + arabic_companion_val = ( + get_arabic_localized_text(obj.sahabi_status.title) + if (obj.sahabi_status and obj.sahabi_status.title) + else get_arabic_localized_text(obj.companion_type) + ) + + madhhab_val = obj.madhhab + madhhabs_objs = list(obj.madhhabs.all()) + if not madhhab_val and madhhabs_objs: + madhhab_val = get_localized_text(madhhabs_objs[0].title, request) or madhhabs_objs[0].slug + return { 'full_name': get_localized_text(obj.full_name, request), 'arabic_full_name': get_arabic_localized_text(obj.full_name), 'gender': obj.gender, - 'kunya': get_localized_text(obj.kunya, request), - 'arabic_kunya': get_arabic_localized_text(obj.kunya), + 'kunya': _resolve_transmitter_m2m_text(obj.kunyas.all(), obj.kunya, request, arabic=False), + 'arabic_kunya': _resolve_transmitter_m2m_text(obj.kunyas.all(), obj.kunya, request, arabic=True), + 'kunyas': _serialize_transmitter_m2m_items(obj.kunyas.all(), request), 'known_as': get_localized_text(obj.known_as, request), 'arabic_known_as': get_arabic_localized_text(obj.known_as), 'nickname': get_localized_text(obj.nickname, request), 'arabic_nickname': get_arabic_localized_text(obj.nickname), - 'origin': get_localized_text(obj.origin, request), - 'arabic_origin': get_arabic_localized_text(obj.origin), + 'origin': _resolve_transmitter_city_text(obj.origin_city, obj.origin, request, arabic=False), + 'arabic_origin': _resolve_transmitter_city_text(obj.origin_city, obj.origin, request, arabic=True), 'mawla_status': get_localized_text(obj.freed_slave_of, request), 'arabic_mawla_status': get_arabic_localized_text(obj.freed_slave_of), - 'profession': get_localized_text(obj.occupation, request), - 'arabic_profession': get_arabic_localized_text(obj.occupation), - 'physical_traits': get_localized_text(obj.features, request), - 'arabic_physical_traits': get_arabic_localized_text(obj.features), - 'lived_in': get_localized_text(obj.lived_in, request), - 'died_in': get_localized_text(obj.died_in, request), + 'profession': _resolve_transmitter_m2m_text(obj.professions_list.all(), obj.profession, request, arabic=False, secondary_fallback_json=obj.occupation), + 'arabic_profession': _resolve_transmitter_m2m_text(obj.professions_list.all(), obj.profession, request, arabic=True, secondary_fallback_json=obj.occupation), + 'professions': _serialize_transmitter_m2m_items(obj.professions_list.all(), request), + 'physical_traits': _resolve_transmitter_m2m_text(obj.features_list.all(), obj.features, request, arabic=False), + 'arabic_physical_traits': _resolve_transmitter_m2m_text(obj.features_list.all(), obj.features, request, arabic=True), + 'features': _serialize_transmitter_m2m_items(obj.features_list.all(), request), + 'lived_in': _resolve_transmitter_city_text(obj.lived_in_city, obj.lived_in, request, arabic=False), + 'arabic_lived_in': _resolve_transmitter_city_text(obj.lived_in_city, obj.lived_in, request, arabic=True), + 'died_in': _resolve_transmitter_city_text(obj.died_in_city, obj.died_in, request, arabic=False), + 'arabic_died_in': _resolve_transmitter_city_text(obj.died_in_city, obj.died_in, request, arabic=True), 'birth_year_hijri': format_transmitter_date(obj, 'birth', request), 'death_year_hijri': format_transmitter_date(obj, 'death', request), 'age_at_death': format_transmitter_date(obj, 'age', request), @@ -928,7 +1130,8 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): 'age_at_death_raw': obj.age_at_death, 'generation': obj.generation, 'reliability': r, - 'madhhab': obj.madhhab, + 'madhhab': madhhab_val, + 'madhhabs': _serialize_transmitter_m2m_items(madhhabs_objs, request, include_description=True), 'in_sahih_muslim': obj.in_sahih_muslim, 'in_sahih_bukhari': obj.in_sahih_bukhari, 'description': get_localized_text(obj.description, request), @@ -936,47 +1139,14 @@ class TransmitterSyncSerializer(serializers.ModelSerializer): 'share_link': obj.share_link, 'tadlis': obj.tadlis, 'ikhtilat': obj.ikhtilat, - 'companion_type': get_localized_text(obj.companion_type, request), - 'arabic_companion_type': get_arabic_localized_text(obj.companion_type), + 'companion_type': companion_val, + 'arabic_companion_type': arabic_companion_val, + 'sahabi_status': sahabi_status_data, 'relatives': self._get_relatives(obj, request), } def _get_relatives(self, obj, request): - relatives_qs = obj.relatives.select_related('relation_type_ref', 'narrator').all() - - grouped = {} - for rel in relatives_qs: - 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 - if rel.narrator and not name: - name = get_localized_text(rel.narrator.full_name, request) - - item = { - "name": name, - "slug": slug - } - - if rel_type not in grouped: - grouped[rel_type] = { - "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) - - result = [] - for rel_type, data in grouped.items(): - result.append({ - "relation_type": rel_type, - "relation_value": data["relation_value"], - "arabic_relation_value": data["arabic_relation_value"], - "members": data["items"] - }) - - return result + return _serialize_transmitter_relatives(obj, request) def get_scholars_opinions(self, obj): """Get all scholarly opinions about this transmitter""" @@ -1172,11 +1342,12 @@ class HadisReferenceSerializer(serializers.ModelSerializer): edition = obj.book_reference.editions.first() if edition: request = self.context.get("request") - pub = edition.publisher - if isinstance(pub, list): - pub = get_localized_text(pub, request) + pub_src = edition.publisher_item.title if (getattr(edition, "publisher_item_id", None) and edition.publisher_item and edition.publisher_item.title) else edition.publisher + pub = get_localized_text(pub_src, request) if isinstance(pub_src, list) else pub_src + ed_title = get_localized_text(edition.title, request) if isinstance(getattr(edition, "title", None), list) else getattr(edition, "title", None) return { "id": edition.id, + "title": ed_title or "", "edition_number": edition.edition_number, "publisher": pub or "", "year_of_publication": edition.year_of_publication or "", @@ -1254,7 +1425,7 @@ class HadisReferenceSerializer(serializers.ModelSerializer): pub = getattr(obj.book_reference, "publisher", None) if pub is None and first_ed: - pub = first_ed.publisher + pub = first_ed.publisher_item.title if (getattr(first_ed, "publisher_item_id", None) and first_ed.publisher_item and first_ed.publisher_item.title) else first_ed.publisher if isinstance(pub, list): pub = get_localized_text(pub, request) @@ -1350,28 +1521,29 @@ class HadisReferenceSerializer(serializers.ModelSerializer): # 3. Loop through authors and convert each object to a string (name) # Result will be like: ["Al-Bukhari", "Muslim"] out = [] + lang = _extract_request_lang(self.context.get('request')) for author in authors: name = field.to_representation(author.name) # Format birth and death dates (Hijri/CE) - birth_hijri = author.birth_year_hijri + birth_hijri = author.get_formatted_birth_year(lang=lang) if hasattr(author, 'get_formatted_birth_year') else author.birth_year_hijri birth_miladi = author.birth_year_miladi - death_hijri = author.death_year_hijri + death_hijri = author.get_formatted_death_year(lang=lang) if hasattr(author, 'get_formatted_death_year') else author.death_year_hijri death_miladi = author.death_year_miladi birth_str = "" if birth_hijri and birth_miladi: - birth_str = f"{birth_hijri}AH/{birth_miladi}CE" + birth_str = f"{birth_hijri} / {birth_miladi}CE" elif birth_hijri: - birth_str = f"{birth_hijri}AH" + birth_str = f"{birth_hijri}" elif birth_miladi: birth_str = f"{birth_miladi}CE" death_str = "" if death_hijri and death_miladi: - death_str = f"{death_hijri}AH/{death_miladi}CE" + death_str = f"{death_hijri} / {death_miladi}CE" elif death_hijri: - death_str = f"{death_hijri}AH" + death_str = f"{death_hijri}" elif death_miladi: death_str = f"{death_miladi}CE" @@ -1624,7 +1796,13 @@ class NarratorRelationSerializer(serializers.ModelSerializer): سریالایزر سبک برای نمایش راویان به عنوان استاد یا شاگرد """ full_name = LocalizedField() + arabic_full_name = ArabicLocalizedField(source='full_name') + kunya = LocalizedField() + arabic_kunya = ArabicLocalizedField(source='kunya') known_as = LocalizedField() + arabic_known_as = ArabicLocalizedField(source='known_as') + nickname = LocalizedField() + arabic_nickname = ArabicLocalizedField(source='nickname') birth_year_hijri = serializers.SerializerMethodField() death_year_hijri = serializers.SerializerMethodField() birth_year_raw = serializers.CharField(source='birth_year_hijri', read_only=True) @@ -1634,7 +1812,8 @@ class NarratorRelationSerializer(serializers.ModelSerializer): class Meta: model = Transmitters fields = [ - 'id', 'slug', 'full_name', 'known_as', + 'id', 'slug', 'full_name', 'arabic_full_name', + 'kunya', 'arabic_kunya', 'known_as', 'arabic_known_as', 'nickname', 'arabic_nickname', 'birth_year_hijri', 'death_year_hijri', 'birth_year_raw', 'death_year_raw', 'reliability', 'gender' ] @@ -1652,10 +1831,12 @@ class NarratorRelationSerializer(serializers.ModelSerializer): return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, request), + 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, 'description': get_localized_text(obj.reliability.description, request), } + return None # --- سریالایزرهای کمکی تصاویر رفرنس --- @@ -1717,11 +1898,12 @@ class BaseDetailedReferenceSerializer(serializers.ModelSerializer): edition = obj.book_reference.editions.first() if edition: request = self.context.get("request") - pub = edition.publisher - if isinstance(pub, list): - pub = get_localized_text(pub, request) + pub_src = edition.publisher_item.title if (getattr(edition, "publisher_item_id", None) and edition.publisher_item and edition.publisher_item.title) else edition.publisher + pub = get_localized_text(pub_src, request) if isinstance(pub_src, list) else pub_src + ed_title = get_localized_text(edition.title, request) if isinstance(getattr(edition, "title", None), list) else getattr(edition, "title", None) return { "id": edition.id, + "title": ed_title or "", "edition_number": edition.edition_number, "publisher": pub or "", "year_of_publication": edition.year_of_publication or "", @@ -1779,7 +1961,7 @@ class DetailedCorrectionReferenceSerializer(BaseDetailedReferenceSerializer): pub = getattr(obj.book_reference, "publisher", None) if pub is None and first_ed: - pub = first_ed.publisher + pub = first_ed.publisher_item.title if (getattr(first_ed, "publisher_item_id", None) and first_ed.publisher_item and first_ed.publisher_item.title) else first_ed.publisher if isinstance(pub, list): pub = get_localized_text(pub, request) @@ -1815,24 +1997,7 @@ class DetailedCorrectionReferenceSerializer(BaseDetailedReferenceSerializer): return None def get_edition(self, obj): - edition = obj.edition - if not edition and getattr(obj, "book_volume", None) and obj.book_volume.edition: - edition = obj.book_volume.edition - if not edition and getattr(obj, "book_reference", None) and hasattr(obj.book_reference, "editions"): - edition = obj.book_reference.editions.first() - if edition: - request = self.context.get('request') - pub = edition.publisher - if isinstance(pub, list): - pub = get_localized_text(pub, request) - return { - 'id': edition.id, - 'edition_number': edition.edition_number, - 'publisher': pub or '', - 'year_of_publication': edition.year_of_publication or '', - 'number_of_volumes': edition.number_of_volumes, - } - return None + return self.get_edition_info(obj) def get_open_book_url(self, obj): request = self.context.get('request') diff --git a/apps/hadis/serializers/reference_v2.py b/apps/hadis/serializers/reference_v2.py index c2107d4..8b3c60a 100644 --- a/apps/hadis/serializers/reference_v2.py +++ b/apps/hadis/serializers/reference_v2.py @@ -95,8 +95,23 @@ class BookReferenceV2ListSerializer(serializers.ModelSerializer): return absolute_https_url(first_image.image.url) return None +def _get_book_editions(obj): + seen = set() + result = [] + for ed in obj.editions.all(): + seen.add(ed.id) + result.append(ed) + if hasattr(obj, 'editions_list'): + for ed in obj.editions_list.all(): + if ed.id not in seen: + seen.add(ed.id) + result.append(ed) + return result + + class BookEditionV2Serializer(serializers.ModelSerializer): - publisher = LocalizedField() + title = LocalizedField() + publisher = serializers.SerializerMethodField() notes = LocalizedField() tag = BookTagV2Serializer(read_only=True) publication_place = serializers.SerializerMethodField() @@ -107,11 +122,19 @@ class BookEditionV2Serializer(serializers.ModelSerializer): class Meta: model = BookEdition fields = [ - 'id', 'publisher', 'publication_place', 'edition_number', + 'id', 'title', 'publisher', 'publication_place', 'edition_number', 'year_of_publication', 'number_of_volumes', 'notes', 'source_url', 'tag', 'researchers', 'editors' ] + def get_publisher(self, obj): + request = self.context.get("request") + if getattr(obj, "publisher_item_id", None) and obj.publisher_item and obj.publisher_item.title: + val = get_localized_text(obj.publisher_item.title, request) + if val: + return val + return get_localized_text(obj.publisher, request) if obj.publisher else None + def get_number_of_volumes(self, obj): count = obj.volumes.count() if count > 0: @@ -120,8 +143,18 @@ class BookEditionV2Serializer(serializers.ModelSerializer): def get_publication_place(self, obj): request = self.context.get("request") - city = get_localized_text(obj.city_of_publication, request) if obj.city_of_publication else "" - country = get_localized_text(obj.country_of_publication, request) if obj.country_of_publication else "" + city_src = ( + obj.city_of_publication_city.title + if (getattr(obj, "city_of_publication_city_id", None) and obj.city_of_publication_city and obj.city_of_publication_city.title) + else obj.city_of_publication + ) + country_src = ( + obj.country_of_publication_country.title + if (getattr(obj, "country_of_publication_country_id", None) and obj.country_of_publication_country and obj.country_of_publication_country.title) + else obj.country_of_publication + ) + city = get_localized_text(city_src, request) if city_src else "" + country = get_localized_text(country_src, request) if country_src else "" return { "city": city, "country": country @@ -193,7 +226,8 @@ class BookReferenceV2DetailSerializer(serializers.ModelSerializer): def get_information(self, obj): request = self.context.get("request") summary = get_localized_text(obj.description, request) if obj.description else "" - valid_editions = [ed for ed in obj.editions.all() if ed.volumes.exists()] + all_editions = _get_book_editions(obj) + valid_editions = [ed for ed in all_editions if ed.volumes.exists()] or all_editions editions = BookEditionV2Serializer(valid_editions, many=True, context=self.context).data return { @@ -203,15 +237,18 @@ class BookReferenceV2DetailSerializer(serializers.ModelSerializer): def get_volumes(self, obj): if obj.has_editions: - editions = obj.editions.all() + editions = _get_book_editions(obj) editions_data = [] + request = self.context.get("request") for ed in editions: vol_qs = ed.volumes.all() if not vol_qs.exists(): continue + ed_title = get_localized_text(ed.title, request) if ed.title else None editions_data.append({ "edition_id": ed.id, - "edition_statement": ed.edition_number or f"Edition {ed.id}", + "edition_title": ed_title, + "edition_statement": ed.edition_number or ed_title or f"Edition {ed.id}", "volumes": BookVolumeV2Serializer(vol_qs, many=True, context=self.context).data }) return { @@ -251,7 +288,8 @@ class BookReferenceV2SyncSerializer(serializers.ModelSerializer): def get_information(self, obj): request = self.context.get("request") summary = get_localized_text(obj.description, request) if obj.description else "" - valid_editions = [ed for ed in obj.editions.all() if ed.volumes.exists()] + all_editions = _get_book_editions(obj) + valid_editions = [ed for ed in all_editions if ed.volumes.exists()] or all_editions editions = BookEditionV2Serializer(valid_editions, many=True, context=self.context).data return { "summary": summary, @@ -267,15 +305,18 @@ class BookReferenceV2SyncSerializer(serializers.ModelSerializer): def get_volumes(self, obj): if obj.has_editions: - editions = obj.editions.all() + editions = _get_book_editions(obj) editions_data = [] + request = self.context.get("request") for ed in editions: vol_qs = ed.volumes.all() if not vol_qs.exists(): continue + ed_title = get_localized_text(ed.title, request) if ed.title else None editions_data.append({ "edition_id": ed.id, - "edition_statement": ed.edition_number or f"Edition {ed.id}", + "edition_title": ed_title, + "edition_statement": ed.edition_number or ed_title or f"Edition {ed.id}", "volumes": BookVolumeV2Serializer(vol_qs, many=True, context=self.context).data }) return { @@ -291,7 +332,7 @@ class BookReferenceV2SyncSerializer(serializers.ModelSerializer): } from apps.hadis.models.reference import BookAuthor, BookReference -from .category import LocalizedField +from .category import LocalizedField, ArabicLocalizedField, get_arabic_localized_text def _get_request_lang_v2(serializer_instance) -> str: from django.utils.translation import get_language @@ -304,58 +345,60 @@ def _get_request_lang_v2(serializer_instance) -> str: return str(lang).split(",")[0].split("-")[0].strip().lower() or "ru" -class BookAuthorListSerializer(serializers.ModelSerializer): - """سریالایزر برای لیست نویسنده‌ها""" - name = LocalizedField() - known_as = LocalizedField() - scholarity = LocalizedField() - tabaqa = LocalizedField() - rutba = serializers.SerializerMethodField() - features = LocalizedField() - related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) - birth_year_hijri = serializers.SerializerMethodField() - death_year_hijri = serializers.SerializerMethodField() - age_at_death = serializers.SerializerMethodField() - birth_year_canonical = serializers.CharField(source='birth_year_hijri', read_only=True) - death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) - age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) - - class Meta: - model = BookAuthor - fields = [ - 'id', 'slug', 'name', 'image', - 'known_as', 'scholarity', 'tabaqa', 'rutba', 'features', - 'birth_year_hijri', 'death_year_hijri', 'age_at_death', - 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', - 'birth_year_miladi', 'death_year_miladi', - 'related_narrator_slug', - ] - - def get_rutba(self, obj): - request = self.context.get('request') - if obj.rutba_status: - return get_localized_text(obj.rutba_status.title, request) - return get_localized_text(obj.rutba, request) if obj.rutba else None - - def get_birth_year_hijri(self, obj): - return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) - - def get_death_year_hijri(self, obj): - return obj.get_formatted_death_year(lang=_get_request_lang_v2(self)) - - def get_age_at_death(self, obj): - return obj.get_formatted_age(lang=_get_request_lang_v2(self)) +def _resolve_author_city(city_obj, fallback_json, request, arabic=False): + if city_obj and getattr(city_obj, 'title', None): + val = get_arabic_localized_text(city_obj.title) if arabic else get_localized_text(city_obj.title, request) + if val: + return val + if fallback_json: + return get_arabic_localized_text(fallback_json) if arabic else get_localized_text(fallback_json, request) + return None + + +def _resolve_author_features(obj, request, arabic=False): + parts = [] + for item in obj.features_list.all(): + txt = get_arabic_localized_text(item.title) if arabic else get_localized_text(item.title, request) + if txt and str(txt).strip(): + parts.append(str(txt).strip()) + if parts: + sep = '، ' if arabic else ', ' + return sep.join(parts) + if obj.features: + return get_arabic_localized_text(obj.features) if arabic else get_localized_text(obj.features, request) + return None + + +def _serialize_author_features_detail(obj, request): + return [ + { + 'id': item.id, + 'slug': item.slug, + 'title': get_localized_text(item.title, request), + 'arabic_title': get_arabic_localized_text(item.title), + } + for item in obj.features_list.all() + ] -class BookAuthorDetailSerializer(serializers.ModelSerializer): - """سریالایزر برای دیتیل یک نویسنده""" +class _BaseBookAuthorV2Mixin(serializers.Serializer): name = LocalizedField() + arabic_name = ArabicLocalizedField(source='name') known_as = LocalizedField() + arabic_known_as = ArabicLocalizedField(source='known_as') scholarity = LocalizedField() tabaqa = LocalizedField() rutba = serializers.SerializerMethodField() rutba_detail = serializers.SerializerMethodField() - features = LocalizedField() + features = serializers.SerializerMethodField() + arabic_features = serializers.SerializerMethodField() + features_detail = serializers.SerializerMethodField() + origin = serializers.SerializerMethodField() + arabic_origin = serializers.SerializerMethodField() + lived_in = serializers.SerializerMethodField() + arabic_lived_in = serializers.SerializerMethodField() + died_in = serializers.SerializerMethodField() + arabic_died_in = serializers.SerializerMethodField() related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) birth_year_hijri = serializers.SerializerMethodField() death_year_hijri = serializers.SerializerMethodField() @@ -364,17 +407,6 @@ class BookAuthorDetailSerializer(serializers.ModelSerializer): death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) - class Meta: - model = BookAuthor - fields = [ - 'id', 'slug', 'name', 'image', 'known_as', 'scholarity', - 'tabaqa', 'rutba', 'rutba_detail', 'features', - 'birth_year_hijri', 'death_year_hijri', 'age_at_death', - 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', - 'birth_year_miladi', 'death_year_miladi', - 'related_narrator_slug' - ] - def get_birth_year_hijri(self, obj): return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) @@ -403,6 +435,69 @@ class BookAuthorDetailSerializer(serializers.ModelSerializer): } return None + def get_features(self, obj): + return _resolve_author_features(obj, self.context.get('request'), arabic=False) + + def get_arabic_features(self, obj): + return _resolve_author_features(obj, self.context.get('request'), arabic=True) + + def get_features_detail(self, obj): + return _serialize_author_features_detail(obj, self.context.get('request')) + + def get_origin(self, obj): + return _resolve_author_city(obj.origin_city, obj.origin, self.context.get('request'), arabic=False) + + def get_arabic_origin(self, obj): + return _resolve_author_city(obj.origin_city, obj.origin, self.context.get('request'), arabic=True) + + def get_lived_in(self, obj): + return _resolve_author_city(obj.lived_in_city, obj.lived_in, self.context.get('request'), arabic=False) + + def get_arabic_lived_in(self, obj): + return _resolve_author_city(obj.lived_in_city, obj.lived_in, self.context.get('request'), arabic=True) + + def get_died_in(self, obj): + return _resolve_author_city(obj.died_in_city, obj.died_in, self.context.get('request'), arabic=False) + + def get_arabic_died_in(self, obj): + return _resolve_author_city(obj.died_in_city, obj.died_in, self.context.get('request'), arabic=True) + + +class BookAuthorListSerializer(_BaseBookAuthorV2Mixin, serializers.ModelSerializer): + """سریالایزر برای لیست نویسنده‌ها""" + + class Meta: + model = BookAuthor + fields = [ + 'id', 'slug', 'name', 'arabic_name', 'image', + 'known_as', 'arabic_known_as', 'scholarity', 'tabaqa', + 'rutba', 'rutba_detail', + 'features', 'arabic_features', 'features_detail', + 'origin', 'arabic_origin', 'lived_in', 'arabic_lived_in', 'died_in', 'arabic_died_in', + 'birth_year_hijri', 'death_year_hijri', 'age_at_death', + 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', + 'birth_year_miladi', 'death_year_miladi', + 'related_narrator_slug', + ] + + +class BookAuthorDetailSerializer(_BaseBookAuthorV2Mixin, serializers.ModelSerializer): + """سریالایزر برای دیتیل یک نویسنده""" + + class Meta: + model = BookAuthor + fields = [ + 'id', 'slug', 'name', 'arabic_name', 'image', + 'known_as', 'arabic_known_as', 'scholarity', 'tabaqa', + 'rutba', 'rutba_detail', + 'features', 'arabic_features', 'features_detail', + 'origin', 'arabic_origin', 'lived_in', 'arabic_lived_in', 'died_in', 'arabic_died_in', + 'birth_year_hijri', 'death_year_hijri', 'age_at_death', + 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', + 'birth_year_miladi', 'death_year_miladi', + 'related_narrator_slug', + ] + class AuthorBookReferenceSerializer(serializers.ModelSerializer): """سریالایزر برای لیست رفرنس‌های (کتاب‌های) یک نویسنده""" @@ -437,26 +532,11 @@ class BookAuthorSyncReferenceSerializer(serializers.ModelSerializer): model = BookReference fields = ['id', 'slug', 'title'] -class BookAuthorSyncSerializer(serializers.ModelSerializer): +class BookAuthorSyncSerializer(_BaseBookAuthorV2Mixin, serializers.ModelSerializer): """ سریالایزر برای سینک آفلاین تمام نویسنده‌ها. شامل تمام اطلاعات و لیست کتاب‌های متصل به این نویسنده. """ - name = LocalizedField() - known_as = LocalizedField() - scholarity = LocalizedField() - tabaqa = LocalizedField() - rutba = serializers.SerializerMethodField() - rutba_detail = serializers.SerializerMethodField() - features = LocalizedField() - related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) - birth_year_hijri = serializers.SerializerMethodField() - death_year_hijri = serializers.SerializerMethodField() - age_at_death = serializers.SerializerMethodField() - birth_year_canonical = serializers.CharField(source='birth_year_hijri', read_only=True) - death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) - age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) - # اضافه کردن لیست کتاب‌ها books = BookAuthorSyncReferenceSerializer(source='book_references', many=True, read_only=True) @@ -466,12 +546,22 @@ class BookAuthorSyncSerializer(serializers.ModelSerializer): 'id', 'slug', 'name', + 'arabic_name', 'known_as', + 'arabic_known_as', 'scholarity', 'tabaqa', 'rutba', 'rutba_detail', 'features', + 'arabic_features', + 'features_detail', + 'origin', + 'arabic_origin', + 'lived_in', + 'arabic_lived_in', + 'died_in', + 'arabic_died_in', 'birth_year_hijri', 'death_year_hijri', 'age_at_death', @@ -483,31 +573,3 @@ class BookAuthorSyncSerializer(serializers.ModelSerializer): 'books', # 👈 لیست کتاب‌های هر نویسنده 'related_narrator_slug' ] - - def get_birth_year_hijri(self, obj): - return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) - - def get_death_year_hijri(self, obj): - return obj.get_formatted_death_year(lang=_get_request_lang_v2(self)) - - def get_age_at_death(self, obj): - return obj.get_formatted_age(lang=_get_request_lang_v2(self)) - - def get_rutba(self, obj): - request = self.context.get('request') - if obj.rutba_status: - return get_localized_text(obj.rutba_status.title, request) - return get_localized_text(obj.rutba, request) if obj.rutba else None - - def get_rutba_detail(self, obj): - if obj.rutba_status: - request = self.context.get('request') - return { - 'id': obj.rutba_status.id, - 'title': get_localized_text(obj.rutba_status.title, request), - 'slug': obj.rutba_status.slug, - 'color': obj.rutba_status.color, - 'main_color_code': obj.rutba_status.main_color_code, - 'description': get_localized_text(obj.rutba_status.description, request), - } - return None diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index 559e0d5..0feff83 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -669,10 +669,16 @@ class HadisCategoryXMindView(APIView): # Fetch active hadiths with their transmitters and corrections transmitter_qs = HadisTransmitter.objects.select_related( 'transmitter__reliability', + 'transmitter__sahabi_status', + 'transmitter__origin_city', + 'transmitter__lived_in_city', + 'transmitter__died_in_city', 'status', 'narrator_layer' ).prefetch_related( - 'uncertain_transmitters__reliability' + 'transmitter__narrator_dates__year_value', + 'uncertain_transmitters__reliability', + 'uncertain_transmitters__narrator_dates__year_value', ).order_by('order') hadiths = Hadis.objects.filter( @@ -793,6 +799,7 @@ class HadisCategoryXMindView(APIView): "color": getattr(rel_obj, 'color', None), "main_color_code": getattr(rel_obj, 'main_color_code', None), "slug": getattr(rel_obj, 'slug', None), + "description": self.get_localized(getattr(rel_obj, 'description', None), lang), } layer_obj = ht.narrator_layer @@ -818,6 +825,7 @@ class HadisCategoryXMindView(APIView): "color": getattr(ut_rel, 'color', None), "main_color_code": getattr(ut_rel, 'main_color_code', None), "slug": getattr(ut_rel, 'slug', None), + "description": self.get_localized(getattr(ut_rel, 'description', None), lang), } uncertain_candidates.append({ "id": ut.id, @@ -834,8 +842,12 @@ class HadisCategoryXMindView(APIView): "arabic_known_as": self.get_arabic(ut.known_as), "nickname": self.get_localized(ut.nickname, lang), "arabic_nickname": self.get_arabic(ut.nickname), - "birth_year_hijri": ut.birth_year_hijri, - "death_year_hijri": ut.death_year_hijri, + "birth_year_hijri": ut.get_formatted_birth_year(lang=lang), + "death_year_hijri": ut.get_formatted_death_year(lang=lang), + "age_at_death": ut.get_formatted_age(lang=lang), + "birth_year_raw": ut.birth_year_hijri, + "death_year_raw": ut.death_year_hijri, + "age_at_death_raw": ut.age_at_death, "reliability": ut_rel_data, "thumbnail": self._get_thumbnail_url(ut.thumbnail, request), "share_link": ut.share_link, @@ -844,6 +856,16 @@ class HadisCategoryXMindView(APIView): node_type_str = "uncertain" if is_node_uncertain else "narrator" node_id_str = f"uncertain-{'-'.join(map(str, cand_ids))}" if is_node_uncertain else f"narrator-{tr.id if tr else ht.id}" + tr_companion = None + tr_arabic_companion = None + if tr: + if tr.sahabi_status and tr.sahabi_status.title: + tr_companion = self.get_localized(tr.sahabi_status.title, lang) + tr_arabic_companion = self.get_arabic(tr.sahabi_status.title) + else: + tr_companion = self.get_localized(tr.companion_type, lang) + tr_arabic_companion = self.get_arabic(tr.companion_type) + tr_node_data = { "id": node_id_str, "transmitter_id": tr.id if tr else (uncertain_candidates[0]["id"] if uncertain_candidates else None), @@ -860,13 +882,29 @@ class HadisCategoryXMindView(APIView): "arabic_known_as": self.get_arabic(tr.known_as) if tr else None, "nickname": self.get_localized(tr.nickname, lang) if tr else None, "arabic_nickname": self.get_arabic(tr.nickname) if tr else None, - "birth_year_hijri": tr.birth_year_hijri if tr else None, - "death_year_hijri": tr.death_year_hijri if tr else None, - "age_at_death": tr.age_at_death if tr else None, + "origin": self.get_localized(tr.origin_city.title if (tr.origin_city_id and tr.origin_city and tr.origin_city.title) else tr.origin, lang) if tr else None, + "arabic_origin": self.get_arabic(tr.origin_city.title if (tr.origin_city_id and tr.origin_city and tr.origin_city.title) else tr.origin) if tr else None, + "lived_in": self.get_localized(tr.lived_in_city.title if (tr.lived_in_city_id and tr.lived_in_city and tr.lived_in_city.title) else tr.lived_in, lang) if tr else None, + "arabic_lived_in": self.get_arabic(tr.lived_in_city.title if (tr.lived_in_city_id and tr.lived_in_city and tr.lived_in_city.title) else tr.lived_in) if tr else None, + "died_in": self.get_localized(tr.died_in_city.title if (tr.died_in_city_id and tr.died_in_city and tr.died_in_city.title) else tr.died_in, lang) if tr else None, + "arabic_died_in": self.get_arabic(tr.died_in_city.title if (tr.died_in_city_id and tr.died_in_city and tr.died_in_city.title) else tr.died_in) if tr else None, + "mawla_status": self.get_localized(tr.freed_slave_of, lang) if tr else None, + "arabic_mawla_status": self.get_arabic(tr.freed_slave_of) if tr else None, + "profession": (self.get_localized(tr.profession, lang) or self.get_localized(tr.occupation, lang)) if tr else None, + "arabic_profession": (self.get_arabic(tr.profession) or self.get_arabic(tr.occupation)) if tr else None, + "physical_traits": self.get_localized(tr.features, lang) if tr else None, + "arabic_physical_traits": self.get_arabic(tr.features) if tr else None, + "birth_year_hijri": tr.get_formatted_birth_year(lang=lang) if tr else None, + "death_year_hijri": tr.get_formatted_death_year(lang=lang) if tr else None, + "age_at_death": tr.get_formatted_age(lang=lang) if tr else None, + "birth_year_raw": tr.birth_year_hijri if tr else None, + "death_year_raw": tr.death_year_hijri if tr else None, + "age_at_death_raw": tr.age_at_death if tr else None, "generation": tr.generation if tr else None, "tadlis": tr.tadlis if tr else None, "ikhtilat": tr.ikhtilat if tr else None, - "companion_type": tr.companion_type if tr else None, + "companion_type": tr_companion, + "arabic_companion_type": tr_arabic_companion, "madhhab": tr.madhhab if tr else None, "in_sahih_muslim": tr.in_sahih_muslim if tr else None, "in_sahih_bukhari": tr.in_sahih_bukhari if tr else None, diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 079e54a..a912198 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -299,6 +299,7 @@ class HadisListView(ListAPIView): "title": get_localized_text(status.title, request=request, language_code=lang) or "", "color": status.color, "main_color_code": status.main_color_code, + "description": get_localized_text(status.description, request=request, language_code=lang), } for status in HadisStatus.objects.all().order_by('order') ] @@ -1099,8 +1100,13 @@ class HadisFiltersView(ListAPIView): title_text = get_localized_text(status.title, request) if title_text and status.slug: statuses.append({ + 'id': status.id, 'text': title_text, - 'slug': status.slug + 'title': title_text, + 'slug': status.slug, + 'color': status.color, + 'main_color_code': status.main_color_code, + 'description': get_localized_text(status.description, request), }) # Get categories from HadisCategory model (excluding empty leaf categories) @@ -1230,6 +1236,7 @@ class HadisFiltersSyncAPIView(APIView): "order": status.order, "color": status.color, "main_color_code": status.main_color_code, + "description": get_localized_text(status.description, request=request, language_code=lang), } for status in HadisStatus.objects.all().order_by('order') ] diff --git a/apps/hadis/views/reference_v2.py b/apps/hadis/views/reference_v2.py index 9b75f0f..c51e45e 100644 --- a/apps/hadis/views/reference_v2.py +++ b/apps/hadis/views/reference_v2.py @@ -19,9 +19,19 @@ class BookReferenceV2DetailView(generics.RetrieveAPIView): .select_related('author', 'tag') .prefetch_related( 'editions__tag', + 'editions__publisher_item', + 'editions__city_of_publication_city', + 'editions__country_of_publication_country', 'editions__researchers__author', 'editions__editors__author', 'editions__volumes__images', + 'editions_list__tag', + 'editions_list__publisher_item', + 'editions_list__city_of_publication_city', + 'editions_list__country_of_publication_country', + 'editions_list__researchers__author', + 'editions_list__editors__author', + 'editions_list__volumes__images', 'volumes__images', 'images', ) @@ -282,8 +292,8 @@ class BookAuthorListView(ListAPIView): """ queryset = ( BookAuthor.objects - .select_related('related_narrator', 'rutba_status') - .prefetch_related('author_dates__year_value') + .select_related('related_narrator', 'rutba_status', 'origin_city', 'lived_in_city', 'died_in_city') + .prefetch_related('author_dates__year_value', 'features_list') .order_by('-created_at') ) serializer_class = BookAuthorListSerializer @@ -307,8 +317,8 @@ class BookAuthorDetailView(RetrieveAPIView): """ queryset = ( BookAuthor.objects - .select_related('related_narrator', 'rutba_status') - .prefetch_related('author_dates__year_value') + .select_related('related_narrator', 'rutba_status', 'origin_city', 'lived_in_city', 'died_in_city') + .prefetch_related('author_dates__year_value', 'features_list') .all() ) serializer_class = BookAuthorDetailSerializer @@ -357,8 +367,8 @@ class BookAuthorSyncView(generics.ListAPIView): # 👇 استفاده از prefetch_related برای جلوگیری از کرش کردن دیتابیس به خاطر N+1 Query queryset = ( BookAuthor.objects - .select_related('related_narrator', 'rutba_status') - .prefetch_related('book_references', 'author_dates__year_value') + .select_related('related_narrator', 'rutba_status', 'origin_city', 'lived_in_city', 'died_in_city') + .prefetch_related('book_references', 'author_dates__year_value', 'features_list') .order_by('id') ) serializer_class = BookAuthorSyncSerializer diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 4455d13..76eabe3 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -99,10 +99,22 @@ class TransmitterDetailView(RetrieveAPIView): def get_queryset(self): input = self.kwargs.get('narrator_slug') - return Transmitters.objects.filter(slug=input).select_related('reliability', 'father').prefetch_related( + return Transmitters.objects.filter(slug=input).select_related( + 'reliability', + 'father', + 'sahabi_status', + 'origin_city', + 'lived_in_city', + 'died_in_city', + ).prefetch_related( 'narrator_dates__year_value', 'children_as_father', 'relatives__narrator', + 'relatives__relation_type_ref', + 'kunyas', + 'features_list', + 'professions_list', + 'madhhabs', ) class TransmitterOpinionView(ListAPIView): @@ -181,9 +193,19 @@ class TransmitterSyncView(ListAPIView): return ( Transmitters.objects # 👇 ۱. واکشی اطلاعات reliability راوی در همان کوئری اصلی - .select_related('reliability') + .select_related( + 'reliability', + 'sahabi_status', + 'origin_city', + 'lived_in_city', + 'died_in_city', + ) .prefetch_related( 'narrator_dates__year_value', + 'kunyas', + 'features_list', + 'professions_list', + 'madhhabs', # 👇 ۲. اضافه کردن 'scholar' برای جلوگیری از کوئری اضافه در نظرات دانشمندان Prefetch('opinions', queryset=TransmitterOpinion.objects.select_related('status', 'scholar', 'opinion', 'opinion__status')), @@ -197,7 +219,7 @@ class TransmitterSyncView(ListAPIView): 'references__images' ) ), - Prefetch('relatives', queryset=TransmitterRelative.objects.select_related('narrator')) + Prefetch('relatives', queryset=TransmitterRelative.objects.select_related('narrator', 'relation_type_ref')) ) .order_by('id') ) @@ -291,6 +313,7 @@ def get_transmitter_filters(request): 'slug': reliability.slug, 'color': reliability.color, 'main_color_code': reliability.main_color_code, + 'description': get_localized_text(reliability.description, request), }) return {