from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.generics import ListAPIView, RetrieveAPIView,ListCreateAPIView from rest_framework.response import Response from django.db.models import Q from ..models import BookReference , BookAuthor , BookReferenceImage, BookAttribute from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer , BookReferenceSerializer, BookReferenceSyncSerializer, BookAttributeSerializer from ..serializers.category import get_localized_text from ..docs import book_attributes_create_swagger, book_attributes_list_swagger, book_references_list_swagger, book_authors_list_swagger, book_detail_swagger, reference_sync_swagger from utils.pagination import NoPagination from utils.pagination import StandardResultsSetPagination class BookReferencesView(ListAPIView): queryset = BookReference.objects.all() serializer_class = BookReferenceSerializer pagination_class = StandardResultsSetPagination @book_references_list_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): queryset = BookReference.objects.all() # Get search parameter search_query = self.request.query_params.get('search', None) # Apply search filter if search_query: queryset = self.apply_search_filter(queryset, search_query) return queryset def apply_search_filter(self, queryset, search_query): """ Apply search filter across book titles (JSONField). Searches in: title """ # Search conditions search_conditions = Q() # For JSONFields, search in the JSON string representation # This will find matches in the "text" values within the JSON arrays search_conditions |= Q(title__icontains=search_query) return queryset.filter(search_conditions) class BookAuthorView(ListAPIView): queryset = BookAuthor.objects.all() serializer_class = BookAuthorSerializer pagination_class = StandardResultsSetPagination @book_authors_list_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) class BookDetailView(RetrieveAPIView): serializer_class = BookDetailSerializer lookup_field = 'slug' lookup_url_kwarg = 'reference_slug' permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] @book_detail_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return ( BookReference.objects .filter(slug = self.kwargs.get('reference_slug')) .prefetch_related( 'attributes', 'authors', 'images', 'hadis_references__hadis' ) ) from django.db.models import Prefetch class BookReferenceSyncView(ListAPIView): """ API view to sync all book reference data for offline mode Returns all book references with basic info, detailed information, and related hadises """ serializer_class = BookReferenceSyncSerializer pagination_class = NoPagination @reference_sync_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def _get_multi_param(self, *param_names): values = [] for name in param_names: for item in self.request.query_params.getlist(name): for val in str(item).split(','): val = val.strip() if val: values.append(val) return values def get_queryset(self): """ Prefetch ALL related data to avoid N+1 queries """ qs = BookReference.objects.select_related('author', 'type').prefetch_related( 'attributes', 'images', 'hadis_references__hadis' ).distinct().order_by('id') # Category (Multi-select) categories = self._get_multi_param('category', 'categories', 'category[]', 'categories[]') category_ids = [int(c) for c in categories if c.isdigit()] if category_ids: qs = qs.filter(subject_area__id__in=category_ids).distinct() # Author (Multi-select) authors = self._get_multi_param('author', 'authors', 'author[]', 'authors[]') author_ids = [int(a) for a in authors if a.isdigit()] if author_ids: qs = qs.filter(author_id__in=author_ids) # Sect / Madhab (Multi-select) sects = self._get_multi_param('sect', 'sects', 'sect[]', 'sects[]', 'madhab', 'madhabs', 'madhab[]', 'madhabs[]') if sects: from django.db.models import Q sect_q = Q() type_ids = [] for s in sects: if s.lower() in ['shia', 'sunni']: sect_q |= Q(type__sect=s.lower()) elif s.isdigit(): type_ids.append(int(s)) if type_ids: sect_q |= Q(type__id__in=type_ids) if sect_q: qs = qs.filter(sect_q) # PDF availability pdf_available = self.request.query_params.get('pdf_available') if pdf_available == 'true': qs = qs.filter(documents__isnull=False).distinct() elif pdf_available == 'false': qs = qs.filter(documents__isnull=True) # Century (Multi-select) centuries = self._get_multi_param('century', 'centuries', 'century[]', 'centuries[]') if centuries: from django.db.models import Q century_q = Q() for c in centuries: if c == 'before_islam': century_q |= Q(author__death_year_hijri__lt=0) elif c.isdigit(): century_num = int(c) start_year = (century_num - 1) * 100 + 1 end_year = century_num * 100 century_q |= Q(author__death_year_hijri__gte=start_year, author__death_year_hijri__lte=end_year) if century_q: qs = qs.filter(century_q) return qs def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) # If pagination is active, paginate it (though it's NoPagination) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) else: serializer = self.get_serializer(queryset, many=True) from ..models import BookSubjectArea, BookType from ..serializers.category import get_localized_text categories_qs = BookSubjectArea.objects.all() categories = [{"id": c.id, "title": get_localized_text(c.title, request)} for c in categories_qs] sects_qs = BookType.objects.all() sects = [{"id": s.id, "title": get_localized_text(s.title, request)} for s in sects_qs] filters_metadata = { "categories": categories, "madhab": sects } if page is not None: response = self.get_paginated_response(serializer.data) response.data['filters'] = filters_metadata return response return Response({ "count": queryset.count(), "filters": filters_metadata, "results": serializer.data }) class BookAttributeView(ListCreateAPIView): """ API view to list all book attributes and create new book attributes """ queryset = BookAttribute.objects.all() serializer_class = BookAttributeSerializer @book_attributes_list_swagger def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) @book_attributes_create_swagger def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)