Browse Source

.\apps\#

videoplaylist and podcastplaylist endpoints added for dashboard
master
Mohsen Taba 1 month ago
parent
commit
5ffcb3c87c
  1. 128
      apps/podcast/serializers_admin.py
  2. 3
      apps/podcast/urls.py
  3. 119
      apps/podcast/views_admin.py
  4. 140
      apps/video/serializers_admin.py
  5. 3
      apps/video/urls.py
  6. 148
      apps/video/views_admin.py

128
apps/podcast/serializers_admin.py

@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import Podcast , PodcastCategory , PodcastCollection
from .models import PlaylistItem, Podcast, PodcastCategory, PodcastCollection, PodcastPlaylist
class AbsoluteImageField(serializers.ImageField):
@ -128,3 +128,129 @@ class AdminPodcastDetailSerializer(serializers.ModelSerializer):
if collections is not None:
podcast.collections.set(collections)
return podcast
class AdminPodcastPlaylistItemPodcastSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
class Meta:
model = Podcast
fields = ["id", "title", "slug", "thumbnail", "audio_time", "status"]
class AdminPodcastPlaylistItemSerializer(serializers.ModelSerializer):
podcast_detail = AdminPodcastPlaylistItemPodcastSerializer(source="podcast", read_only=True)
class Meta:
model = PlaylistItem
fields = ["id", "playlist", "podcast", "priority", "created_at", "updated_at", "podcast_detail"]
read_only_fields = ["id", "created_at", "updated_at", "podcast_detail"]
def validate(self, attrs):
playlist = attrs.get("playlist", getattr(self.instance, "playlist", None))
podcast = attrs.get("podcast", getattr(self.instance, "podcast", None))
if playlist and podcast:
query = PlaylistItem.objects.filter(podcast=podcast).exclude(playlist=playlist)
if self.instance:
query = query.exclude(pk=self.instance.pk)
existing = query.select_related("playlist").first()
if existing:
raise serializers.ValidationError({
"podcast": f'This podcast is already used in playlist "{existing.playlist.title}".',
})
return attrs
class AdminPodcastPlaylistListSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
categories_detail = AdminPodcastCategorySerializer(source="categories", many=True, read_only=True)
collections_detail = AdminPodcastCollectionSerializer(source="collections", many=True, read_only=True)
items_count = serializers.SerializerMethodField()
class Meta:
model = PodcastPlaylist
fields = [
"id",
"title",
"slug",
"thumbnail",
"slogan",
"status",
"order",
"view_count",
"total_time",
"created_at",
"updated_at",
"categories_detail",
"collections_detail",
"items_count",
]
read_only_fields = ["id", "view_count", "total_time", "created_at", "updated_at", "items_count"]
def get_items_count(self, obj):
return obj.playlist_items.count()
class AdminPodcastPlaylistDetailSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
categories = serializers.PrimaryKeyRelatedField(queryset=PodcastCategory.objects.all(), many=True, required=False)
collections = serializers.PrimaryKeyRelatedField(queryset=PodcastCollection.objects.all(), many=True, required=False)
categories_detail = AdminPodcastCategorySerializer(source="categories", many=True, read_only=True)
collections_detail = AdminPodcastCollectionSerializer(source="collections", many=True, read_only=True)
playlist_items_detail = AdminPodcastPlaylistItemSerializer(source="playlist_items", many=True, read_only=True)
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
class Meta:
model = PodcastPlaylist
fields = [
"id",
"title",
"slug",
"slogan",
"description",
"thumbnail",
"order",
"status",
"view_count",
"total_time",
"created_at",
"updated_at",
"categories",
"collections",
"categories_detail",
"collections_detail",
"playlist_items_detail",
"remove_thumbnail",
]
read_only_fields = ["id", "view_count", "total_time", "created_at", "updated_at", "playlist_items_detail"]
def create(self, validated_data):
validated_data.pop("remove_thumbnail", False)
categories = validated_data.pop("categories", [])
collections = validated_data.pop("collections", [])
playlist = super().create(validated_data)
if categories:
playlist.categories.set(categories)
if collections:
playlist.collections.set(collections)
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return playlist
def update(self, instance, validated_data):
remove_thumbnail = validated_data.pop("remove_thumbnail", 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
playlist = super().update(instance, validated_data)
if categories is not None:
playlist.categories.set(categories)
if collections is not None:
playlist.collections.set(collections)
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return playlist

3
apps/podcast/urls.py

@ -5,6 +5,7 @@ from .views_admin import *
admin_router = SimpleRouter()
admin_router.register(r'podcasts', AdminPodcastViewSet, basename='admin-podcast')
admin_router.register(r'playlists', AdminPodcastPlaylistViewSet, basename='admin-podcast-playlist')
admin_router.register(r'categories', AdminPodcastCategoryViewSet, basename='admin-podcast-category')
admin_router.register(r'collections', AdminPodcastCollectionViewSet, basename='admin-podcast-collection')
@ -24,4 +25,4 @@ urlpatterns = [
path('user-playlist/list/', UserPlaylistListAPIView.as_view(), name='user-playlist-list'),
]
]

119
apps/podcast/views_admin.py

@ -1,18 +1,25 @@
from django.db import transaction
from django.db.models import Q
from rest_framework import status
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, ReadOnlyModelViewSet
from apps.account.permissions import IsSuperAdmin
from utils.pagination import StandardResultsSetPagination
from .models import Podcast, PodcastCollection, PodcastCategory
from .models import PlaylistItem, Podcast, PodcastCollection, PodcastCategory, PodcastPlaylist
from .serializers_admin import (
AdminPodcastCategorySerializer,
AdminPodcastCollectionSerializer,
AdminPodcastDetailSerializer,
AdminPodcastListSerializer,
AdminPodcastPlaylistDetailSerializer,
AdminPodcastPlaylistItemSerializer,
AdminPodcastPlaylistListSerializer,
)
@ -71,3 +78,113 @@ class AdminPodcastViewSet(ModelViewSet):
queryset = queryset.filter(collections__id=collection_id)
return queryset.distinct().order_by("-created_at")
class AdminPodcastPlaylistViewSet(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 AdminPodcastPlaylistListSerializer
return AdminPodcastPlaylistDetailSerializer
def get_queryset(self):
queryset = PodcastPlaylist.objects.all().prefetch_related("categories", "collections", "playlist_items__podcast")
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(slogan__icontains=search_query)
| Q(description__icontains=search_query)
)
status_filter = self.request.query_params.get("status")
if status_filter in {"true", "false"}:
queryset = queryset.filter(status=status_filter == "true")
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("order", "-created_at")
@action(detail=True, methods=["get", "post"], url_path="items")
def items(self, request, pk=None):
playlist = self.get_object()
if request.method.lower() == "get":
items = playlist.playlist_items.select_related("podcast").order_by("priority", "id")
serializer = AdminPodcastPlaylistItemSerializer(items, many=True, context={"request": request})
return Response(serializer.data)
serializer = AdminPodcastPlaylistItemSerializer(
data={
**request.data,
"playlist": playlist.id,
"priority": request.data.get("priority", playlist.playlist_items.count() + 1),
},
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(serializer.data, status=status.HTTP_201_CREATED)
@action(detail=True, methods=["patch", "delete"], url_path=r"items/(?P<item_id>\d+)")
def item_detail(self, request, pk=None, item_id=None):
playlist = self.get_object()
item = playlist.playlist_items.select_related("podcast").get(pk=item_id)
if request.method.lower() == "delete":
item.delete()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = AdminPodcastPlaylistItemSerializer(
item,
data={**request.data, "playlist": playlist.id},
partial=True,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(serializer.data)
@action(detail=True, methods=["post"], url_path="items/reorder")
def reorder_items(self, request, pk=None):
playlist = self.get_object()
item_ids = request.data.get("item_ids", [])
if not isinstance(item_ids, list):
return Response({"detail": "item_ids must be a list."}, status=status.HTTP_400_BAD_REQUEST)
items = list(playlist.playlist_items.filter(id__in=item_ids))
if len(items) != len(item_ids):
return Response({"detail": "Some items were not found."}, status=status.HTTP_400_BAD_REQUEST)
item_map = {item.id: item for item in items}
with transaction.atomic():
for index, item_id in enumerate(item_ids, start=1):
item = item_map[item_id]
if item.priority != index:
item.priority = index
item.save(update_fields=["priority"])
serializer = AdminPodcastPlaylistItemSerializer(
playlist.playlist_items.select_related("podcast").order_by("priority", "id"),
many=True,
context={"request": request},
)
return Response(serializer.data)

140
apps/video/serializers_admin.py

@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import Video
from .models import PlaylistItem, Video, VideoCategory, VideoCollection, VideoPlaylist
class AbsoluteImageField(serializers.ImageField):
@ -25,6 +25,18 @@ class AbsoluteFileField(serializers.FileField):
return url
class AdminVideoCategorySerializer(serializers.ModelSerializer):
class Meta:
model = VideoCategory
fields = ["id", "title", "slug", "status", "order"]
class AdminVideoCollectionSerializer(serializers.ModelSerializer):
class Meta:
model = VideoCollection
fields = ["id", "title", "slug", "summary", "display_position", "status", "order", "pin_top"]
class AdminVideoListSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
video_file = AbsoluteFileField(required=False, allow_null=True)
@ -117,3 +129,129 @@ class AdminVideoDetailSerializer(serializers.ModelSerializer):
instance.video_file = None
return super().update(instance, validated_data)
class AdminVideoPlaylistItemVideoSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
class Meta:
model = Video
fields = ["id", "title", "slug", "thumbnail", "video_type", "video_time", "status"]
class AdminVideoPlaylistItemSerializer(serializers.ModelSerializer):
video_detail = AdminVideoPlaylistItemVideoSerializer(source="video", read_only=True)
class Meta:
model = PlaylistItem
fields = ["id", "playlist", "video", "priority", "created_at", "updated_at", "video_detail"]
read_only_fields = ["id", "created_at", "updated_at", "video_detail"]
def validate(self, attrs):
playlist = attrs.get("playlist", getattr(self.instance, "playlist", None))
video = attrs.get("video", getattr(self.instance, "video", None))
if playlist and video:
query = PlaylistItem.objects.filter(video=video).exclude(playlist=playlist)
if self.instance:
query = query.exclude(pk=self.instance.pk)
existing = query.select_related("playlist").first()
if existing:
raise serializers.ValidationError({
"video": f'This video is already used in playlist "{existing.playlist.title}".',
})
return attrs
class AdminVideoPlaylistListSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
categories_detail = AdminVideoCategorySerializer(source="categories", many=True, read_only=True)
collections_detail = AdminVideoCollectionSerializer(source="collections", many=True, read_only=True)
items_count = serializers.SerializerMethodField()
class Meta:
model = VideoPlaylist
fields = [
"id",
"title",
"slug",
"thumbnail",
"slogan",
"status",
"order",
"view_count",
"total_time",
"created_at",
"updated_at",
"categories_detail",
"collections_detail",
"items_count",
]
read_only_fields = ["id", "view_count", "total_time", "created_at", "updated_at", "items_count"]
def get_items_count(self, obj):
return obj.playlist_items.count()
class AdminVideoPlaylistDetailSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True)
categories = serializers.PrimaryKeyRelatedField(queryset=VideoCategory.objects.all(), many=True, required=False)
collections = serializers.PrimaryKeyRelatedField(queryset=VideoCollection.objects.all(), many=True, required=False)
categories_detail = AdminVideoCategorySerializer(source="categories", many=True, read_only=True)
collections_detail = AdminVideoCollectionSerializer(source="collections", many=True, read_only=True)
playlist_items_detail = AdminVideoPlaylistItemSerializer(source="playlist_items", many=True, read_only=True)
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
class Meta:
model = VideoPlaylist
fields = [
"id",
"title",
"slug",
"slogan",
"description",
"thumbnail",
"order",
"status",
"view_count",
"total_time",
"created_at",
"updated_at",
"categories",
"collections",
"categories_detail",
"collections_detail",
"playlist_items_detail",
"remove_thumbnail",
]
read_only_fields = ["id", "view_count", "total_time", "created_at", "updated_at", "playlist_items_detail"]
def create(self, validated_data):
validated_data.pop("remove_thumbnail", False)
categories = validated_data.pop("categories", [])
collections = validated_data.pop("collections", [])
playlist = super().create(validated_data)
if categories:
playlist.categories.set(categories)
if collections:
playlist.collections.set(collections)
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return playlist
def update(self, instance, validated_data):
remove_thumbnail = validated_data.pop("remove_thumbnail", 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
playlist = super().update(instance, validated_data)
if categories is not None:
playlist.categories.set(categories)
if collections is not None:
playlist.collections.set(collections)
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return playlist

3
apps/video/urls.py

@ -7,6 +7,9 @@ app_name = 'video'
admin_router = SimpleRouter()
admin_router.register(r'videos', AdminVideoViewSet, basename='admin-video')
admin_router.register(r'playlists', AdminVideoPlaylistViewSet, basename='admin-video-playlist')
admin_router.register(r'categories', AdminVideoCategoryViewSet, basename='admin-video-category')
admin_router.register(r'collections', AdminVideoCollectionViewSet, basename='admin-video-collection')
urlpatterns = [
path('admin/', include(admin_router.urls)),

148
apps/video/views_admin.py

@ -1,14 +1,46 @@
from django.db import transaction
from django.db.models import Q
from rest_framework import status
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.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from apps.account.permissions import IsSuperAdmin
from utils.pagination import StandardResultsSetPagination
from .models import Video
from .serializers_admin import AdminVideoDetailSerializer, AdminVideoListSerializer
from .models import PlaylistItem, Video, VideoCategory, VideoCollection, VideoPlaylist
from .serializers_admin import (
AdminVideoCategorySerializer,
AdminVideoCollectionSerializer,
AdminVideoDetailSerializer,
AdminVideoListSerializer,
AdminVideoPlaylistDetailSerializer,
AdminVideoPlaylistItemSerializer,
AdminVideoPlaylistListSerializer,
)
class AdminVideoCategoryViewSet(ReadOnlyModelViewSet):
serializer_class = AdminVideoCategorySerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
pagination_class = None
def get_queryset(self):
return VideoCategory.objects.all().order_by("title")
class AdminVideoCollectionViewSet(ReadOnlyModelViewSet):
serializer_class = AdminVideoCollectionSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
pagination_class = None
def get_queryset(self):
return VideoCollection.objects.all().order_by("display_position", "order", "title")
class AdminVideoViewSet(ModelViewSet):
@ -43,3 +75,113 @@ class AdminVideoViewSet(ModelViewSet):
queryset = queryset.filter(video_type=video_type)
return queryset.order_by("-created_at")
class AdminVideoPlaylistViewSet(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 AdminVideoPlaylistListSerializer
return AdminVideoPlaylistDetailSerializer
def get_queryset(self):
queryset = VideoPlaylist.objects.all().prefetch_related("categories", "collections", "playlist_items__video")
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(slogan__icontains=search_query)
| Q(description__icontains=search_query)
)
status_filter = self.request.query_params.get("status")
if status_filter in {"true", "false"}:
queryset = queryset.filter(status=status_filter == "true")
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("order", "-created_at")
@action(detail=True, methods=["get", "post"], url_path="items")
def items(self, request, pk=None):
playlist = self.get_object()
if request.method.lower() == "get":
items = playlist.playlist_items.select_related("video").order_by("priority", "id")
serializer = AdminVideoPlaylistItemSerializer(items, many=True, context={"request": request})
return Response(serializer.data)
serializer = AdminVideoPlaylistItemSerializer(
data={
**request.data,
"playlist": playlist.id,
"priority": request.data.get("priority", playlist.playlist_items.count() + 1),
},
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(serializer.data, status=status.HTTP_201_CREATED)
@action(detail=True, methods=["patch", "delete"], url_path=r"items/(?P<item_id>\d+)")
def item_detail(self, request, pk=None, item_id=None):
playlist = self.get_object()
item = playlist.playlist_items.select_related("video").get(pk=item_id)
if request.method.lower() == "delete":
item.delete()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = AdminVideoPlaylistItemSerializer(
item,
data={**request.data, "playlist": playlist.id},
partial=True,
context={"request": request},
)
serializer.is_valid(raise_exception=True)
serializer.save()
playlist.total_time = playlist.calculate_total_time()
playlist.save(update_fields=["total_time"])
return Response(serializer.data)
@action(detail=True, methods=["post"], url_path="items/reorder")
def reorder_items(self, request, pk=None):
playlist = self.get_object()
item_ids = request.data.get("item_ids", [])
if not isinstance(item_ids, list):
return Response({"detail": "item_ids must be a list."}, status=status.HTTP_400_BAD_REQUEST)
items = list(playlist.playlist_items.filter(id__in=item_ids))
if len(items) != len(item_ids):
return Response({"detail": "Some items were not found."}, status=status.HTTP_400_BAD_REQUEST)
item_map = {item.id: item for item in items}
with transaction.atomic():
for index, item_id in enumerate(item_ids, start=1):
item = item_map[item_id]
if item.priority != index:
item.priority = index
item.save(update_fields=["priority"])
serializer = AdminVideoPlaylistItemSerializer(
playlist.playlist_items.select_related("video").order_by("priority", "id"),
many=True,
context={"request": request},
)
return Response(serializer.data)
Loading…
Cancel
Save