From 4557d1f9fa2b7fa7224826b16655364ca111cfaf Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Fri, 12 Jun 2026 18:32:56 +0330 Subject: [PATCH] video and podcasts archive objects endpoints added for archive page admin --- apps/podcast/serializers_dovodi.py | 91 +++++++++++++++++++++++ apps/podcast/urls.py | 6 ++ apps/podcast/views.py | 61 ++++++++++++++- apps/video/serializers_dovodi.py | 115 +++++++++++++++++++++++++++++ apps/video/urls.py | 6 ++ apps/video/views.py | 61 ++++++++++++++- 6 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 apps/podcast/serializers_dovodi.py create mode 100644 apps/video/serializers_dovodi.py diff --git a/apps/podcast/serializers_dovodi.py b/apps/podcast/serializers_dovodi.py new file mode 100644 index 0000000..eafdf9b --- /dev/null +++ b/apps/podcast/serializers_dovodi.py @@ -0,0 +1,91 @@ +from rest_framework import serializers + +from .models import Podcast, PodcastCategory, PodcastCollection +from .serializers_admin import AbsoluteFileField, AbsoluteImageField + + +class DovodiPodcastCategorySerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + + class Meta: + model = PodcastCategory + fields = ["id", "title", "slug", "status", "order"] + + +class DovodiPodcastCollectionSerializer(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 = PodcastCollection + 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) + + +class DovodiPodcastItemSerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + thumbnail = AbsoluteImageField(required=False, allow_null=True) + audio_file = AbsoluteFileField(required=False, allow_null=True) + remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) + remove_audio_file = serializers.BooleanField(write_only=True, required=False, default=False) + + class Meta: + model = Podcast + fields = [ + "id", + "title", + "slug", + "thumbnail", + "description", + "audio_file", + "audio_time", + "status", + "view_count", + "download_count", + "created_at", + "updated_at", + "remove_thumbnail", + "remove_audio_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_audio_file", False) + return super().create(validated_data) + + def update(self, instance, validated_data): + remove_thumbnail = validated_data.pop("remove_thumbnail", False) + remove_audio_file = validated_data.pop("remove_audio_file", False) + + if remove_thumbnail and instance.thumbnail: + instance.thumbnail.delete(save=False) + instance.thumbnail = None + + if remove_audio_file and instance.audio_file: + instance.audio_file.delete(save=False) + instance.audio_file = None + + return super().update(instance, validated_data) diff --git a/apps/podcast/urls.py b/apps/podcast/urls.py index 864c0c1..2e9a145 100755 --- a/apps/podcast/urls.py +++ b/apps/podcast/urls.py @@ -9,10 +9,16 @@ admin_router.register(r'playlists', AdminPodcastPlaylistViewSet, basename='admin admin_router.register(r'categories', AdminPodcastCategoryViewSet, basename='admin-podcast-category') admin_router.register(r'collections', AdminPodcastCollectionViewSet, basename='admin-podcast-collection') +dovodi_router = SimpleRouter() +dovodi_router.register(r'categories', DovodiPodcastCategoryViewSet, basename='dovodi-podcast-category') +dovodi_router.register(r'collections', DovodiPodcastCollectionViewSet, basename='dovodi-podcast-collection') +dovodi_router.register(r'items', DovodiPodcastItemViewSet, basename='dovodi-podcast-item') + app_name = 'podcast' urlpatterns = [ path('admin/', include(admin_router.urls)), + path('dovodi/', include(dovodi_router.urls)), path('categories/', PodcastCategoryListAPIView.as_view(), name='category-list'), path('pinned-collections/', PinnedPodcastCollectionListView.as_view(), name='pinned-collection-list'), path('collections/', MiddlePodcastCollectionListView.as_view(), name='collection-list'), diff --git a/apps/podcast/views.py b/apps/podcast/views.py index 4496124..e57d4f9 100644 --- a/apps/podcast/views.py +++ b/apps/podcast/views.py @@ -1,15 +1,24 @@ from rest_framework import generics, status -from rest_framework.response import Response 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 rest_framework.permissions import IsAuthenticated from apps.podcast.models import * from apps.podcast.serializers import * +from apps.podcast.serializers_admin import ( + AdminPodcastCategorySerializer, + AdminPodcastCollectionSerializer, + AdminPodcastListSerializer, +) from utils.pagination import StandardResultsSetPagination +from apps.podcast.serializers_dovodi import DovodiPodcastCategorySerializer, DovodiPodcastCollectionSerializer, DovodiPodcastItemSerializer @@ -354,3 +363,51 @@ class UserPlaylistCreateAPIView(generics.CreateAPIView): response_serializer.data, status=status.HTTP_201_CREATED if created else status.HTTP_200_OK ) + + +class DovodiPodcastCategoryViewSet(ModelViewSet): + serializer_class = DovodiPodcastCategorySerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + queryset = PodcastCategory.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 DovodiPodcastCollectionViewSet(ModelViewSet): + serializer_class = DovodiPodcastCollectionSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_queryset(self): + queryset = PodcastCollection.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 + + +class DovodiPodcastItemViewSet(ModelViewSet): + serializer_class = DovodiPodcastItemSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_queryset(self): + queryset = Podcast.objects.all().prefetch_related("categories", "collections").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.distinct() diff --git a/apps/video/serializers_dovodi.py b/apps/video/serializers_dovodi.py new file mode 100644 index 0000000..c02b466 --- /dev/null +++ b/apps/video/serializers_dovodi.py @@ -0,0 +1,115 @@ +from rest_framework import serializers + +from .models import Video, VideoCategory, VideoCollection +from .serializers_admin import AbsoluteFileField, AbsoluteImageField + + +class DovodiVideoCategorySerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + + class Meta: + model = VideoCategory + fields = ["id", "title", "slug", "status", "order"] + + +class DovodiVideoCollectionSerializer(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 = VideoCollection + 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) + + +class DovodiVideoItemSerializer(serializers.ModelSerializer): + slug = serializers.CharField(required=False, allow_blank=True) + thumbnail = AbsoluteImageField(required=False, allow_null=True) + video_file = AbsoluteFileField(required=False, allow_null=True) + remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) + remove_video_file = serializers.BooleanField(write_only=True, required=False, default=False) + + class Meta: + model = Video + fields = [ + "id", + "title", + "slug", + "thumbnail", + "description", + "video_type", + "video_file", + "video_url", + "video_time", + "status", + "view_count", + "created_at", + "updated_at", + "remove_thumbnail", + "remove_video_file", + ] + read_only_fields = ["id", "view_count", "created_at", "updated_at"] + + def validate(self, attrs): + video_type = attrs.get("video_type", getattr(self.instance, "video_type", None)) + video_url = attrs.get("video_url", getattr(self.instance, "video_url", None)) + video_file = attrs.get("video_file", getattr(self.instance, "video_file", None)) + remove_video_file = attrs.get("remove_video_file", False) + + if video_type == Video.VedioTypeChoices.YOUTUBE_LINK and not video_url: + raise serializers.ValidationError({ + "video_url": "This field is required when video type is link.", + }) + + if video_type == Video.VedioTypeChoices.VIDEO_FILE and not video_file and not getattr(self.instance, "video_file", None): + raise serializers.ValidationError({ + "video_file": "This field is required when video type is file.", + }) + + if video_type == Video.VedioTypeChoices.VIDEO_FILE and remove_video_file and "video_file" not in attrs: + raise serializers.ValidationError({ + "video_file": "Please upload a new file before removing the current one.", + }) + + return attrs + + def create(self, validated_data): + validated_data.pop("remove_thumbnail", False) + validated_data.pop("remove_video_file", False) + return super().create(validated_data) + + def update(self, instance, validated_data): + remove_thumbnail = validated_data.pop("remove_thumbnail", False) + remove_video_file = validated_data.pop("remove_video_file", False) + + if remove_thumbnail and instance.thumbnail: + instance.thumbnail.delete(save=False) + instance.thumbnail = None + + if remove_video_file and instance.video_file: + instance.video_file.delete(save=False) + instance.video_file = None + + return super().update(instance, validated_data) diff --git a/apps/video/urls.py b/apps/video/urls.py index 48dca16..290a151 100644 --- a/apps/video/urls.py +++ b/apps/video/urls.py @@ -11,8 +11,14 @@ admin_router.register(r'playlists', AdminVideoPlaylistViewSet, basename='admin-v admin_router.register(r'categories', AdminVideoCategoryViewSet, basename='admin-video-category') admin_router.register(r'collections', AdminVideoCollectionViewSet, basename='admin-video-collection') +dovodi_router = SimpleRouter() +dovodi_router.register(r'categories', DovodiVideoCategoryViewSet, basename='dovodi-video-category') +dovodi_router.register(r'collections', DovodiVideoCollectionViewSet, basename='dovodi-video-collection') +dovodi_router.register(r'items', DovodiVideoItemViewSet, basename='dovodi-video-item') + urlpatterns = [ path('admin/', include(admin_router.urls)), + path('dovodi/', include(dovodi_router.urls)), path('categories/', VideoCategoryListAPIView.as_view(), name='category-list'), path('pinned-collections/', PinnedVideoCollectionListView.as_view(), name='pinned-collection-list'), path('collections/', MiddleVideoCollectionListView.as_view(), name='collection-list'), diff --git a/apps/video/views.py b/apps/video/views.py index 0f4e5fe..4a973be 100644 --- a/apps/video/views.py +++ b/apps/video/views.py @@ -1,15 +1,24 @@ from rest_framework import generics, status -from rest_framework.response import Response 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.pagination import StandardResultsSetPagination -from rest_framework.permissions import IsAuthenticated 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): @@ -432,3 +441,51 @@ class VideoDetailAPIView(generics.RetrieveAPIView): # serializer = self.get_serializer(instance) # return Response(serializer.data) + +class DovodiVideoCategoryViewSet(ModelViewSet): + serializer_class = DovodiVideoCategorySerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + + def get_queryset(self): + queryset = VideoCategory.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 DovodiVideoCollectionViewSet(ModelViewSet): + serializer_class = DovodiVideoCollectionSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + parser_classes = (MultiPartParser, FormParser, JSONParser) + + def get_queryset(self): + queryset = VideoCollection.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 + + +class DovodiVideoItemViewSet(ModelViewSet): + serializer_class = DovodiVideoItemSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + pagination_class = None + 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 +