From 3b50aa2b1b8f79c5b0a6d5fc50c8cfa47e829ffe Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 8 Jun 2026 13:00:49 +0330 Subject: [PATCH] library views and serializers for admin panel added --- apps/library/serializers.py | 8 +- apps/library/serializers_admin.py | 152 ++++++++++++++++++++++++++++++ apps/library/urls.py | 12 ++- apps/library/views_admin.py | 80 ++++++++++++++++ 4 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 apps/library/serializers_admin.py create mode 100644 apps/library/views_admin.py diff --git a/apps/library/serializers.py b/apps/library/serializers.py index 2c385f5..3e99b9e 100644 --- a/apps/library/serializers.py +++ b/apps/library/serializers.py @@ -46,13 +46,19 @@ class PinnedBookCollectionSerializer(serializers.ModelSerializer): model = BookCollection fields = ('id', 'title', 'summary', 'covers') - +from dj_language.models import Language class BookSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField() user_rate = serializers.SerializerMethodField() average_rate = serializers.SerializerMethodField() share_link = serializers.CharField(read_only=True) + language = serializers.SlugRelatedField( + slug_field='code', + queryset=Language.objects.all(), + allow_null=True, + required=False + ) def get_thumbnail(self, obj): if obj.thumbnail: diff --git a/apps/library/serializers_admin.py b/apps/library/serializers_admin.py new file mode 100644 index 0000000..9e84ff0 --- /dev/null +++ b/apps/library/serializers_admin.py @@ -0,0 +1,152 @@ +from rest_framework import serializers + +from .models import Book, BookCollection, Category + + +class AbsoluteImageField(serializers.ImageField): + def to_representation(self, value): + if not value: + return None + request = self.context.get("request") + url = value.url if hasattr(value, "url") else str(value) + if request: + return request.build_absolute_uri(url) + return url + + +class AbsoluteFileField(serializers.FileField): + def to_representation(self, value): + if not value: + return None + request = self.context.get("request") + url = value.url if hasattr(value, "url") else str(value) + if request: + return request.build_absolute_uri(url) + return url + + +class AdminLibraryCategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + fields = ["id", "title", "slug", "status", "created_at", "updated_at"] + + +class AdminLibraryCollectionSerializer(serializers.ModelSerializer): + class Meta: + model = BookCollection + fields = ["id", "title", "slug", "summary", "display_position", "status", "order", "pin_top"] + + +class AdminBookListSerializer(serializers.ModelSerializer): + thumbnail = AbsoluteImageField(required=False, allow_null=True) + book_file = AbsoluteFileField(required=False, allow_null=True) + categories_detail = AdminLibraryCategorySerializer(source="categories", many=True, read_only=True) + + class Meta: + model = Book + fields = [ + "id", + "title", + "slug", + "slogan", + "thumbnail", + "author", + "publisher", + "file_type", + "status", + "pin", + "view_count", + "download_count", + "created_at", + "updated_at", + "categories_detail", + "book_file", + ] + read_only_fields = ["id", "view_count", "download_count", "created_at", "updated_at"] + +from dj_language.models import Language +class AdminBookDetailSerializer(serializers.ModelSerializer): + thumbnail = AbsoluteImageField(required=False, allow_null=True) + book_file = AbsoluteFileField(required=False, allow_null=True) + categories = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True, required=False) + collections = serializers.PrimaryKeyRelatedField(queryset=BookCollection.objects.all(), many=True, required=False) + categories_detail = AdminLibraryCategorySerializer(source="categories", many=True, read_only=True) + collections_detail = AdminLibraryCollectionSerializer(source="collections", many=True, read_only=True) + remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) + remove_book_file = serializers.BooleanField(write_only=True, required=False, default=False) + language = serializers.SlugRelatedField( + slug_field='code', + queryset=Language.objects.all(), + allow_null=True, + required=False + ) + + class Meta: + model = Book + fields = [ + "id", + "title", + "slug", + "slogan", + "summary_title", + "summary", + "description", + "thumbnail", + "publisher", + "year_of_publication", + "author", + "isbn", + "numnber_of_volume", + "language", + "main_themes", + "notable_works", + "pages_count", + "status", + "pin", + "view_count", + "download_count", + "file_type", + "book_file", + "created_at", + "updated_at", + "categories", + "collections", + "categories_detail", + "collections_detail", + "remove_thumbnail", + "remove_book_file", + ] + read_only_fields = ["id", "view_count", "download_count", "created_at", "updated_at"] + + def create(self, validated_data): + validated_data.pop("remove_thumbnail", False) + validated_data.pop("remove_book_file", False) + categories = validated_data.pop("categories", []) + collections = validated_data.pop("collections", []) + book = super().create(validated_data) + if categories: + book.categories.set(categories) + if collections: + book.collections.set(collections) + return book + + def update(self, instance, validated_data): + remove_thumbnail = validated_data.pop("remove_thumbnail", False) + remove_book_file = validated_data.pop("remove_book_file", False) + categories = validated_data.pop("categories", None) + collections = validated_data.pop("collections", None) + + if remove_thumbnail and instance.thumbnail: + instance.thumbnail.delete(save=False) + instance.thumbnail = None + + if remove_book_file and instance.book_file: + instance.book_file.delete(save=False) + instance.book_file = None + + book = super().update(instance, validated_data) + if categories is not None: + book.categories.set(categories) + if collections is not None: + book.collections.set(collections) + return book diff --git a/apps/library/urls.py b/apps/library/urls.py index 8206da7..b7f3570 100644 --- a/apps/library/urls.py +++ b/apps/library/urls.py @@ -1,4 +1,5 @@ -from django.urls import path +from django.urls import include, path +from rest_framework.routers import SimpleRouter from apps.library.views import ( CategoryListView, @@ -9,8 +10,15 @@ from apps.library.views import ( DownloadedBooksListView, BookDownloadCreateAPIView, ) +from apps.library.views_admin import AdminBookViewSet, AdminLibraryCategoryViewSet, AdminLibraryCollectionViewSet + +admin_router = SimpleRouter() +admin_router.register(r'books', AdminBookViewSet, basename='admin-library-books') +admin_router.register(r'categories', AdminLibraryCategoryViewSet, basename='admin-library-categories') +admin_router.register(r'collections', AdminLibraryCollectionViewSet, basename='admin-library-collections') urlpatterns = [ + path('admin/', include(admin_router.urls)), path('categories/', CategoryListView.as_view(), name='category-list'), path('pinned-collections/', PinnedBookCollectionListView.as_view(), name='pinned-collection-list'), path('collections/', MiddleBookCollectionListView.as_view(), name='collection-list'), @@ -18,4 +26,4 @@ urlpatterns = [ path('books//', BookDetailView.as_view(), name='book-detail'), path('books/downloaded/', DownloadedBooksListView.as_view(), name='downloaded-books-list'), path('books/download/', BookDownloadCreateAPIView.as_view(), name='book-download'), -] \ No newline at end of file +] diff --git a/apps/library/views_admin.py b/apps/library/views_admin.py new file mode 100644 index 0000000..603c255 --- /dev/null +++ b/apps/library/views_admin.py @@ -0,0 +1,80 @@ +from django.db.models import Q +from rest_framework.authentication import TokenAuthentication +from rest_framework.parsers import FormParser, JSONParser, MultiPartParser +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet + +from apps.account.permissions import IsSuperAdmin +from utils.pagination import StandardResultsSetPagination + +from .models import Book, BookCollection, Category +from .serializers_admin import ( + AdminBookDetailSerializer, + AdminBookListSerializer, + AdminLibraryCategorySerializer, + AdminLibraryCollectionSerializer, +) + + +class AdminLibraryCategoryViewSet(ReadOnlyModelViewSet): + serializer_class = AdminLibraryCategorySerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + return Category.objects.all().order_by("title") + + +class AdminLibraryCollectionViewSet(ReadOnlyModelViewSet): + serializer_class = AdminLibraryCollectionSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + return BookCollection.objects.all().order_by("display_position", "order", "title") + + +class AdminBookViewSet(ModelViewSet): + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = StandardResultsSetPagination + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_serializer_class(self): + if self.action == "list": + return AdminBookListSerializer + return AdminBookDetailSerializer + + def get_queryset(self): + queryset = Book.objects.all().prefetch_related("categories", "collections") + + 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(author__icontains=search_query) + | Q(publisher__icontains=search_query) + | Q(isbn__icontains=search_query) + | Q(summary__icontains=search_query) + ) + + status_filter = self.request.query_params.get("status") + if status_filter in {"true", "false"}: + queryset = queryset.filter(status=status_filter == "true") + + file_type = self.request.query_params.get("file_type") + if file_type and file_type != "all": + queryset = queryset.filter(file_type=file_type) + + category_id = self.request.query_params.get("category") + if category_id and category_id != "all": + queryset = queryset.filter(categories__id=category_id) + + collection_id = self.request.query_params.get("collection") + if collection_id and collection_id != "all": + queryset = queryset.filter(collections__id=collection_id) + + return queryset.distinct().order_by("-created_at")