From e30f301809da5a310a41d358459d6d7ff7ddac03 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Wed, 25 Mar 2026 10:35:55 +0330 Subject: [PATCH] feat: Add Hadis category serializers featuring localized fields, hierarchical data, and related counts, and create a new Hadis view file. --- apps/hadis/serializers/category.py | 9 +- apps/hadis/views/hadis.py | 151 +++++++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 11 deletions(-) diff --git a/apps/hadis/serializers/category.py b/apps/hadis/serializers/category.py index b7c7a46..c73798e 100644 --- a/apps/hadis/serializers/category.py +++ b/apps/hadis/serializers/category.py @@ -6,9 +6,9 @@ from django.utils.translation import gettext_lazy as _ from ..models import HadisSect, HadisCategory, Hadis , HadisCategory from django.utils.translation import get_language -def get_localized_text(json_list, request=None, fallback_lang="en"): +def get_localized_text(json_list, request=None, fallback_lang="en", language_code=None): """ - Extract localized text from a JSON list based on request's language. + Extract localized text from a JSON list based on language. Expects: [{"language_code": "en", "text": "..."}, ...] Returns: Single text string or None @@ -16,8 +16,9 @@ def get_localized_text(json_list, request=None, fallback_lang="en"): if not json_list or not isinstance(json_list, list): return None - # Get target language - language_code = getattr(request, "LANGUAGE_CODE", None) if request else None + # Get target language if not provided + if not language_code: + language_code = getattr(request, "LANGUAGE_CODE", None) if request else None if not language_code: language_code = get_language() or fallback_lang diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index 525ea13..d450af0 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -72,15 +72,152 @@ class HadisSyncView(ListAPIView): return self.list(request, *args, **kwargs) def list(self, request, *args, **kwargs): + from django.utils.translation import get_language + + # 1. PRE-FETCH DATA ONCE queryset = self.get_queryset() - serializer = self.get_serializer(queryset, many=True) - - response_data = { - 'count': queryset.count(), - 'results': serializer.data - } - return Response(response_data) + # Get language once for the entire bulk operation + lang = getattr(request, "LANGUAGE_CODE", None) or get_language() or "en" + + # Pre-calculate base URL for images + media_url = request.build_absolute_uri('/')[:-1] # Remove trailing slash + + results = [] + for obj in queryset: + # --- Detail Block --- + status_block = None + if obj.hadis_status: + status_block = { + 'id': obj.hadis_status.id, + 'title': get_localized_text(obj.hadis_status.title, language_code=lang), + 'color': obj.hadis_status.color, + 'main_color_code': obj.hadis_status.main_color_code, + } + + tags_block = [ + {'id': tag.id, 'title': get_localized_text(tag.title, language_code=lang)} + for tag in obj.tags.all() + ] + + references_block = [] + reference_images_block = [] + for ref in obj.references.all(): + book = ref.book_reference + references_block.append({ + 'id': ref.id, + 'title': get_localized_text(book.title, language_code=lang) if book else None, + 'authors': [ + {'id': a.id, 'name': get_localized_text(a.name, language_code=lang)} + for a in (book.authors.all() if book else []) + ], + 'description': book.description if book else None, + }) + + for img in ref.images.all(): + reference_images_block.append({ + 'id': img.id, + 'thumbnail': f"{media_url}{img.thumbnail.url}" if img.thumbnail else None, + 'priority': img.priority, + }) + + 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)) + + detail_block = { + 'address': get_localized_text(obj.address, language_code=lang), + 'address_details': address_details_list, + 'hadis_status': status_block, + 'status_text': get_localized_text(obj.hadis_status_text, language_code=lang), + 'share_link': obj.share_link, + 'links': obj.links, + 'tags': tags_block, + 'references': references_block, + 'reference_images': reference_images_block, + } + + # --- Narrators Block --- + transmitters_data = [] + for tr_rel in obj.transmitters.all(): + t = tr_rel.transmitter + layer = tr_rel.narrator_layer + + rel_data = None + if t.reliability: + rel_data = { + 'id': t.reliability.id, + 'title': get_localized_text(t.reliability.title, language_code=lang), + '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, language_code=lang), + 'slug': t.slug, + 'known_as': get_localized_text(t.known_as, language_code=lang), + 'nickname': get_localized_text(t.nickname, language_code=lang), + 'reliability': rel_data, + 'layer_level': layer.number if layer else None, + 'layer_name': get_localized_text(layer.name, language_code=lang) if layer else None, + 'is_gap': tr_rel.is_gap, + 'birth_year_hijri': t.birth_year_hijri, + 'death_year_hijri': t.death_year_hijri, + 'order': tr_rel.order, + }) + + narrators_block = { + 'description': get_localized_text(obj.description, language_code=lang), + 'transmitters': transmitters_data, + } + + # --- Explanations (Complex Logic) --- + explanation_data = [] + if hasattr(obj, 'explanations') and obj.explanations and isinstance(obj.explanations, list): + for item in obj.explanations: + if isinstance(item, dict) and item.get('language_code') == lang: + explanation_data.append({ + 'title': item.get('title', ''), + 'description': item.get('description', '') + }) + + if not explanation_data and obj.explanation: + explanation_data = get_localized_text(obj.explanation, language_code=lang) + else: + explanation_data = explanation_data if explanation_data else None + + # --- Corrections --- + corrections_block = [] + for c in obj.hadiscorrection_set.all(): + corrections_block.append({ + 'id': c.id, + 'title': get_localized_text(c.title, language_code=lang), + 'description': get_localized_text(c.description, language_code=lang), + 'translation': get_localized_text(c.translation, language_code=lang), + 'share_link': c.share_link, + }) + + # --- Assemble Hadis Item --- + results.append({ + 'id': obj.id, + 'number': obj.number, + 'slug': obj.slug, + 'category_id': obj.category_id, + 'title': get_localized_text(obj.title, language_code=lang), + 'title_narrator': get_localized_text(obj.title_narrator, language_code=lang), + 'text': obj.text, # Usually JSON structure in our app + 'translation': get_localized_text(obj.translation, language_code=lang), + 'detail': detail_block, + 'narrators': narrators_block, + 'explanations': explanation_data, + 'corrections': corrections_block, + }) + + return Response({ + 'count': len(results), + 'results': results + }) class HadisListView(ListAPIView):