From 0f609b7d7a404ab08c0d4246a661ca8641e03103 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 6 Jan 2026 10:59:42 +0330 Subject: [PATCH] added authentication method and bookmark settings to hadis and reference details. --- apps/hadis/serializers/hadis.py | 29 ++++++- apps/hadis/serializers/reference.py | 6 +- apps/hadis/urls.py | 6 +- apps/hadis/views/hadis.py | 45 ++++++++++- apps/hadis/views/reference.py | 4 + apps/video/serializers.py | 3 +- apps/video/urls.py | 4 +- apps/video/views.py | 114 ++++++++++++++++++++++++++-- 8 files changed, 192 insertions(+), 19 deletions(-) diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 082da88..b302c70 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -5,6 +5,7 @@ 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, @@ -156,6 +157,7 @@ class HadisListSerializer(serializers.ModelSerializer): """Serializer for Hadis list""" category = serializers.SerializerMethodField() status = serializers.SerializerMethodField() + bookmark = serializers.SerializerMethodField() translation = LocalizedField() title = LocalizedField() title_narrator = LocalizedField() @@ -163,7 +165,7 @@ class HadisListSerializer(serializers.ModelSerializer): class Meta: model = Hadis fields = ['id', 'number', 'slug', 'title', 'title_narrator', 'text', - 'translation', 'category', 'status', 'share_link'] + 'translation', 'category', 'status', 'bookmark', 'share_link'] def get_category(self, obj): """Get category id and title""" @@ -203,6 +205,17 @@ class HadisListSerializer(serializers.ModelSerializer): 'color': obj.hadis_status.color } + 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() @@ -573,6 +586,7 @@ class HadisBasicSerializer(serializers.ModelSerializer): """Basic serializer for Hadis with minimal information""" translation = LocalizedField() category = serializers.SerializerMethodField() + bookmark= serializers.SerializerMethodField() title = LocalizedField() title_narrator = LocalizedField() @@ -581,7 +595,7 @@ class HadisBasicSerializer(serializers.ModelSerializer): class Meta: model = Hadis fields = [ - 'id', 'slug', 'title', 'title_narrator', 'text', + 'id', 'slug', 'title','bookmark', 'title_narrator', 'text', 'translation', 'share_link','explanation','category' ] @@ -599,6 +613,17 @@ class HadisBasicSerializer(serializers.ModelSerializer): '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""" diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index 75e12b1..2708891 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -77,6 +77,7 @@ class BookAttributeSerializer(serializers.ModelSerializer): model = BookAttribute fields = ['id', 'title', 'value','book_reference'] +from apps.hadis.serializers import HadisListSerializer class BookDetailSerializer(serializers.ModelSerializer): attribute = BookAttributeSerializer( @@ -100,7 +101,6 @@ class BookDetailSerializer(serializers.ModelSerializer): # read_only=True, # source='hadis_references__hadis' # ) - hadis = serializers.SerializerMethodField() title = LocalizedField() language = LocalizedField() @@ -114,7 +114,7 @@ class BookDetailSerializer(serializers.ModelSerializer): def get_hadis(self,obj): references = obj.hadis_references.all() hadis_list = [ref.hadis for ref in references if ref.hadis] - return HadisBasicSerializer(hadis_list,many=True).data + return HadisListSerializer(hadis_list,many=True, context=self.context).data @@ -166,4 +166,4 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer): def get_hadises(self,obj): references = obj.hadis_references.all() hadis_list = [ref.hadis for ref in references if ref.hadis] - return HadisShortSerializer(hadis_list,many=True).data \ No newline at end of file + return HadisShortSerializer(hadis_list,many=True, context=self.context).data \ No newline at end of file diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 2bb3849..283b062 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -32,7 +32,7 @@ urlpatterns = [ path('categories/', cached_view(CategoriesView.as_view()), name='categories'), # ← Least specific LAST # Hadis paths - path('category//', cached_view(HadisListView.as_view()), name='hadis-list'), + path('category//', HadisListView.as_view(), name='hadis-list'), path('arguments/', cached_view(HadisMainListView.as_view()), name='hadis-main-list'), path('arguments/filters/', cached_view(HadisFiltersView.as_view()), name='hadis-filters'), @@ -44,11 +44,11 @@ urlpatterns = [ path('narrators/', cached_view(TransmitterView.as_view()), name='narrators'), # Reference paths - path('references/', cached_view(BookDetailView.as_view()), name='reference-detail'), + path('references/', BookDetailView.as_view(), name='reference-detail'), path('references/', cached_view(BookReferencesView.as_view()), name='references'), # Hadis detail paths (with slug, more specific) - path('/detail/', cached_view(HadisDetailView.as_view()), name='hadis-detail'), + path('/detail/', HadisDetailView.as_view(), name='hadis-detail'), path('/transmitters/', cached_view(HadisTransmittersView.as_view()), name='hadis-transmitters'), path('/corrections/', cached_view(HadisCorrectionsView.as_view()), name='hadis-corrections'), path('/', cached_view(HadisBasicView.as_view()), name='hadis-basic'), # ← Least specific LAST diff --git a/apps/hadis/views/hadis.py b/apps/hadis/views/hadis.py index bfa7558..830ef93 100644 --- a/apps/hadis/views/hadis.py +++ b/apps/hadis/views/hadis.py @@ -1,3 +1,4 @@ +from rest_framework.authentication import TokenAuthentication from rest_framework.generics import ListAPIView, RetrieveAPIView from django.shortcuts import get_object_or_404 from utils.pagination import NoPagination, StandardResultsSetPagination @@ -88,6 +89,7 @@ class HadisListView(ListAPIView): """ serializer_class = HadisListSerializer pagination_class = StandardResultsSetPagination + authentication_classes = [TokenAuthentication] @hadis_list_swagger def get(self, request, *args, **kwargs): @@ -124,17 +126,36 @@ class HadisListView(ListAPIView): category_slug = self.kwargs.get('category_slug') if not HadisCategory.objects.filter(slug=category_slug).exists(): return Hadis.objects.none() - + return Hadis.objects.filter( category__slug=category_slug, status=True ).order_by('number').annotate( - # distinct=True is CRITICAL here. + # distinct=True is CRITICAL here. # Without it, if 3 narrators are from "Layer 1", it counts as 3. # With it, it counts as 1 (unique layer). layer_count=Count('transmitters__narrator_layer', distinct=True) ).select_related('category') + def get_serializer_context(self): + """Add user bookmarks to serializer context to avoid caching issues""" + context = super().get_serializer_context() + + # Add user's bookmarked hadis IDs to context + user = self.request.user + if user.is_authenticated: + from apps.bookmark.models.bookmark import Bookmark + user_bookmarks = Bookmark.objects.filter( + user=user, + service=Bookmark.ServiceChoices.HADITH, + status=True + ).values_list('content_id', flat=True) + context['user_bookmarked_hadis_ids'] = set(user_bookmarks) + else: + context['user_bookmarked_hadis_ids'] = set() + + return context + @@ -289,6 +310,7 @@ class HadisBasicView(RetrieveAPIView): serializer_class = HadisBasicSerializer lookup_field = 'slug' lookup_url_kwarg = 'hadis_slug' + authentication_classes = [TokenAuthentication] @hadis_basic_swagger def get(self, request, *args, **kwargs): @@ -297,6 +319,25 @@ class HadisBasicView(RetrieveAPIView): def get_queryset(self): return Hadis.objects.filter(status=True) + def get_serializer_context(self): + """Add user bookmarks to serializer context to avoid caching issues""" + context = super().get_serializer_context() + + # Add user's bookmarked hadis IDs to context + user = self.request.user + if user.is_authenticated: + from apps.bookmark.models.bookmark import Bookmark + user_bookmarks = Bookmark.objects.filter( + user=user, + service=Bookmark.ServiceChoices.HADITH, + status=True + ).values_list('content_id', flat=True) + context['user_bookmarked_hadis_ids'] = set(user_bookmarks) + else: + context['user_bookmarked_hadis_ids'] = set() + + return context + class HadisDetailView(RetrieveAPIView): """ diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py index e202a1d..a0d5912 100644 --- a/apps/hadis/views/reference.py +++ b/apps/hadis/views/reference.py @@ -1,3 +1,5 @@ +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 @@ -59,6 +61,8 @@ 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): diff --git a/apps/video/serializers.py b/apps/video/serializers.py index 97b097d..da34808 100644 --- a/apps/video/serializers.py +++ b/apps/video/serializers.py @@ -94,7 +94,8 @@ class VideoPlaylistDetailSerializer(serializers.ModelSerializer): def get_bookmark(self, obj): """Get bookmark information for this playlist.""" request = self.context.get('request') - user = request.user if request else None + user = request.user if request else None + print(user.id) book_mark = BookmarkStatusSerializer.get_bookmark_info( obj=obj, user=user, diff --git a/apps/video/urls.py b/apps/video/urls.py index f00325b..f827d55 100644 --- a/apps/video/urls.py +++ b/apps/video/urls.py @@ -12,6 +12,6 @@ urlpatterns = [ re_path(r'playlists/(?P[\w-]+)/$', VideoPlaylistDetailAPIView.as_view(), name='playlist-detail'), # Keep old video endpoints for backward compatibility if needed - path('list/', VideoPlaylistListAPIView.as_view(), name='video-list'), - re_path(r'detail/(?P[\w-]+)/$', VideoPlaylistDetailAPIView.as_view(), name='video-detail'), + path('list/', VideoListAPIView.as_view(), name='video-list'), + re_path(r'detail/(?P[\w-]+)/$', VideoDetailAPIView.as_view(), name='video-detail'), ] \ No newline at end of file diff --git a/apps/video/views.py b/apps/video/views.py index 3788463..8358323 100644 --- a/apps/video/views.py +++ b/apps/video/views.py @@ -216,6 +216,108 @@ class VideoPlaylistListAPIView(generics.ListAPIView): return queryset +class VideoListAPIView(generics.ListAPIView): + """ + API view to list all video playlists, with optional filtering by category or collection + """ + serializer_class = VideoListSerializer + permission_classes = (IsAuthenticated,) + authentication_classes = [TokenAuthentication] + pagination_class = StandardResultsSetPagination + + @swagger_auto_schema( + operation_description="Get a list of videos 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 = Video.objects.filter(status=True).order_by('-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', '').lower() + if is_bookmark == 'true': + # Import Bookmark model here to avoid circular imports + from apps.bookmark.models import Bookmark + + # Get all bookmarked playlist IDs for the current user + bookmarked_ids = Bookmark.objects.filter( + user=self.request.user, + service=Bookmark.ServiceChoices.VIDEO_PLAYLIST, + status=True + ).values_list('content_id', flat=True) + + # Filter playlists by these IDs + queryset = queryset.filter(id__in=bookmarked_ids) + sort = self.request.query_params.get('sort', '-created_at') + allowed_sorts = [ + 'created_at', '-created_at', 'view_count', '-view_count', + 'title', '-title', + '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 VideoPlaylistDetailAPIView(generics.RetrieveAPIView): serializer_class = VideoPlaylistDetailSerializer @@ -266,11 +368,11 @@ class VideoDetailAPIView(generics.RetrieveAPIView): return super().get(request, *args, **kwargs) def get_queryset(self): - return Video.objects.filter(status=True) + return Video.objects.filter(slug = self.kwargs.get('slug')) - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - instance.increment_view_count() - serializer = self.get_serializer(instance) - return Response(serializer.data) + # def retrieve(self, request, *args, **kwargs): + # instance = self.get_object() + # instance.increment_view_count() + # serializer = self.get_serializer(instance) + # return Response(serializer.data)