diff --git a/apps/article/serializers_dovodi.py b/apps/article/serializers_dovodi.py new file mode 100644 index 0000000..d34765f --- /dev/null +++ b/apps/article/serializers_dovodi.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from .models import ArticleCategory, ArticleCollection +from .serializers_admin import AbsoluteImageField + + +class DovodiArticleCategorySerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + + class Meta: + model = ArticleCategory + fields = ["id", "title", "slug", "status", "order"] + + +class DovodiArticleCollectionSerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + thumbnail = AbsoluteImageField(required=False, allow_null=True) + remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) + + class Meta: + model = ArticleCollection + fields = [ + "id", + "title", + "slug", + "summary", + "display_position", + "status", + "order", + "pin_top", + "thumbnail", + "remove_thumbnail", + ] + + def create(self, validated_data): + validated_data.pop("remove_thumbnail", False) + return super().create(validated_data) + + def update(self, instance, validated_data): + remove_thumbnail = validated_data.pop("remove_thumbnail", False) + if remove_thumbnail and instance.thumbnail: + instance.thumbnail.delete(save=False) + instance.thumbnail = None + return super().update(instance, validated_data) diff --git a/apps/article/urls.py b/apps/article/urls.py index f47f315..59e9be5 100755 --- a/apps/article/urls.py +++ b/apps/article/urls.py @@ -10,7 +10,12 @@ admin_router.register(r'articles', AdminArticleViewSet, basename='admin-articles admin_router.register(r'categories', AdminArticleCategoryViewSet, basename='admin-article-categories') admin_router.register(r'contents', AdminArticleContentViewSet, basename='admin-article-contents') +dovodi_router = SimpleRouter() +dovodi_router.register(r'categories', DovodiArticleCategoryViewSet, basename='dovodi-article-category') +dovodi_router.register(r'collections', DovodiArticleCollectionViewSet, basename='dovodi-article-collection') + urlpatterns = [ + path('dovodi/', include(dovodi_router.urls)), path('categories/', ArticleCategoryListAPIView.as_view(), name='category-list'), path('pinned-collections/', PinnedArticleCollectionListView.as_view(), name='pinned-collection-list'), path('collections/', MiddleArticleCollectionListView.as_view(), name='collection-list'), diff --git a/apps/article/views.py b/apps/article/views.py index 835107c..36846d0 100755 --- a/apps/article/views.py +++ b/apps/article/views.py @@ -1,6 +1,9 @@ +from django.db.models import Q from rest_framework import generics, status from rest_framework.response import Response from rest_framework.authentication import TokenAuthentication +from rest_framework.parsers import FormParser, JSONParser, MultiPartParser +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 @@ -10,6 +13,10 @@ from utils.pagination import StandardResultsSetPagination from apps.article.models import * from apps.article.serializers import * +from apps.article.serializers_dovodi import ( + DovodiArticleCategorySerializer, + DovodiArticleCollectionSerializer, +) class ArticleCategoryListAPIView(generics.ListAPIView): @@ -243,3 +250,32 @@ class ArticleDetailAPIView(generics.RetrieveAPIView): instance.increment_view_count() serializer = self.get_serializer(instance) return Response(serializer.data) + + +class DovodiArticleCategoryViewSet(ModelViewSet): + serializer_class = DovodiArticleCategorySerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + queryset = ArticleCategory.objects.all().order_by("order", "title") + search_query = self.request.query_params.get("search") + if search_query: + queryset = queryset.filter(Q(title__icontains=search_query) | Q(slug__icontains=search_query)) + return queryset + + +class DovodiArticleCollectionViewSet(ModelViewSet): + serializer_class = DovodiArticleCollectionSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_queryset(self): + queryset = ArticleCollection.objects.all().order_by("display_position", "order", "title") + search_query = self.request.query_params.get("search") + if search_query: + queryset = queryset.filter(Q(title__icontains=search_query) | Q(slug__icontains=search_query)) + return queryset diff --git a/apps/library/serializers_admin.py b/apps/library/serializers_admin.py index 4df0630..bd94fa6 100644 --- a/apps/library/serializers_admin.py +++ b/apps/library/serializers_admin.py @@ -40,12 +40,22 @@ class AbsoluteFileField(serializers.FileField): class AdminLibraryCategorySerializer(serializers.ModelSerializer): + books_count = serializers.SerializerMethodField() + class Meta: model = Category - fields = ["id", "title", "slug", "status", "created_at", "updated_at"] + fields = ["id", "title", "slug", "status", "books_count", "created_at", "updated_at"] + read_only_fields = ["id", "books_count", "created_at", "updated_at"] + + def get_books_count(self, obj): + if hasattr(obj, "books_count_annotation"): + return obj.books_count_annotation + return obj.books_count class AdminLibraryCollectionSerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + class Meta: model = BookCollection fields = ["id", "title", "slug", "summary", "display_position", "status", "order", "pin_top"] @@ -54,10 +64,11 @@ class AdminLibraryCollectionSerializer(serializers.ModelSerializer): class AdminAuthorSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) books_count = serializers.SerializerMethodField() + remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) class Meta: model = Author - fields = ["id", "full_name", "slug", "thumbnail", "birth_date", "death_date", "books_count", "created_at", "updated_at"] + fields = ["id", "full_name", "slug", "thumbnail", "birth_date", "death_date", "books_count", "created_at", "updated_at", "remove_thumbnail"] read_only_fields = ["id", "created_at", "updated_at", "books_count"] def get_books_count(self, obj): @@ -65,6 +76,17 @@ class AdminAuthorSerializer(serializers.ModelSerializer): return obj.books_count_annotation return obj.books_count + def create(self, validated_data): + validated_data.pop("remove_thumbnail", False) + return super().create(validated_data) + + def update(self, instance, validated_data): + remove_thumbnail = validated_data.pop("remove_thumbnail", False) + if remove_thumbnail and instance.thumbnail: + instance.thumbnail.delete(save=False) + instance.thumbnail = None + return super().update(instance, validated_data) + class AdminBookListSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) diff --git a/apps/library/urls.py b/apps/library/urls.py index e07eb45..383caa1 100644 --- a/apps/library/urls.py +++ b/apps/library/urls.py @@ -13,13 +13,22 @@ from apps.library.views import ( DownloadedBooksListView, BookDownloadCreateAPIView, ) -from apps.library.views_admin import AdminAuthorViewSet, AdminBookViewSet, AdminLibraryCategoryViewSet, AdminLibraryCollectionViewSet +from apps.library.views_admin import ( + AdminAuthorViewSet, + AdminBookViewSet, + AdminLibraryCategoryViewSet, + AdminLibraryCollectionViewSet, + DovodiAdminLibraryCategoryViewSet, + DovodiAdminLibraryCollectionViewSet, +) admin_router = SimpleRouter() admin_router.register(r'books', AdminBookViewSet, basename='admin-library-books') admin_router.register(r'authors', AdminAuthorViewSet, basename='admin-library-authors') admin_router.register(r'categories', AdminLibraryCategoryViewSet, basename='admin-library-categories') +admin_router.register(r'dovodi-categories', DovodiAdminLibraryCategoryViewSet, basename='admin-library-dovodi-categories') admin_router.register(r'collections', AdminLibraryCollectionViewSet, basename='admin-library-collections') +admin_router.register(r'dovodi-collections', DovodiAdminLibraryCollectionViewSet, basename='admin-library-dovodi-collections') urlpatterns = [ path('admin/', include(admin_router.urls)), diff --git a/apps/library/views_admin.py b/apps/library/views_admin.py index ca1fbee..54c8d8b 100644 --- a/apps/library/views_admin.py +++ b/apps/library/views_admin.py @@ -27,6 +27,29 @@ class AdminLibraryCategoryViewSet(ReadOnlyModelViewSet): return Category.objects.all().order_by("title") +class DovodiAdminLibraryCategoryViewSet(ModelViewSet): + serializer_class = AdminLibraryCategorySerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = StandardResultsSetPagination + + def get_queryset(self): + queryset = Category.objects.annotate(books_count_annotation=Count("related_categories")).all() + + 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.order_by("title", "id") + + class AdminLibraryCollectionViewSet(ReadOnlyModelViewSet): serializer_class = AdminLibraryCollectionSerializer permission_classes = [IsAuthenticated, IsSuperAdmin] @@ -37,6 +60,31 @@ class AdminLibraryCollectionViewSet(ReadOnlyModelViewSet): return BookCollection.objects.all().order_by("display_position", "order", "title") +class DovodiAdminLibraryCollectionViewSet(ModelViewSet): + serializer_class = AdminLibraryCollectionSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = StandardResultsSetPagination + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_queryset(self): + queryset = BookCollection.objects.all() + + 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(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") + + return queryset.order_by("display_position", "order", "title", "id") + + class AdminAuthorViewSet(ModelViewSet): serializer_class = AdminAuthorSerializer permission_classes = [IsAuthenticated, IsSuperAdmin]