You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
13 KiB
361 lines
13 KiB
from django.db.models import Count, Q, Avg, OuterRef, Subquery, F
|
|
from rest_framework.permissions import IsAuthenticated , AllowAny
|
|
from rest_framework.authentication import TokenAuthentication
|
|
from rest_framework.response import Response
|
|
from rest_framework.generics import ListAPIView, RetrieveAPIView, CreateAPIView
|
|
from rest_framework.filters import SearchFilter
|
|
from rest_framework import status
|
|
from drf_yasg.utils import swagger_auto_schema
|
|
from drf_yasg import openapi
|
|
|
|
from apps.library.pagination import NoPagination
|
|
from utils.pagination import StandardResultsSetPagination
|
|
from apps.library.models import *
|
|
from apps.library.serializers import *
|
|
from apps.account.models import User
|
|
from apps.library.doc import (
|
|
book_list_swagger,
|
|
book_detail_swagger,
|
|
category_list_swagger,
|
|
pinned_collection_list_swagger,
|
|
middle_collection_list_swagger
|
|
)
|
|
|
|
from utils.pagination import StandardResultsSetPagination
|
|
|
|
|
|
|
|
|
|
class CategoryListView(ListAPIView):
|
|
"""
|
|
API view to list all book categories
|
|
"""
|
|
serializer_class = CategorySerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
pagination_class = StandardResultsSetPagination
|
|
|
|
@category_list_swagger
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return Category.objects.filter(
|
|
status=True
|
|
).annotate(
|
|
books_count_annotation=Count('related_categories')
|
|
).order_by('title')
|
|
|
|
|
|
class PinnedBookCollectionListView(ListAPIView):
|
|
"""
|
|
API view to list pinned book collections with their top 3 book covers
|
|
"""
|
|
serializer_class = PinnedBookCollectionSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
pagination_class = NoPagination
|
|
|
|
@pinned_collection_list_swagger
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return BookCollection.objects.filter(
|
|
status=True,
|
|
display_position=BookCollection.DisplayPosition.PINNED
|
|
).order_by('-order', '-id')
|
|
|
|
|
|
def list(self, request, *args, **kwargs):
|
|
response = super().list(request, *args, **kwargs)
|
|
categories_count = Category.objects.filter(status=True).count()
|
|
from apps.bookmark.models import Bookmark
|
|
bookmarks_count = Bookmark.objects.filter(
|
|
service=Bookmark.ServiceChoices.LIBRARY,
|
|
).count()
|
|
downloads_count = BookDownload.objects.all().count()
|
|
info = {
|
|
"categories_count": categories_count,
|
|
"bookmarks_count": bookmarks_count,
|
|
"downloads_count": downloads_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 BookListView(ListAPIView):
|
|
"""
|
|
API view to list books with filtering and search capabilities
|
|
"""
|
|
serializer_class = BookSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
filter_backends = [SearchFilter]
|
|
search_fields = ['title', 'summary', 'publisher', 'isbn']
|
|
pagination_class = StandardResultsSetPagination
|
|
@book_list_swagger
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
queryset = Book.objects.filter(status=True)
|
|
|
|
# Filter by collection if provided
|
|
collection_id = self.request.query_params.get('collection_id')
|
|
if collection_id:
|
|
queryset = queryset.filter(collections__id=collection_id)
|
|
|
|
# Filter by category if provided
|
|
category = self.request.query_params.get('category')
|
|
if category:
|
|
# Support both single slug and comma-separated list of slugs
|
|
category_slugs = [slug.strip() for slug in category.split(',')]
|
|
queryset = queryset.filter(categories__slug__in=category_slugs).distinct()
|
|
|
|
# Filter by middle collection if requested
|
|
# if self.request.query_params.get('middle'):
|
|
# middle_collections = BookCollection.objects.filter(
|
|
# status=True,
|
|
# display_position=BookCollection.DisplayPosition.MIDDLE
|
|
# )
|
|
# if middle_collections.exists():
|
|
# queryset = queryset.filter(collections__in=middle_collections)
|
|
|
|
# Filter by bottom collection if requested
|
|
# if self.request.query_params.get('bottom'):
|
|
# bottom_collections = BookCollection.objects.filter(
|
|
# status=True,
|
|
# display_position=BookCollection.DisplayPosition.BOTTOM
|
|
# )
|
|
# if bottom_collections.exists():
|
|
# queryset = queryset.filter(collections__in=bottom_collections)
|
|
|
|
# Filter by bookmarked books if requested
|
|
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
|
|
|
|
# DEBUG: Hardcode user to root@admin.com
|
|
# user = User.objects.get(email='root@admin.com')
|
|
# # Get all bookmarked book IDs for the current user
|
|
bookmarked_ids = Bookmark.objects.filter(
|
|
user=self.request.user,
|
|
service=Bookmark.ServiceChoices.LIBRARY,
|
|
status=True
|
|
).values_list('content_id', flat=True)
|
|
# bookmarked_ids = Bookmark.objects.filter(
|
|
# user=user,
|
|
# service=Bookmark.ServiceChoices.LIBRARY,
|
|
# status=True
|
|
# ).values_list('content_id', flat=True)
|
|
|
|
# Import Rate here to avoid circular imports if any
|
|
from apps.bookmark.models.rate import Rate
|
|
|
|
# Subquery to calculate average rating for each book
|
|
avg_rating = Rate.objects.filter(
|
|
service=Rate.ServiceChoices.LIBRARY,
|
|
content_id=OuterRef('pk'),
|
|
status=True
|
|
).order_by().values('content_id').annotate(
|
|
avg_rate=Avg('rate')
|
|
).values('avg_rate')
|
|
|
|
queryset = queryset.annotate(average_rate=Subquery(avg_rating))
|
|
|
|
# Sort mapping
|
|
sort_mapping = {
|
|
'most_popular': [F('download_count').desc(nulls_last=True), '-created_at'],
|
|
'newest': ['-created_at'],
|
|
'most_view': [F('view_count').desc(nulls_last=True), '-created_at'],
|
|
'most_rated': [F('average_rate').desc(nulls_last=True), '-created_at'],
|
|
}
|
|
|
|
# Sort by parameter
|
|
sort = self.request.query_params.get('sort', '-pin,-created_at')
|
|
|
|
if sort in sort_mapping:
|
|
queryset = queryset.order_by(*sort_mapping[sort])
|
|
else:
|
|
# Allowed sort fields
|
|
allowed_sorts = [
|
|
'created_at', '-created_at', 'view_count', '-view_count',
|
|
'download_count', '-download_count', 'title', '-title',
|
|
'pin', '-pin', '-pin,-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('-pin', '-created_at')
|
|
|
|
return queryset
|
|
|
|
|
|
class BookDetailView(RetrieveAPIView):
|
|
"""
|
|
API view to retrieve detailed information about a specific book
|
|
"""
|
|
serializer_class = BookSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
queryset = Book.objects.filter(status=True)
|
|
lookup_field = 'slug'
|
|
|
|
@book_detail_swagger
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
instance = self.get_object()
|
|
# Increment view count when book details are viewed
|
|
instance.increment_view_count()
|
|
serializer = self.get_serializer(instance)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class MiddleBookCollectionListView(ListAPIView):
|
|
"""
|
|
API view to list middle section book collections with their books
|
|
"""
|
|
serializer_class = MiddleBookCollectionSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
pagination_class = NoPagination
|
|
|
|
@middle_collection_list_swagger
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return BookCollection.objects.filter(
|
|
status=True,
|
|
display_position=BookCollection.DisplayPosition.MIDDLE
|
|
).order_by('order')
|
|
|
|
|
|
class DownloadedBooksListView(ListAPIView):
|
|
"""
|
|
API view to list books that have been downloaded by the current user
|
|
"""
|
|
serializer_class = BookSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
filter_backends = [SearchFilter]
|
|
search_fields = ['title', 'summary', 'publisher', 'isbn']
|
|
pagination_class = StandardResultsSetPagination
|
|
|
|
@swagger_auto_schema(
|
|
operation_id="list_downloaded_books",
|
|
operation_description="""
|
|
Retrieve a list of books that have been downloaded by the current user.
|
|
|
|
This endpoint returns a paginated list of books that the authenticated user has downloaded.
|
|
The results are not cached to ensure real-time accuracy of the download list.
|
|
|
|
You can search for downloaded books by title, summary, publisher, or ISBN using the 'search' query parameter.
|
|
""",
|
|
operation_summary="List Downloaded Books",
|
|
tags=["Dobodbi - Library"],
|
|
manual_parameters=[
|
|
openapi.Parameter(
|
|
'search',
|
|
openapi.IN_QUERY,
|
|
description="Search downloaded books by title, summary, publisher, or ISBN",
|
|
type=openapi.TYPE_STRING,
|
|
required=False
|
|
)
|
|
],
|
|
responses={
|
|
200: "List of downloaded books with pagination",
|
|
401: "Authentication credentials were not provided or are invalid",
|
|
500: "Internal server error occurred"
|
|
}
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
# DEBUG: Hardcode user to root@admin.com
|
|
# user =self.request.user
|
|
# # Get all downloaded book IDs for the current user
|
|
downloaded_ids = BookDownload.objects.filter(
|
|
user=self.request.user,
|
|
status=True
|
|
).values_list('book_id', flat=True)
|
|
# downloaded_ids = BookDownload.objects.filter(
|
|
# user=user,
|
|
# status=True
|
|
# ).values_list('book_id', flat=True)
|
|
|
|
# Return books that match these IDs
|
|
return Book.objects.filter(
|
|
id__in=downloaded_ids,
|
|
status=True
|
|
).order_by('-created_at')
|
|
|
|
|
|
class BookDownloadCreateAPIView(CreateAPIView):
|
|
"""
|
|
API view to create a book download record and increment the book's download count
|
|
"""
|
|
serializer_class = BookDownloadSerializer
|
|
permission_classes = (IsAuthenticated,)
|
|
authentication_classes = [TokenAuthentication]
|
|
|
|
@swagger_auto_schema(
|
|
operation_id="download_book",
|
|
operation_description="""
|
|
Create a book download record and increment the book's download count.
|
|
|
|
This endpoint creates a record of a book download by the current user and increments
|
|
the book's download count. It requires the book ID in the request body.
|
|
|
|
If the user has already downloaded the book, the existing record will be updated
|
|
with the current timestamp.
|
|
""",
|
|
operation_summary="Download Book",
|
|
tags=["Dobodbi - Library"],
|
|
request_body=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'book_id': openapi.Schema(
|
|
type=openapi.TYPE_INTEGER,
|
|
description="ID of the book to download"
|
|
)
|
|
},
|
|
required=['book_id']
|
|
),
|
|
responses={
|
|
201: openapi.Response(
|
|
description="Book download record created successfully",
|
|
schema=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
|
|
'created_at': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATETIME),
|
|
'updated_at': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATETIME),
|
|
'status': openapi.Schema(type=openapi.TYPE_BOOLEAN)
|
|
}
|
|
)
|
|
),
|
|
400: "Invalid request data or book not found",
|
|
401: "Authentication credentials were not provided or are invalid",
|
|
500: "Internal server error occurred"
|
|
}
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
return super().post(request, *args, **kwargs)
|
|
|