from rest_framework import serializers from django.core.files.uploadedfile import SimpleUploadedFile from utils.image_compression import maybe_compress_uploaded_file from .models import PlaylistItem, Podcast, PodcastCategory, PodcastCollection, PodcastPlaylist class AbsoluteImageField(serializers.ImageField): def to_internal_value(self, data): uploaded = super().to_internal_value(data) compressed_bytes = maybe_compress_uploaded_file(uploaded) if compressed_bytes is None: return uploaded return SimpleUploadedFile( name=getattr(uploaded, "name", "image"), content=compressed_bytes, content_type=getattr(uploaded, "content_type", None), ) 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 AdminPodcastCategorySerializer(serializers.ModelSerializer): class Meta: model = PodcastCategory fields = ["id", "title", "slug", "status", "created_at", "updated_at"] class AdminPodcastCollectionSerializer(serializers.ModelSerializer): class Meta: model = PodcastCollection fields = ["id", "title", "slug", "summary", "display_position", "status", "order", "pin_top"] class AdminPodcastListSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) audio_file = AbsoluteFileField(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) class Meta: model = Podcast fields = [ "id", "title", "slug", "thumbnail", "status", "view_count", "download_count", "created_at", "updated_at", "categories_detail", "collections_detail", "audio_file" ] read_only_fields = ["id", "view_count", "download_count", "created_at", "updated_at"] class AdminPodcastDetailSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) audio_file = AbsoluteFileField(required=False, allow_null=True) audio_time = serializers.TimeField(required=False) 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) remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) remove_audio_file = serializers.BooleanField(write_only=True, required=False, default=False) bookmark_count = serializers.SerializerMethodField() class Meta: model = Podcast fields = [ "id", "title", "slug", "thumbnail", "description", "audio_file", "audio_time", "status", "view_count", "download_count", "bookmark_count", "created_at", "updated_at", "categories", "collections", "categories_detail", "collections_detail", "remove_thumbnail", "remove_audio_file", ] read_only_fields = ["id", "view_count", "download_count", "bookmark_count", "created_at", "updated_at"] def get_bookmark_count(self, obj): from apps.bookmark.models.bookmark import Bookmark return Bookmark.objects.filter(service=Bookmark.ServiceChoices.PODCAST, content_id=obj.id, status=True).count() def create(self, validated_data): validated_data.pop("remove_thumbnail", False) validated_data.pop("remove_audio_file", False) categories = validated_data.pop("categories", []) collections = validated_data.pop("collections", []) podcast = super().create(validated_data) if categories: podcast.categories.set(categories) if collections: podcast.collections.set(collections) return podcast def update(self, instance, validated_data): remove_thumbnail = validated_data.pop("remove_thumbnail", False) remove_audio_file = validated_data.pop("remove_audio_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_audio_file and instance.audio_file: instance.audio_file.delete(save=False) instance.audio_file = None podcast = super().update(instance, validated_data) if categories is not None: podcast.categories.set(categories) 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) bookmark_count = serializers.SerializerMethodField() class Meta: model = PodcastPlaylist fields = [ "id", "title", "slug", "slogan", "description", "thumbnail", "order", "status", "view_count", "bookmark_count", "total_time", "created_at", "updated_at", "categories", "collections", "categories_detail", "collections_detail", "playlist_items_detail", "remove_thumbnail", ] read_only_fields = ["id", "view_count", "bookmark_count", "total_time", "created_at", "updated_at", "playlist_items_detail"] def get_bookmark_count(self, obj): from apps.bookmark.models.bookmark import Bookmark return Bookmark.objects.filter(service=Bookmark.ServiceChoices.PODCAST_PLAYLIST, content_id=obj.id, status=True).count() 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