from django.db.models import Q, Prefetch from rest_framework import generics, status from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from apps.library.pagination import NoPagination from utils.mixins import CanonicalSlugViewSetMixin, Gone410ViewMixin from utils.pagination import StandardResultsSetPagination, OptionalPagination from apps.video.models import * from apps.video.serializers import * from apps.video.serializers_admin import ( AdminVideoCategorySerializer, AdminVideoCollectionSerializer, AdminVideoListSerializer, ) from apps.video.serializers_dovodi import DovodiVideoCategorySerializer, DovodiVideoCollectionSerializer, DovodiVideoItemSerializer class VideoCategoryListAPIView(generics.ListAPIView): """ API view to list all video categories """ serializer_class = VideoCategoryListSerializer pagination_class = StandardResultsSetPagination @swagger_auto_schema( operation_description="Get a list of all active video categories", tags=["Dobodbi - Video"], responses={ 200: openapi.Response( description="List of video categories", schema=VideoCategoryListSerializer(many=True) ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): """ Optimized queryset with prefetch_related for playlists with items """ return VideoCategory.objects.filter(status=True).prefetch_related( Prefetch( 'playlists', queryset=VideoPlaylist.objects.filter(status=True, playlist_items__video__status=True).distinct() ) ).order_by('order') class PinnedVideoCollectionListView(generics.ListAPIView): serializer_class = PinnedVideoCollectionSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] pagination_class = NoPagination @swagger_auto_schema( operation_description="Get a list of pinned video collections", tags=["Dobodbi - Video"], manual_parameters=[ openapi.Parameter( name='is_bookmark', in_=openapi.IN_QUERY, description='Filter collections that contain bookmarked playlists (true/false)', type=openapi.TYPE_BOOLEAN, required=False ), ], responses={ 200: openapi.Response( description="List of pinned video collections", schema=PinnedVideoCollectionSerializer(many=True) ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return PinnedVideoCollection.objects.filter( status=True, display_position=VideoCollection.DisplayPosition.PINNED, related_playlists__status=True, related_playlists__playlist_items__video__status=True ).distinct().order_by('order', '-created_at') def list(self, request, *args, **kwargs): response = super().list(request, *args, **kwargs) categories_count = VideoCategory.objects.filter(status=True).count() info = { "categories_count": categories_count, } data = { "count": response.data.get("count"), "next": response.data.get("next"), "previous": response.data.get("previous"), "info": info, "results": response.data.get("results") } return Response(data, status=status.HTTP_200_OK) class MiddleVideoCollectionListView(generics.ListAPIView): serializer_class = MiddleVideoCollectionSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] pagination_class = NoPagination @swagger_auto_schema( operation_description="Get a list of middle video collections", tags=["Dobodbi - Video"], manual_parameters=[ openapi.Parameter( name='is_bookmark', in_=openapi.IN_QUERY, description='Filter collections that contain bookmarked playlists (true/false)', type=openapi.TYPE_BOOLEAN, required=False ), ], responses={ 200: openapi.Response( description="List of middle video collections", schema=MiddleVideoCollectionSerializer(many=True) ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return VideoCollection.objects.filter( status=True, display_position=VideoCollection.DisplayPosition.MIDDLE, related_playlists__status=True, related_playlists__playlist_items__video__status=True ).distinct().order_by('order', '-created_at') def list(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True, context={'request': request}) return Response(serializer.data) class VideoPlaylistListAPIView(generics.ListAPIView): """ API view to list all video playlists, with optional filtering by category or collection """ serializer_class = VideoPlaylistListSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination @swagger_auto_schema( operation_description="Get a list of video playlists with optional filtering", tags=["Dobodbi - Video"], manual_parameters=[ openapi.Parameter( name='category', in_=openapi.IN_QUERY, description='Filter playlists by category slug', type=openapi.TYPE_STRING, required=False ), openapi.Parameter( name='collection', in_=openapi.IN_QUERY, description='Filter playlists by collection slug', type=openapi.TYPE_STRING, required=False ), openapi.Parameter( name='is_bookmark', in_=openapi.IN_QUERY, description='Filter playlists that are bookmarked by the user (true/false)', type=openapi.TYPE_BOOLEAN, required=False ), openapi.Parameter( name='search', in_=openapi.IN_QUERY, description='Search playlists by title', type=openapi.TYPE_STRING, required=False ) ], responses={ 200: openapi.Response( description="List of video playlists", schema=VideoPlaylistListSerializer(many=True) ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): queryset = VideoPlaylist.objects.filter( status=True, playlist_items__video__status=True ).prefetch_related('playlist_items__video').distinct().order_by('order', '-created_at') # Search by title if search parameter is provided search_query = self.request.query_params.get('search', None) if search_query: queryset = queryset.filter(title__icontains=search_query) # Filter by category if provided category_slug = self.request.query_params.get('category', None) if category_slug: queryset = queryset.filter(categories__slug=category_slug) # Filter by collection if provided collection_slug = self.request.query_params.get('collection', None) if collection_slug: queryset = queryset.filter(collections__slug=collection_slug) is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() if is_bookmark in ('true', '1') and self.request.user.is_authenticated: from apps.bookmark.models import Bookmark from django.db.models import Q bookmarked_playlist_ids = Bookmark.objects.filter( user=self.request.user, service=Bookmark.ServiceChoices.VIDEO_PLAYLIST, status=True ).values_list('content_id', flat=True) bookmarked_video_ids = Bookmark.objects.filter( user=self.request.user, service=Bookmark.ServiceChoices.VIDEO, status=True ).values_list('content_id', flat=True) queryset = queryset.filter( Q(id__in=bookmarked_playlist_ids) | Q(playlist_items__video__id__in=bookmarked_video_ids) ).distinct() sort = self.request.query_params.get('sort', '-created_at') allowed_sorts = [ 'created_at', '-created_at', 'view_count', '-view_count', 'title', '-title','order' , 'order', 'total_time', '-total_time','-created_at','created_at' ] if sort in allowed_sorts: # Handle multiple sort fields (e.g., '-pin,-created_at') if ',' in sort: queryset = queryset.order_by(*sort.split(',')) else: queryset = queryset.order_by(sort) else: queryset = queryset.order_by('-created_at') return queryset class VideoListAPIView(generics.ListAPIView): """ API view to list all video playlists, with optional filtering by category or collection """ serializer_class = VideoPlaylistListSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] pagination_class = StandardResultsSetPagination @swagger_auto_schema( operation_description="Get a list of video playlists with optional filtering", tags=["Dobodbi - Video"], manual_parameters=[ openapi.Parameter( name='category', in_=openapi.IN_QUERY, description='Filter playlists by category slug', type=openapi.TYPE_STRING, required=False ), openapi.Parameter( name='collection', in_=openapi.IN_QUERY, description='Filter playlists by collection slug', type=openapi.TYPE_STRING, required=False ), openapi.Parameter( name='is_bookmark', in_=openapi.IN_QUERY, description='Filter playlists that are bookmarked by the user (true/false)', type=openapi.TYPE_BOOLEAN, required=False ), openapi.Parameter( name='search', in_=openapi.IN_QUERY, description='Search playlists by title', type=openapi.TYPE_STRING, required=False ), openapi.Parameter( name='sort', in_=openapi.IN_QUERY, description='Sort playlists by field. Options: created_at, -created_at, view_count, -view_count, title, -title, order, -order', type=openapi.TYPE_STRING, required=False ) ], responses={ 200: openapi.Response( description="List of video playlists", schema=VideoPlaylistListSerializer(many=True) ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): queryset = VideoPlaylist.objects.filter( status=True, playlist_items__video__status=True ).distinct().order_by('order', '-created_at') # Search by title if search parameter is provided search_query = self.request.query_params.get('search', None) if search_query: queryset = queryset.filter(title__icontains=search_query) # Filter by category if provided category_slug = self.request.query_params.get('category', None) if category_slug: queryset = queryset.filter(categories__slug=category_slug) # Filter by collection if provided collection_slug = self.request.query_params.get('collection', None) if collection_slug: queryset = queryset.filter(collections__slug=collection_slug) is_bookmark = (self.request.query_params.get('is_bookmark') or self.request.query_params.get('is_bookmarked') or '').lower() if is_bookmark in ('true', '1') and self.request.user.is_authenticated: from apps.bookmark.models import Bookmark from django.db.models import Q bookmarked_playlist_ids = Bookmark.objects.filter( user=self.request.user, service=Bookmark.ServiceChoices.VIDEO_PLAYLIST, status=True ).values_list('content_id', flat=True) bookmarked_video_ids = Bookmark.objects.filter( user=self.request.user, service=Bookmark.ServiceChoices.VIDEO, status=True ).values_list('content_id', flat=True) queryset = queryset.filter( Q(id__in=bookmarked_playlist_ids) | Q(playlist_items__video__id__in=bookmarked_video_ids) ).distinct() sort = self.request.query_params.get('sort', '-created_at') allowed_sorts = [ 'created_at', '-created_at', 'view_count', '-view_count', 'title', '-title', 'order', '-order', 'total_time', '-total_time' ] if sort in allowed_sorts: if ',' in sort: queryset = queryset.order_by(*sort.split(',')) else: queryset = queryset.order_by(sort) else: queryset = queryset.order_by('-created_at') return queryset class VideoPlaylistDetailAPIView(generics.RetrieveAPIView): serializer_class = VideoPlaylistDetailSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] lookup_field = 'slug' @swagger_auto_schema( operation_description="Get video playlist details by slug", tags=["Dobodbi - Video"], responses={ 200: openapi.Response( description="Video playlist details", schema=VideoPlaylistDetailSerializer() ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return VideoPlaylist.objects.filter(status=True, playlist_items__video__status=True).distinct() def retrieve(self, request, *args, **kwargs): instance = self.get_object() instance.increment_view_count() serializer = self.get_serializer(instance) return Response(serializer.data) class VideoDetailAPIView(Gone410ViewMixin, CanonicalSlugViewSetMixin, generics.RetrieveAPIView): serializer_class = VideoPlaylistDetailSerializer permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] lookup_field = 'slug' active_field_name = 'status' active_expected_value = True @swagger_auto_schema( operation_description="Get video playlist details by slug", tags=["Dobodbi - Video"], responses={ 200: openapi.Response( description="Video playlist details", schema=VideoPlaylistDetailSerializer() ) } ) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): return VideoPlaylist.objects.filter(status=True, playlist_items__video__status=True).distinct() def retrieve(self, request, *args, **kwargs): instance = self.get_object() instance.increment_view_count() serializer = self.get_serializer(instance) return Response(serializer.data) class DovodiVideoCategoryViewSet(ModelViewSet): serializer_class = DovodiVideoCategorySerializer permission_classes = [IsAuthenticated] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination def get_queryset(self): queryset = VideoCategory.objects.all().order_by("-created_at", "-id") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(Q(title__icontains=search_query) | Q(slug__icontains=search_query)) status_filter = self.request.query_params.get("status") if status_filter in {"true", "false"}: queryset = queryset.filter(status=status_filter == "true") return queryset class DovodiVideoCollectionViewSet(ModelViewSet): serializer_class = DovodiVideoCollectionSerializer permission_classes = [IsAuthenticated] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): queryset = VideoCollection.objects.all().order_by("-created_at", "-id") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(Q(title__icontains=search_query) | Q(slug__icontains=search_query)) status_filter = self.request.query_params.get("status") if status_filter in {"true", "false"}: queryset = queryset.filter(status=status_filter == "true") return queryset @action(detail=True, methods=["post"], url_path="assign-items") def assign_items(self, request, pk=None): collection = self.get_object() add_ids = request.data.get("add_ids", []) or request.data.get("item_ids", []) remove_ids = request.data.get("remove_ids", []) if add_ids: new_items = [ VideoPlaylistInCollection(playlist_id=pid, collection_id=collection.id) for pid in add_ids ] VideoPlaylistInCollection.objects.bulk_create(new_items, ignore_conflicts=True) if remove_ids: VideoPlaylistInCollection.objects.filter(collection_id=collection.id, playlist_id__in=remove_ids).delete() return Response({"status": "success", "attached_count": collection.collection_playlists.count()}) class DovodiVideoItemViewSet(ModelViewSet): serializer_class = DovodiVideoItemSerializer permission_classes = [IsAuthenticated] authentication_classes = [TokenAuthentication] pagination_class = OptionalPagination parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): queryset = Video.objects.all().order_by("-created_at") search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(slug__icontains=search_query) | Q(description__icontains=search_query) ) return queryset class ResolveYouTubeStreamURLAPIView(generics.GenericAPIView): """ API view to extract direct stream URL for a YouTube URL using yt-dlp. """ permission_classes = (IsAuthenticated,) authentication_classes = [TokenAuthentication] @swagger_auto_schema( operation_description="Extract direct playable stream URL from YouTube URL", tags=["Dobodbi - Video"], manual_parameters=[ openapi.Parameter( name='url', in_=openapi.IN_QUERY, description='YouTube video URL', type=openapi.TYPE_STRING, required=True ), ], responses={ 200: openapi.Response( description="Resolved direct stream URL", schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'youtube_url': openapi.Schema(type=openapi.TYPE_STRING), 'stream_url': openapi.Schema(type=openapi.TYPE_STRING), } ) ), 400: "Bad Request" } ) def get(self, request, *args, **kwargs): youtube_url = request.query_params.get('url') if not youtube_url: return Response({'error': 'URL parameter is required.'}, status=status.HTTP_400_BAD_REQUEST) from utils.youtube import get_youtube_stream_url stream_url = get_youtube_stream_url(youtube_url) return Response({ 'youtube_url': youtube_url, 'stream_url': stream_url or youtube_url }, status=status.HTTP_200_OK)