from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from rest_framework.fields import SerializerMethodField from urllib3 import request from .category import LocalizedField from .category import get_localized_text from .category import get_localized_text from apps.bookmark.serializers.bookmark import BookmarkStatusSerializer from ..models import ( Hadis, HadisStatus, HadisTag, HadisTransmitter, HadisReference, ReferenceImage, Transmitters, HadisCollection, TransmitterOpinion, TransmitterOriginalText, BookReference, BookReferenceImage, BookAuthor, HadisCorrection ) 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 request.build_absolute_uri(obj.thumbnail.url) return 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 request.build_absolute_uri(obj.thumbnail.url) return 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() title =LocalizedField() title_narrator = LocalizedField() translation = LocalizedField() class Meta: model = Hadis fields = [ # header (no-extend) 'id', 'number', 'slug', 'category_id', 'title', 'title_narrator', 'text', 'translation', # grouped sections 'detail', 'narrators', 'explanations', 'corrections', ] 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), '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.authors.all() if book 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 = request.build_absolute_uri(img.thumbnail.url) if request else img.thumbnail.url 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.reliability: reliability_block = { 'id': t.reliability.id, 'title': get_localized_text(t.reliability.title, request), 'color': t.reliability.color, 'main_color_code': t.reliability.main_color_code, } transmitters_data.append({ 'id': t.id, 'name': get_localized_text(t.full_name, request), 'slug': t.slug, 'known_as': get_localized_text(t.known_as, request), 'nickname': get_localized_text(t.nickname, request), 'reliability': reliability_block, 'layer_level': layer.number if layer else None, 'layer_name': get_localized_text(layer.name, request) if layer else None, 'is_gap': transmitter_rel.is_gap, 'birth_year_hijri': t.birth_year_hijri, 'death_year_hijri': t.death_year_hijri, '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): request = self.context.get('request') corrections_data = [] for correction in obj.hadiscorrection_set.all(): corrections_data.append({ 'id': correction.id, 'title': get_localized_text(correction.title, request), 'description': correction.text, 'translation': get_localized_text(correction.translation, request), 'share_link': correction.share_link, }) return corrections_data class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" category = serializers.SerializerMethodField() status = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField() translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() class Meta: model = Hadis fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text', 'translation', 'category', 'status', 'bookmark', 'share_link'] 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) return { 'id': obj.hadis_status.id, 'title': title, '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() known_as = LocalizedField() nickname = LocalizedField() reliability = serializers.SerializerMethodField() share_link = serializers.CharField(read_only=True) class Meta: model = Transmitters fields = [ 'id', 'full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as",'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')), 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } return None class TransmitterShortSerializer(serializers.ModelSerializer): """Serializer for Transmitters""" full_name = LocalizedField() known_as = LocalizedField() nickname = LocalizedField() class Meta: model = Transmitters fields = [ 'id', 'full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as",'nickname','reliability' ] class TransmitterOpinionSerializer(serializers.ModelSerializer): """ Serializer for TransmitterOpinions """ scholar_name = LocalizedField() opinion_text = LocalizedField() status = serializers.SerializerMethodField() class Meta: model = TransmitterOpinion fields = ['id','transmitter','scholar_name','opinion_text','status'] 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 class TransmitterOriginalTextSerializer(serializers.ModelSerializer): """ Serializer for TransmitterOriginalText """ title = LocalizedField() text = LocalizedField() translation =LocalizedField() class Meta: model = TransmitterOriginalText fields = ['id', 'title', 'text', 'translation', 'share_link'] class TransmitterDetailSerializer(serializers.ModelSerializer): """ Serializer for Details of Transmitters """ full_name = LocalizedField() known_as = LocalizedField() nickname = LocalizedField() kunya = LocalizedField() origin = LocalizedField() lived_in = LocalizedField() died_in = LocalizedField() description = LocalizedField() reliability = serializers.SerializerMethodField() share_link = serializers.CharField(read_only=True) class Meta: model = Transmitters fields = [ 'id','full_name','kunya','known_as','nickname', 'origin','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' ] 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, } return None 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() class Meta: model = Transmitters fields = [ 'id', 'full_name','slug' ,'biographical', 'scholars_opinions', 'original_texts' ] 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), 'kunya': get_localized_text(obj.kunya, request), 'known_as': get_localized_text(obj.known_as, request), 'nickname': get_localized_text(obj.nickname, request), 'origin': get_localized_text(obj.origin, request), '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, } 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), 'opinion_text': get_localized_text(opinion.opinion_text, request), '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): """Get original texts of the transmitter""" request = self.context.get('request') # ← FIX: Define request return [ { 'id': t.id, 'title': get_localized_text(t.title, request), 'text': get_localized_text(t.text, request), 'translation': get_localized_text(t.translation, request), 'share_link': t.share_link, } for t in obj.originaltexts.all() # Already prefetched ] class HadisTransmitterSerializer(serializers.ModelSerializer): """Serializer for HadisTransmitter with transmitter details""" transmitter = TransmitterShortSerializer(read_only=True) layer = serializers.SerializerMethodField() status = serializers.SerializerMethodField() class Meta: model = HadisTransmitter fields = [ 'id', 'order', 'is_gap','layer', 'transmitter', '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): # Calculate distinct layers efficiently return obj.transmitters.values('narrator_layer').distinct().count() def get_layer_names(self, obj): """Get list of localized narrator layer names""" request = self.context.get('request') # Import here to get actual objects from apps.hadis.models import NarratorLayer # Get ALL distinct narrator layer IDs for this hadis (not filtered) # This ensures layer names are returned regardless of layer filtering all_layers_for_hadis = HadisTransmitter.objects.filter( hadis=obj ).values_list('narrator_layer', flat=True).distinct() layer_objects = NarratorLayer.objects.filter(id__in=all_layers_for_hadis) # Extract localized names 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({ '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 request.build_absolute_uri(obj.thumbnail.url) return 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.authors.all() 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() class Meta: model = HadisCorrection fields = ['id', 'title','slug','description', 'translation','share_link','bookmark'] def get_description(self, obj): return obj.text 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) 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() class Meta: model = Hadis fields = [ 'id', 'slug', 'title', 'title_narrator', 'text', 'translation', 'share_link'] 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() # address_details = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'number', '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)