diff --git a/apps/video/serializers_admin.py b/apps/video/serializers_admin.py new file mode 100644 index 0000000..ec912d9 --- /dev/null +++ b/apps/video/serializers_admin.py @@ -0,0 +1,119 @@ +from rest_framework import serializers + +from .models import Video + + +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 AdminVideoListSerializer(serializers.ModelSerializer): + thumbnail = AbsoluteImageField(required=False, allow_null=True) + video_file = AbsoluteFileField(required=False, allow_null=True) + + 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", + ] + read_only_fields = ["id", "view_count", "created_at", "updated_at"] + + +class AdminVideoDetailSerializer(serializers.ModelSerializer): + 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 f827d55..87680ab 100644 --- a/apps/video/urls.py +++ b/apps/video/urls.py @@ -1,9 +1,15 @@ -from django.urls import path, re_path +from django.urls import include, path, re_path +from rest_framework.routers import SimpleRouter from .views import * +from .views_admin import * app_name = 'video' +admin_router = SimpleRouter() +admin_router.register(r'videos', AdminVideoViewSet, basename='admin-video') + urlpatterns = [ + path('admin/', include(admin_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'), @@ -14,4 +20,4 @@ urlpatterns = [ # Keep old video endpoints for backward compatibility if needed 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_admin.py b/apps/video/views_admin.py new file mode 100644 index 0000000..b65493c --- /dev/null +++ b/apps/video/views_admin.py @@ -0,0 +1,45 @@ +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 + +from apps.account.permissions import IsSuperAdmin +from utils.pagination import StandardResultsSetPagination + +from .models import Video +from .serializers_admin import AdminVideoDetailSerializer, AdminVideoListSerializer + + +class AdminVideoViewSet(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 AdminVideoListSerializer + return AdminVideoDetailSerializer + + def get_queryset(self): + queryset = Video.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(description__icontains=search_query) + | Q(video_url__icontains=search_query) + ) + + status_filter = self.request.query_params.get("status") + if status_filter in {"true", "false"}: + queryset = queryset.filter(status=status_filter == "true") + + video_type = self.request.query_params.get("video_type") + if video_type and video_type != "all": + queryset = queryset.filter(video_type=video_type) + + return queryset.order_by("-created_at")