from rest_framework import serializers import json from django.utils.translation import gettext_lazy as _ from rest_framework.fields import SerializerMethodField from urllib3 import request from .category import LocalizedField, ArabicLocalizedField from .category import get_localized_text, get_arabic_localized_text from .category import get_localized_text from apps.bookmark.serializers.bookmark import BookmarkStatusSerializer from utils import absolute_https_url from ..models import ( Hadis, HadisStatus, HadisTag, HadisTransmitter, HadisReference, ReferenceImage, Transmitters, HadisCollection, TransmitterOpinion, TransmitterOriginalText, BookReference, BookReferenceImage, BookAuthor, HadisCorrection,HadisInterpretation , CorrectionReferenceImage , InterpretationReferenceImage , OriginalTextReferenceImage,CorrectionReference,InterpretationReference,OriginalTextReference ) def get_localized_correction_text(value, request=None): """ Resolve HadisCorrection.text in the same multilingual style used across the hadis app. Supports: - a parsed JSON list: [{"language_code": "en", "text": "..."}] - a JSON string containing that list - plain text fallback """ if value in (None, ""): return value parsed_value = value if isinstance(value, str): stripped = value.strip() if stripped.startswith("[") or stripped.startswith("{"): try: parsed_value = json.loads(stripped) except (TypeError, ValueError, json.JSONDecodeError): return value if isinstance(parsed_value, list): return get_localized_text(parsed_value, request=request) return value class PinnedHadisCollectionSerializer(serializers.ModelSerializer): title = LocalizedField() summary = LocalizedField() thumbnail = serializers.SerializerMethodField() class Meta: model = HadisCollection # فیلدهایی که برای نمایش در بخش پین شده (مثل اسلایدر یا لیست بالا) نیاز هست fields = ['id', 'title', 'slug', 'summary', 'thumbnail', 'order', 'created_at'] def get_thumbnail(self, obj): if obj.thumbnail: request = self.context.get('request') if request: return absolute_https_url(obj.thumbnail.url, request) return absolute_https_url(obj.thumbnail.url) return None class HadisCollectionListSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() title = LocalizedField() summary = LocalizedField() class Meta: model = HadisCollection fields = ['id', 'title', 'summary','slug', 'thumbnail'] def get_thumbnail(self, obj): if obj.thumbnail: request = self.context.get('request') if request: return absolute_https_url(obj.thumbnail.url, request) return absolute_https_url(obj.thumbnail.url) return None class HadisSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all hadis data (grouped fields)""" detail = serializers.SerializerMethodField() narrators = serializers.SerializerMethodField() explanations = serializers.SerializerMethodField() corrections = serializers.SerializerMethodField() category_slug = serializers.CharField(source='category.slug', read_only=True) interpretations = serializers.SerializerMethodField() title =LocalizedField() title_narrator = LocalizedField() translation = LocalizedField() class Meta: model = Hadis fields = [ # header (no-extend) 'id', 'number', 'slug', 'category_id', 'category_slug', 'title', 'title_narrator', 'text', 'translation', # grouped sections 'detail', 'narrators', 'explanations', 'corrections', 'interpretations' ] def get_detail(self, obj): request = self.context.get('request') # status status_block = None if obj.hadis_status: status_block = { 'id': obj.hadis_status.id, 'title': get_localized_text(obj.hadis_status.title, request), 'description': get_localized_text(obj.hadis_status.description, request), 'color': obj.hadis_status.color, 'main_color_code': obj.hadis_status.main_color_code, } # tags (already prefetched) tags_block = [ {'id': tag.id, 'title': get_localized_text(tag.title, request)} for tag in obj.tags.all() ] # references and reference images (already prefetched) references_block = [] reference_images_block = [] for reference in obj.references.all(): book = reference.book_reference authors = [book.author] if book and book.author else [] references_block.append({ 'id': reference.id, 'title': get_localized_text(book.title, request) if book else None, 'authors': [ {'id': a.id, 'name': get_localized_text(a.name, request)} for a in authors ], 'description': book.description if book else None, 'share_link': book.share_link if book else None, }) for img in reference.images.all(): thumb_url = None if img.thumbnail: thumb_url = absolute_https_url(img.thumbnail.url, request) reference_images_block.append({ 'id': img.id, 'thumbnail': thumb_url, 'priority': img.priority, }) # Process address_details address_details_list = [] if hasattr(obj, 'address_details') and obj.address_details and isinstance(obj.address_details, list): address_details_list = sorted( obj.address_details, key=lambda x: x.get('priority', 0) ) return { 'address': get_localized_text(obj.address, request), 'address_details': address_details_list, 'hadis_status': status_block, 'status_text': get_localized_text(obj.hadis_status_text, request), 'share_link': obj.share_link, 'links': obj.links, 'tags': tags_block, 'references': references_block, 'reference_images': reference_images_block, } def get_narrators(self, obj): request = self.context.get('request') transmitters_data = [] for transmitter_rel in obj.transmitters.all(): t = transmitter_rel.transmitter layer = transmitter_rel.narrator_layer reliability_block = None if t and t.reliability: reliability_block = { 'id': t.reliability.id, 'title': get_localized_text(t.reliability.title, request), 'slug': t.reliability.slug, 'color': t.reliability.color, 'main_color_code': t.reliability.main_color_code, } uncertain_list = [] if transmitter_rel.is_uncertain: for ut in transmitter_rel.uncertain_transmitters.all(): ut_rel_block = None if ut.reliability: ut_rel_block = { 'id': ut.reliability.id, 'title': get_localized_text(ut.reliability.title, request), 'slug': ut.reliability.slug, 'color': ut.reliability.color, 'main_color_code': ut.reliability.main_color_code, } uncertain_list.append({ 'id': ut.id, 'name': get_localized_text(ut.full_name, request), 'slug': ut.slug, 'known_as': get_localized_text(ut.known_as, request), 'nickname': get_localized_text(ut.nickname, request), 'birth_year_hijri': ut.birth_year_hijri, 'death_year_hijri': ut.death_year_hijri, 'reliability': ut_rel_block, }) transmitters_data.append({ 'id': t.id if t else None, 'name': get_localized_text(t.full_name, request) if t else None, 'slug': t.slug if t else None, 'known_as': get_localized_text(t.known_as, request) if t else None, 'nickname': get_localized_text(t.nickname, request) if t else None, 'reliability': reliability_block, 'layer_id': layer.id if layer else None, 'narrator_layer_id': layer.id if layer else None, 'layer_level': layer.number if layer else None, 'layer_name': get_localized_text(layer.name, request) if layer else None, 'layer_description': get_localized_text(layer.description, request) if layer else None, 'is_gap': transmitter_rel.is_gap, 'is_uncertain': transmitter_rel.is_uncertain, 'uncertain_transmitters': uncertain_list, 'birth_year_hijri': t.birth_year_hijri if t else None, 'death_year_hijri': t.death_year_hijri if t else None, 'order': transmitter_rel.order, }) return { 'description': get_localized_text(obj.description, request), 'transmitters': transmitters_data, } def get_explanations(self, obj): request = self.context.get('request') # Return structured explanations with title and description explanations_list = [] if hasattr(obj, 'explanations') and obj.explanations and isinstance(obj.explanations, list): for item in obj.explanations: if isinstance(item, dict): lang = item.get('language_code') # Check if matches request language request_lang = request.query_params.get('lang', 'en') if request else 'en' if lang == request_lang: explanations_list.append({ 'title': item.get('title', ''), 'description': item.get('description', '') }) # If no match, return first available or fallback to old explanation field if not explanations_list and obj.explanation: return get_localized_text(obj.explanation, request) return explanations_list if explanations_list else None def get_corrections(self, obj): """گرفتن لیست کامل تصحیحات با تمام جزئیات و رفرنس‌ها""" # استفاده از سریالایزر Detail که در مرحله قبل ساختیم return HadisCorrectionDetailSerializer( obj.hadiscorrection_set.all(), many=True, context=self.context ).data def get_interpretations(self, obj): """گرفتن لیست کامل تفاسیر متصل به دسته‌بندی این حدیث با جزئیات کامل""" if obj.category: return HadisInterpretationDetailSerializer( obj.category.interpretations.all(), many=True, context=self.context ).data return [] class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" category = serializers.SerializerMethodField() status = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField() translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() address = serializers.SerializerMethodField() links = serializers.SerializerMethodField() reference_images = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = Hadis fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text', 'translation', 'category', 'status', 'bookmark', 'share_link', 'address', 'links', 'reference_images', 'images'] def get_address(self, obj): formatted = format_address_output(obj.address) if formatted: return formatted refs = list(obj.references.all()) first_ref = refs[0] if refs else None if first_ref and hasattr(first_ref, 'address') and first_ref.address: return format_address_output(first_ref.address) return [] def get_links(self, obj): return format_links_output(obj.links) def get_reference_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in sorted(ref.images.all(), key=lambda x: getattr(x, 'priority', 0)): url = None if img.thumbnail: url = absolute_https_url(img.thumbnail.url, request) if request else absolute_https_url(img.thumbnail.url) elif hasattr(img, 'image') and img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) if url: images_list.append({ "id": img.id, "image": url, "thumbnail": url, "priority": img.priority, }) return images_list def get_images(self, obj): return self.get_reference_images(obj) def get_category(self, obj): """Get category id and title""" if not obj.category: return None request = self.context.get('request') # Use your helper method title = get_localized_text(obj.category.title, request) or "" # Safe access for sect_type to prevent crashes if sect is missing # Note: Ensure your view uses .select_related('category__sect') for speed! sect_type = obj.category.sect.sect_type if obj.category.sect else None return { 'id': obj.category.id, 'title': title, 'slug': obj.category.slug, 'source_type': obj.category.source_type, 'sect_type': sect_type } def get_status(self, obj): """Get status id and title""" if not obj.hadis_status: return None request = self.context.get('request') # Use your helper method title = get_localized_text(obj.hadis_status.title, request) description = get_localized_text(obj.hadis_status.description, request) return { 'id': obj.hadis_status.id, 'title': title, 'description': description, 'color': obj.hadis_status.color, 'main_color_code': obj.hadis_status.main_color_code, } def get_bookmark(self, obj): """Get bookmark information for this playlist.""" request = self.context.get('request') user = request.user if request else None book_mark = BookmarkStatusSerializer.get_bookmark_info( obj=obj, user=user, service='hadith' ) return book_mark.get('is_bookmarked', False) class HadisStatusSerializer(serializers.ModelSerializer): """Serializer for HadisStatus""" title = LocalizedField() description = LocalizedField() main_color_code = serializers.ReadOnlyField() class Meta: model = HadisStatus fields = ['id', 'title', 'color', 'main_color_code', 'description'] class HadisTagSerializer(serializers.ModelSerializer): """Serializer for HadisTag""" title = LocalizedField() class Meta: model = HadisTag fields = ['id', 'title'] class TransmitterSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') known_as = LocalizedField() arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() arabic_nickname = ArabicLocalizedField(source='nickname') reliability = serializers.SerializerMethodField() share_link = serializers.CharField(read_only=True) class Meta: model = Transmitters fields = [ 'id', 'full_name', 'arabic_full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability','madhhab','generation','share_link' ] def get_reliability(self, obj): """Serialize the reliability foreign key""" if obj.reliability: return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } return None class TransmitterShortSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') known_as = LocalizedField() arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() arabic_nickname = ArabicLocalizedField(source='nickname') reliability = serializers.SerializerMethodField() class Meta: model = Transmitters fields = [ 'id', 'full_name', 'arabic_full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability' ] def get_reliability(self, obj): if obj.reliability: request = self.context.get('request') 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, } return None class TransmitterOpinionSerializer(serializers.ModelSerializer): """ Serializer for TransmitterOpinions """ scholar_name = LocalizedField() opinion_text = LocalizedField() arabic_opinion_text = ArabicLocalizedField(source='opinion_text') status = serializers.SerializerMethodField() scholar = serializers.SerializerMethodField() class Meta: model = TransmitterOpinion fields = ['id','transmitter','scholar_name','scholar','opinion_text','arabic_opinion_text','status'] def get_scholar(self, obj): request = self.context.get('request') if obj.scholar: return { 'id': obj.scholar.id, 'name': get_localized_text(obj.scholar.name, request), 'slug': obj.scholar.slug } return None def get_status(self, obj): """Serialize the opinion status foreign key""" if obj.status: request = self.context.get('request') return { 'id': obj.status.id, 'title': get_localized_text(obj.status.title, request), 'slug': obj.status.slug, 'color': obj.status.color, 'main_color_code': obj.status.main_color_code, } return None def format_address_output(ref_or_data): if not ref_or_data: return [] if hasattr(ref_or_data, 'address'): addr = ref_or_data.address else: addr = ref_or_data if not addr: return [] if isinstance(addr, str): trimmed = addr.strip() if trimmed.startswith("[") or trimmed.startswith("{"): try: import json parsed = json.loads(trimmed) return format_address_output(parsed) except (ValueError, TypeError): pass return [trimmed] if trimmed else [] elif isinstance(addr, list): result = [] for item in addr: if isinstance(item, str): s = item.strip() if s.startswith("[") or s.startswith("{"): try: import json parsed = json.loads(s) result.extend(format_address_output(parsed)) continue except (ValueError, TypeError): pass if s: result.append(s) elif isinstance(item, dict): # could be {'title': ['pok'], 'language_code': 'fa'} or {'title': 'pok'} or {'address': '...'} or {'text': '...'} val = item.get('title') or item.get('address') or item.get('text') or item.get('value') if val: result.extend(format_address_output(val)) elif isinstance(item, list): result.extend(format_address_output(item)) return result return [str(addr)] def format_links_output(links_data): if not links_data: return [] if isinstance(links_data, str): trimmed = links_data.strip() if trimmed.startswith("[") or trimmed.startswith("{"): try: import json parsed = json.loads(trimmed) return format_links_output(parsed) except (ValueError, TypeError): pass return [trimmed] if trimmed else [] elif isinstance(links_data, dict): out = [] for k, v in links_data.items(): if isinstance(v, str) and v.strip(): out.append(v.strip()) elif isinstance(v, (list, dict)): out.extend(format_links_output(v)) return out elif isinstance(links_data, list): out = [] for item in links_data: if isinstance(item, str): s = item.strip() if s: out.append(s) elif isinstance(item, dict): url = item.get('url') or item.get('link') or item.get('href') or item.get('value') if url and isinstance(url, str) and url.strip(): out.append(url.strip()) elif isinstance(item, list): out.extend(format_links_output(item)) return out return [] class TransmitterOriginalTextSerializer(serializers.ModelSerializer): """ Serializer for TransmitterOriginalText """ title = LocalizedField() text = LocalizedField() translation = LocalizedField() address = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = TransmitterOriginalText fields = ['id', 'title', 'text', 'translation', 'share_link', 'slug', 'address', 'images'] def get_address(self, obj): refs = list(obj.references.all()) return format_address_output(refs[0] if refs else None) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in ref.images.all(): if img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) images_list.append({ "id": img.id, "image": url, "thumbnail": url, }) return images_list class TransmitterDetailSerializer(serializers.ModelSerializer): """ Serializer for Details of Transmitters """ full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') known_as = LocalizedField() 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') 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() description = LocalizedField() reliability = serializers.SerializerMethodField() relatives = serializers.SerializerMethodField() father = serializers.SerializerMethodField() sons = serializers.SerializerMethodField() share_link = serializers.CharField(read_only=True) 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','reliability', 'madhhab',"in_sahih_muslim","in_sahih_bukhari", "description",'generation','share_link', 'tadlis', 'ikhtilat', 'companion_type', 'relatives', 'father', 'sons' ] def get_father(self, obj): """Serialize the father foreign key or relative""" father_obj = obj.father request = self.context.get('request') if not father_obj: parent_rel = obj.relatives.filter(relation_type__in=['parents', 'stepfather']).first() if parent_rel: if parent_rel.narrator: father_obj = parent_rel.narrator elif parent_rel.name: return { 'id': None, 'name': parent_rel.name, 'arabic_name': parent_rel.name, 'slug': None, } if father_obj: return { 'id': father_obj.id, 'name': get_localized_text(father_obj.full_name, request) or (father_obj.get_name('en') if hasattr(father_obj, 'get_name') else None) or '—', 'arabic_name': get_arabic_localized_text(father_obj.full_name) or (father_obj.get_name('ar') if hasattr(father_obj, 'get_name') else None), 'slug': father_obj.slug, } return None def get_sons(self, obj): """Serialize all sons (from children_as_father FK and TransmitterRelative)""" request = self.context.get('request') result = [] seen_ids = set() for child in obj.children_as_father.all(): seen_ids.add(child.id) result.append({ 'id': child.id, 'name': get_localized_text(child.full_name, request) or (child.get_name('en') if hasattr(child, 'get_name') else None) or '—', 'arabic_name': get_arabic_localized_text(child.full_name) or (child.get_name('ar') if hasattr(child, 'get_name') else None), 'slug': child.slug, }) for rel in obj.relatives.filter(relation_type='children'): if rel.narrator and rel.narrator.id not in seen_ids: seen_ids.add(rel.narrator.id) result.append({ 'id': rel.narrator.id, 'name': get_localized_text(rel.narrator.full_name, request) or (rel.narrator.get_name('en') if hasattr(rel.narrator, 'get_name') else None) or '—', 'arabic_name': get_arabic_localized_text(rel.narrator.full_name) or (rel.narrator.get_name('ar') if hasattr(rel.narrator, 'get_name') else None), 'slug': rel.narrator.slug, }) elif not rel.narrator and rel.name: result.append({ 'id': None, 'name': rel.name, 'arabic_name': rel.name, 'slug': None, }) return result def get_reliability(self, obj): """Serialize the reliability foreign key""" if obj.reliability: return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } return None def get_relatives(self, obj): request = self.context.get('request') relatives_qs = obj.relatives.all() grouped = {} for rel in relatives_qs: rel_type = rel.relation_type 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.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, "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 TransmittersFiltersSerializer(serializers.ModelSerializer): pass class TransmitterSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all transmitter data for offline mode""" biographical = serializers.SerializerMethodField() scholars_opinions = serializers.SerializerMethodField() original_texts = serializers.SerializerMethodField() full_name = LocalizedField() arabic_full_name = ArabicLocalizedField(source='full_name') hadis = serializers.SerializerMethodField() relations = serializers.SerializerMethodField() class Meta: model = Transmitters fields = [ 'id', 'full_name', 'arabic_full_name', 'slug', 'biographical', 'scholars_opinions', 'original_texts','hadis','relations' ] def get_biographical(self, obj): """Get biographical information (flattened)""" request = self.context.get('request') # ← FIX: Define request if obj.reliability: r= { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } else : r= None return { 'full_name': get_localized_text(obj.full_name, request), 'arabic_full_name': get_arabic_localized_text(obj.full_name), 'kunya': get_localized_text(obj.kunya, request), 'arabic_kunya': get_arabic_localized_text(obj.kunya), '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), '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), 'birth_year_hijri': obj.birth_year_hijri, 'death_year_hijri': obj.death_year_hijri, 'age_at_death': obj.age_at_death, 'generation': obj.generation, 'reliability': r, 'madhhab': obj.madhhab, 'in_sahih_muslim': obj.in_sahih_muslim, 'in_sahih_bukhari': obj.in_sahih_bukhari, 'description': get_localized_text(obj.description, request), 'thumbnail': obj.thumbnail.url if obj.thumbnail else None, 'share_link': obj.share_link, 'tadlis': obj.tadlis, 'ikhtilat': obj.ikhtilat, 'companion_type': obj.companion_type, 'relatives': self._get_relatives(obj, request), } def _get_relatives(self, obj, request): relatives_qs = obj.relatives.all() grouped = {} for rel in relatives_qs: rel_type = rel.relation_type 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.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, "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 def get_scholars_opinions(self, obj): """Get all scholarly opinions about this transmitter""" request = self.context.get('request') # ← FIX: Define request return [ { 'id': opinion.id, 'scholar_name': get_localized_text(opinion.scholar_name, request), 'arabic_scholar_name': get_arabic_localized_text(opinion.scholar_name), 'scholar': { 'id': opinion.scholar.id, 'name': get_localized_text(opinion.scholar.name, request), 'slug': opinion.scholar.slug } if opinion.scholar else None, 'opinion_text': get_localized_text(opinion.opinion_text, request), 'arabic_opinion_text': get_arabic_localized_text(opinion.opinion_text), 'status': { 'id': opinion.status.id, 'title': get_localized_text(opinion.status.title, request), 'slug': opinion.status.slug, 'color': opinion.status.color, 'main_color_code': opinion.status.main_color_code, } if opinion.status else None, 'created_at': opinion.created_at.isoformat() if opinion.created_at else None, 'updated_at': opinion.updated_at.isoformat() if opinion.updated_at else None, } for opinion in obj.opinions.all() # Already prefetched ] def get_original_texts(self, obj): """گرفتن متون اصلی راوی با جزئیات کامل و رفرنس‌ها""" # استفاده از سریالایزر Detail که در مرحله قبل ساختیم return TransmitterOriginalTextDetailSerializer( obj.originaltexts.all(), many=True, context=self.context ).data def get_hadis(self, obj): categories_map = self.context.get('categories_map', {}) return list(categories_map.get(int(obj.id), set())) def get_relations(self, obj): """ استخراج آنی آیدی اساتید و شاگردان از کانتکست بدون اجرای کوئری اضافه """ teachers_map = self.context.get('teachers_map', {}) students_map = self.context.get('students_map', {}) # حتماً آیدی را به عدد صحیح تبدیل می‌کنیم تا از مچ شدن با دیکشنری مطمئن شویم obj_id = int(obj.id) return { "teachers": list(teachers_map.get(obj_id, set())), "students": list(students_map.get(obj_id, set())) } class HadisTransmitterSerializer(serializers.ModelSerializer): """Serializer for HadisTransmitter with transmitter details""" transmitter = TransmitterShortSerializer(read_only=True) uncertain_transmitters = TransmitterShortSerializer(many=True, read_only=True) layer = serializers.SerializerMethodField() status = serializers.SerializerMethodField() class Meta: model = HadisTransmitter fields = [ 'id', 'order', 'is_gap', 'is_uncertain', 'layer', 'transmitter', 'uncertain_transmitters', 'status' ] def get_layer(self, obj): """Get narrator layer slug""" return obj.narrator_layer.slug if obj.narrator_layer else None def get_status(self, obj): """Serialize the status foreign key""" if obj.status: request = self.context.get('request') return { 'id': obj.status.id, 'title': get_localized_text(obj.status.title, request), 'slug': obj.status.slug, 'color': obj.status.color, 'main_color_code': obj.status.main_color_code, } return None # serializers.py class HadisTransmitterListSerializer(serializers.ModelSerializer): """ The 'Parent' Serializer. It takes a HADIS object and returns the count + the list of transmitters. """ layer_count = serializers.SerializerMethodField() layer_names = serializers.SerializerMethodField() results = HadisTransmitterSerializer( source='transmitters', # Access the 'transmitters' reverse relation many=True, read_only=True ) class Meta: model = Hadis fields = ['id', 'layer_count','layer_names', 'results'] def get_layer_count(self, obj): return obj.narrator_layers.count() def get_layer_names(self, obj): """Get list of localized narrator layer names""" request = self.context.get('request') layer_objects = obj.narrator_layers.all().order_by('number') layer_names = [] for layer in layer_objects: name = get_localized_text(layer.name, request=request) description = get_localized_text(layer.description, request=request) slug = layer.slug layer_names.append({ 'id': layer.id, 'name': name, 'slug': slug, 'description': description }) return layer_names class ReferenceImageSerializer(serializers.ModelSerializer): """Serializer for ReferenceImage""" thumbnail = serializers.SerializerMethodField() class Meta: model = ReferenceImage fields = ['id', 'thumbnail', 'priority'] def get_thumbnail(self, obj): """Get thumbnail URL""" if obj.thumbnail: request = self.context.get('request') if request: return absolute_https_url(obj.thumbnail.url, request) return absolute_https_url(obj.thumbnail.url) return None class HadisReferenceSerializer(serializers.ModelSerializer): """Serializer for HadisReference with book and images""" book_title = serializers.SerializerMethodField() book_authors = serializers.SerializerMethodField() book_description = serializers.SerializerMethodField() share_link = serializers.CharField(source='book_reference.share_link', read_only=True) class Meta: model = HadisReference fields = [ 'id', 'book_title', 'book_description','book_authors','share_link' ] # def get_type_name(self, obj): # """Get localized type name from book_reference.type""" # if obj.book_reference and obj.book_reference.type: # field = LocalizedField() # field._context = self.context # return field.to_representation(obj.book_reference.type.title) # return None # def get_subject_area(self, obj): # """Get list of localized subject area names""" # if obj.book_reference and obj.book_reference.subject_area.exists(): # subject_areas = [] # field = LocalizedField() # field._context = self.context # for subject in obj.book_reference.subject_area.all(): # subject_areas.append(field.to_representation(subject.title)) # return subject_areas # return [] # def get_book_english_name(self,obj): # english_name_data = obj.book_reference.title # for item in english_name_data: # if item.get('language_code') == 'en': # return item.get('text') # return None # def get_book_language(self,obj): # language_data = obj.book_reference.language # field = LocalizedField() # field._context = self.context # return field.to_representation(language_data) def get_book_title(self, obj): title_data = obj.book_reference.title field = LocalizedField() field._context = self.context return field.to_representation(title_data) def get_book_authors(self, obj): """Get list of localized author names""" try: # 1. Fetch the BookAuthor objects authors = [obj.book_reference.author] if obj.book_reference and obj.book_reference.author else [] if not authors: return [] # 2. Prepare your localized field helper field = LocalizedField() field._context = self.context # 3. Loop through authors and convert each object to a string (name) # Result will be like: ["Al-Bukhari", "Muslim"] out = [] for author in authors: name = field.to_representation(author.name) # Format birth and death dates (Hijri/CE) birth_hijri = author.birth_year_hijri birth_miladi = author.birth_year_miladi death_hijri = 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" elif birth_hijri: birth_str = f"{birth_hijri}AH" 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" elif death_hijri: death_str = f"{death_hijri}AH" elif death_miladi: death_str = f"{death_miladi}CE" out.append({ 'name': name, 'birth': birth_str, 'death': death_str, }) return out except Exception: return [] def get_book_description(self, obj): description_data = obj.book_reference.description field = LocalizedField() field._context = self.context return field.to_representation(description_data) class HadisCorrectionSerializer(serializers.ModelSerializer): """Serializer for HadisCorrection""" title = LocalizedField() description = serializers.SerializerMethodField() translation = LocalizedField() bookmark = serializers.SerializerMethodField() address = serializers.SerializerMethodField() images = serializers.SerializerMethodField() links = serializers.JSONField(read_only=True) hadis_info = serializers.SerializerMethodField() class Meta: model = HadisCorrection fields = ['id', 'title', 'slug', 'narrator', 'description', 'translation', 'share_link', 'bookmark', 'address', 'images', 'links', 'hadis_info'] def get_hadis_info(self, obj): if obj.hadis: request = self.context.get('request') return { "id": obj.hadis.id, "number": obj.hadis.number, "slug": obj.hadis.slug, "title": get_localized_text(obj.hadis.title, request) if hasattr(obj.hadis, 'title') and obj.hadis.title else f"Hadith {obj.hadis.number}", } return None def get_description(self, obj): request = self.context.get('request') return get_localized_correction_text(obj.text, request=request) def get_bookmark(self, obj): """Get bookmark information for this correction.""" request = self.context.get('request') user = request.user if request else None book_mark = BookmarkStatusSerializer.get_bookmark_info( obj=obj, user=user, service='hadith_correction' ) return book_mark.get('is_bookmarked', False) def get_address(self, obj): refs = list(obj.references.all()) return format_address_output(refs[0] if refs else None) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in ref.images.all(): if img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) images_list.append({ "id": img.id, "image": url, "thumbnail": url, }) return images_list class HadisBasicSerializer(serializers.ModelSerializer): """Basic serializer for Hadis with minimal information""" translation = LocalizedField() category = serializers.SerializerMethodField() bookmark= serializers.SerializerMethodField() title = LocalizedField() title_narrator = LocalizedField() explanation = LocalizedField() # explanations = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'slug', 'title','bookmark', 'title_narrator', 'text', 'translation', 'share_link','explanation','category' ] # def get_explanations(self, obj): # """Get structured explanations for the requested language""" # request = self.context.get('request') # request_lang = request.query_params.get('lang', 'en') if request else 'en' # explanations_list = [] # if obj.explanations and isinstance(obj.explanations, list): # for item in obj.explanations: # if isinstance(item, dict) and item.get('language_code') == request_lang: # explanations_list.append({ # 'title': item.get('title', ''), # 'description': item.get('description', '') # }) # return explanations_list if explanations_list else None def get_category(self, obj): """Get category id and title""" if obj.category: request = self.context.get('request') return { 'id': obj.category.id, 'title': get_localized_text(obj.title,request), 'slug':obj.category.slug, 'source_type':obj.category.source_type, 'sect_type':obj.category.sect.sect_type } return None def get_bookmark(self, obj): """Get bookmark information for this playlist.""" request = self.context.get('request') user = request.user if request else None book_mark = BookmarkStatusSerializer.get_bookmark_info( obj=obj, user=user, service='hadith' ) return book_mark.get('is_bookmarked', False) class HadisShortSerializer(serializers.ModelSerializer): """Basic serializer for Hadis with minimal information""" translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() bookmark = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'slug', 'title', 'title_narrator', 'text', 'translation', 'share_link', 'bookmark'] def get_bookmark(self, obj): """Get bookmark status for this Hadis.""" request = self.context.get('request') user = request.user if request else None book_mark = BookmarkStatusSerializer.get_bookmark_info( obj=obj, user=user, service='hadith' ) return book_mark.get('is_bookmarked', False) class HadisDetailSerializer(serializers.ModelSerializer): """Detailed serializer for Hadis with core information (transmitters and corrections moved to separate endpoints)""" hadis_status = HadisStatusSerializer(read_only=True) tags = HadisTagSerializer(many=True, read_only=True) references = HadisReferenceSerializer( many=True, read_only=True ) reference_images = SerializerMethodField() address = LocalizedField() category_slug = serializers.CharField(source='category.slug', read_only=True) # address_details = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'number', 'slug', 'category_id', 'category_slug', 'hadis_status','links','share_link', 'tags','references','reference_images','address' ] # def get_address_details(self, obj): # """Get address details sorted by priority""" # if obj.address_details and isinstance(obj.address_details, list): # return sorted(obj.address_details, key=lambda x: x.get('priority', 0)) # return [] def get_reference_images(self, obj): """Get all reference images from all references""" all_images = [] for reference in obj.references.all(): images = reference.images.all().order_by('priority') serializer = ReferenceImageSerializer(images, many=True, context=self.context) all_images.extend(serializer.data) return all_images # def get_category(self, obj): # """Get category details""" # if obj.category: # return { # 'id': obj.category.id, # 'title': obj.category.title, # 'category_type': obj.category.content_type # } # return None def get_translation(self, obj): """Get translation based on request language""" request = self.context.get('request') language_code = getattr(request, 'LANGUAGE_CODE', 'en') return obj.translation.get(language_code) class NarratorLayerSerializer(serializers.Serializer): """Serializer for narrator layers information""" name = serializers.SerializerMethodField() slug = serializers.CharField(read_only=True) description = serializers.SerializerMethodField() def get_name(self, obj): """Get localized name""" request = self.context.get('request') return get_localized_text(obj.name, request=request) def get_description(self, obj): """Get localized description""" request = self.context.get('request') return get_localized_text(obj.description, request=request) class NarratorRelationSerializer(serializers.ModelSerializer): """ سریالایزر سبک برای نمایش راویان به عنوان استاد یا شاگرد """ full_name = LocalizedField() known_as = LocalizedField() reliability = SerializerMethodField() class Meta: model = Transmitters fields = ['id', 'slug', 'full_name', 'known_as', 'birth_year_hijri', 'death_year_hijri','reliability'] def get_reliability(self, obj): """Serialize the reliability foreign key""" if obj.reliability: return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } # --- سریالایزرهای کمکی تصاویر رفرنس --- class CorrectionReferenceImageSerializer(serializers.ModelSerializer): class Meta: model = CorrectionReferenceImage fields = ['id', 'image', 'priority'] class InterpretationReferenceImageSerializer(serializers.ModelSerializer): class Meta: model = InterpretationReferenceImage fields = ['id', 'image', 'priority'] class OriginalTextReferenceImageSerializer(serializers.ModelSerializer): class Meta: model = OriginalTextReferenceImage fields = ['id', 'image', 'priority'] # --- سریالایزرهای جامع رفرنس‌ها (شامل اطلاعات کتاب، ادیشن و ولوم) --- class BaseDetailedReferenceSerializer(serializers.ModelSerializer): book_title = serializers.SerializerMethodField() book_authors = serializers.SerializerMethodField() edition_info = serializers.SerializerMethodField() volume_info = serializers.SerializerMethodField() def get_book_title(self, obj): if obj.book_reference: return get_localized_text(obj.book_reference.title, self.context.get('request')) return None def get_book_authors(self, obj): if obj.book_reference: request = self.context.get('request') authors = [obj.book_reference.author] if obj.book_reference.author else [] return [ {"id": author.id, "name": get_localized_text(author.name, request)} for author in authors ] return [] def get_edition_info(self, obj): if obj.edition: return { "id": obj.edition.id, "edition_number": obj.edition.edition_number, "publisher": obj.edition.publisher } return None def get_volume_info(self, obj): if obj.book_volume: return { "id": obj.book_volume.id, "title": obj.book_volume.title } return None class DetailedCorrectionReferenceSerializer(BaseDetailedReferenceSerializer): images = CorrectionReferenceImageSerializer(many=True, read_only=True) class Meta: model = CorrectionReference fields = ['id', 'book_title', 'book_authors', 'edition_info', 'volume_info', 'hadith_number', 'volume', 'pages', 'address', 'url', 'images'] class DetailedInterpretationReferenceSerializer(BaseDetailedReferenceSerializer): images = InterpretationReferenceImageSerializer(many=True, read_only=True) class Meta: model = InterpretationReference fields = ['id', 'book_title', 'book_authors', 'edition_info', 'volume_info', 'hadith_number', 'volume', 'pages', 'address', 'url', 'images'] class DetailedOriginalTextReferenceSerializer(BaseDetailedReferenceSerializer): images = OriginalTextReferenceImageSerializer(many=True, read_only=True) class Meta: model = OriginalTextReference fields = ['id', 'book_title', 'book_authors', 'edition_info', 'volume_info', 'volume', 'pages', 'address', 'url', 'images'] # --- سریالایزرهای اصلی جزئیات (Detail Serializers) --- class HadisCorrectionDetailSerializer(serializers.ModelSerializer): title = LocalizedField() description = serializers.SerializerMethodField() translation = LocalizedField() references = DetailedCorrectionReferenceSerializer(many=True, read_only=True) hadis_info = serializers.SerializerMethodField() address = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = HadisCorrection fields = ['id', 'title', 'slug', 'narrator', 'description', 'translation', 'share_link', 'hadis_info', 'address', 'images', 'references', 'links'] def get_description(self, obj): return get_localized_correction_text(obj.text, request=self.context.get('request')) def get_hadis_info(self, obj): return {"id": obj.hadis.id, "number": obj.hadis.number, "slug": obj.hadis.slug} def get_address(self, obj): refs = list(obj.references.all()) return format_address_output(refs[0] if refs else None) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in ref.images.all(): if img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) images_list.append({ "id": img.id, "image": url, "thumbnail": url, }) return images_list class HadisInterpretationDetailSerializer(serializers.ModelSerializer): title = LocalizedField() translation = LocalizedField() references = DetailedInterpretationReferenceSerializer(many=True, read_only=True) category_info = serializers.SerializerMethodField() address = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = HadisInterpretation fields = ['id', 'title', 'slug', 'narrator', 'text', 'translation', 'category_info', 'address', 'images', 'links', 'references'] def get_category_info(self, obj): return { "id": obj.category.id, "slug": obj.category.slug, "title": get_localized_text(obj.category.title, self.context.get('request')) } def get_address(self, obj): refs = list(obj.references.all()) return format_address_output(refs[0] if refs else None) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in ref.images.all(): if img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) images_list.append({ "id": img.id, "image": url, "thumbnail": url, }) return images_list class TransmitterOriginalTextDetailSerializer(serializers.ModelSerializer): title = LocalizedField() text = LocalizedField() translation = LocalizedField() references = DetailedOriginalTextReferenceSerializer(many=True, read_only=True) transmitter_info = serializers.SerializerMethodField() address = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = TransmitterOriginalText fields = ['id', 'slug', 'title', 'text', 'translation', 'share_link', 'transmitter_info', 'address', 'images', 'references'] def get_transmitter_info(self, obj): return { "id": obj.transmitter.id, "slug": obj.transmitter.slug, "name": get_localized_text(obj.transmitter.full_name, self.context.get('request')) } def get_address(self, obj): refs = list(obj.references.all()) return format_address_output(refs[0] if refs else None) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in ref.images.all(): if img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) images_list.append({ "id": img.id, "image": url, "thumbnail": url, }) return images_list class HadisSourceDetailsSerializer(serializers.ModelSerializer): """ Lightweight serializer for Hadis Source Details (Address, Links, Images). Used on-demand when opening source detail modals in web and mobile clients. """ title = LocalizedField() narrator = LocalizedField(source='title_narrator') source_type = serializers.SerializerMethodField() address = serializers.SerializerMethodField() links = serializers.SerializerMethodField() images = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'slug', 'title', 'narrator', 'source_type', 'address', 'links', 'images', 'share_link' ] def get_source_type(self, obj): if obj.category and obj.category.source_type: return obj.category.source_type return 'hadith' def get_address(self, obj): # 1. Primary: obj.address formatted = format_address_output(obj.address) if formatted: return formatted # 2. Fallback: references address refs = list(obj.references.all()) first_ref = refs[0] if refs else None if first_ref and hasattr(first_ref, 'address') and first_ref.address: return format_address_output(first_ref.address) return [] def get_links(self, obj): return format_links_output(obj.links) def get_images(self, obj): request = self.context.get('request') images_list = [] for ref in obj.references.all(): for img in sorted(ref.images.all(), key=lambda x: getattr(x, 'priority', 0)): url = None if img.thumbnail: url = absolute_https_url(img.thumbnail.url, request) if request else absolute_https_url(img.thumbnail.url) elif hasattr(img, 'image') and img.image: url = absolute_https_url(img.image.url, request) if request else absolute_https_url(img.image.url) if url: images_list.append({ "id": img.id, "image": url, "thumbnail": url, "priority": img.priority, }) return images_list