from rest_framework import viewsets, filters from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination from django_filters.rest_framework import DjangoFilterBackend from django.db.models import Q, Prefetch from .models import CaseStudy, CaseStudyCategory, Client, CaseStudyImage, CaseStudyProcess from .serializers import ( CaseStudyListSerializer, CaseStudyDetailSerializer, CaseStudyCategorySerializer, ClientSerializer ) class CaseStudyPagination(PageNumberPagination): """Custom pagination for case studies""" page_size = 9 page_size_query_param = 'page_size' max_page_size = 100 class CaseStudyViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for case studies Supports filtering by category, client, and search """ queryset = CaseStudy.objects.filter(published=True) pagination_class = CaseStudyPagination filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['category__slug', 'client', 'featured'] search_fields = ['title', 'description', 'excerpt'] ordering_fields = ['published_at', 'views_count', 'created_at', 'display_order'] ordering = ['display_order', '-published_at'] lookup_field = 'slug' def get_serializer_class(self): if self.action == 'retrieve': return CaseStudyDetailSerializer return CaseStudyListSerializer def get_queryset(self): queryset = super().get_queryset() # Always optimize foreign key lookups queryset = queryset.select_related('category', 'client') # Don't add prefetch_related here for retrieve action - it's handled in retrieve() method # to avoid duplicate prefetch lookups # Filter by category category_slug = self.request.query_params.get('category', None) if category_slug and category_slug != 'all': queryset = queryset.filter(category__slug=category_slug) # Filter by client client_slug = self.request.query_params.get('client', None) if client_slug: queryset = queryset.filter(client__slug=client_slug) # Search query search = self.request.query_params.get('search', None) if search: queryset = queryset.filter( Q(title__icontains=search) | Q(description__icontains=search) | Q(excerpt__icontains=search) ) return queryset.distinct() def retrieve(self, request, *args, **kwargs): """Override retrieve to increment view count and ensure optimized queryset""" # Get the base queryset (without prefetch_related to avoid duplicates) queryset = self.filter_queryset(self.get_queryset()) # Now add prefetch_related for detail view optimization queryset = queryset.prefetch_related( Prefetch( 'gallery_images', queryset=CaseStudyImage.objects.order_by('display_order', 'created_at') ), Prefetch( 'process_steps', queryset=CaseStudyProcess.objects.order_by('step_number') ) ) # Get the object using the optimized queryset lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field filter_kwargs = {self.lookup_field: kwargs[lookup_url_kwarg]} instance = queryset.get(**filter_kwargs) # Increment view count instance.increment_views() # Serialize and return serializer = self.get_serializer(instance) return Response(serializer.data) @action(detail=False, methods=['get']) def featured(self, request): """Get featured case studies""" featured_case_studies = self.get_queryset().filter(featured=True)[:6] serializer = self.get_serializer(featured_case_studies, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def latest(self, request): """Get latest case studies""" limit = int(request.query_params.get('limit', 6)) latest_case_studies = self.get_queryset()[:limit] serializer = self.get_serializer(latest_case_studies, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def popular(self, request): """Get popular case studies by views""" limit = int(request.query_params.get('limit', 6)) popular_case_studies = self.get_queryset().order_by('-views_count')[:limit] serializer = self.get_serializer(popular_case_studies, many=True) return Response(serializer.data) @action(detail=True, methods=['get']) def related(self, request, slug=None): """Get related case studies for a specific case study""" case_study = self.get_object() related_case_studies = self.get_queryset().filter( category=case_study.category ).exclude(id=case_study.id)[:4] serializer = self.get_serializer(related_case_studies, many=True) return Response(serializer.data) class CaseStudyCategoryViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for case study categories""" queryset = CaseStudyCategory.objects.filter(is_active=True) serializer_class = CaseStudyCategorySerializer lookup_field = 'slug' @action(detail=False, methods=['get']) def with_case_studies(self, request): """Get categories that have published case studies""" categories = self.get_queryset().filter(case_studies__published=True).distinct() serializer = self.get_serializer(categories, many=True) return Response(serializer.data) class ClientViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for clients""" queryset = Client.objects.filter(is_active=True) serializer_class = ClientSerializer lookup_field = 'slug' @action(detail=True, methods=['get']) def case_studies(self, request, slug=None): """Get all case studies for a specific client""" client = self.get_object() case_studies = CaseStudy.objects.filter(client=client, published=True) page = self.paginate_queryset(case_studies) if page is not None: serializer = CaseStudyListSerializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = CaseStudyListSerializer(case_studies, many=True) return Response(serializer.data)