from rest_framework import serializers from django.core.files.uploadedfile import SimpleUploadedFile from utils.image_compression import maybe_compress_uploaded_file from .models import Article, ArticleCategory, ArticleCollection, ArticleContent, ContentPart, TextSection 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 AdminArticleCategorySerializer(serializers.ModelSerializer): class Meta: model = ArticleCategory fields = ["id", "title", "slug", "status", "order"] class AdminArticleCollectionSerializer(serializers.ModelSerializer): class Meta: model = ArticleCollection fields = ["id", "title", "slug", "summary", "display_position", "status", "order"] class AdminArticleListSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) article_file = AbsoluteFileField(required=False, allow_null=True) categories = AdminArticleCategorySerializer(many=True, read_only=True) collections_detail = AdminArticleCollectionSerializer(source="collections", many=True, read_only=True) class Meta: model = Article fields = [ "id", "title", "slug", "thumbnail", "description", "article_file", "view_count", "download_count", "status", "created_at", "updated_at", "categories", "collections_detail", ] read_only_fields = ["id", "view_count", "download_count", "created_at", "updated_at"] class AdminArticleDetailSerializer(serializers.ModelSerializer): thumbnail = AbsoluteImageField(required=False, allow_null=True) article_file = AbsoluteFileField(required=False, allow_null=True) categories = serializers.PrimaryKeyRelatedField( queryset=ArticleCategory.objects.all(), many=True, required=False, ) categories_detail = AdminArticleCategorySerializer(source="categories", many=True, read_only=True) collections = serializers.PrimaryKeyRelatedField( queryset=ArticleCollection.objects.all(), many=True, required=False, ) collections_detail = AdminArticleCollectionSerializer(source="collections", many=True, read_only=True) remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) remove_article_file = serializers.BooleanField(write_only=True, required=False, default=False) bookmark_count = serializers.SerializerMethodField() class Meta: model = Article fields = [ "id", "title", "slug", "thumbnail", "description", "content", "article_file", "view_count", "download_count", "bookmark_count", "status", "created_at", "updated_at", "categories", "categories_detail", "collections", "collections_detail", "embedded_in", "remove_thumbnail", "remove_article_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.ARTICLE, content_id=obj.id, status=True).count() def create(self, validated_data): validated_data.pop("remove_thumbnail", False) validated_data.pop("remove_article_file", False) categories = validated_data.pop("categories", []) collections = validated_data.pop("collections", []) article = super().create(validated_data) if categories: article.categories.set(categories) if collections: article.collections.set(collections) return article def update(self, instance, validated_data): remove_thumbnail = validated_data.pop("remove_thumbnail", False) remove_article_file = validated_data.pop("remove_article_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_article_file and instance.article_file: instance.article_file.delete(save=False) instance.article_file = None article = super().update(instance, validated_data) if categories is not None: article.categories.set(categories) if collections is not None: article.collections.set(collections) return article class AdminTextSectionSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) class Meta: model = TextSection fields = ["id", "arabic_text", "translation", "order"] class AdminContentPartSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) text_sections = AdminTextSectionSerializer(many=True, required=False) class Meta: model = ContentPart fields = ["id", "order", "text_sections"] class AdminArticleContentSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) parts = AdminContentPartSerializer(many=True, required=False) class Meta: model = ArticleContent fields = [ "id", "article", "title", "content", "priority", "status", "created_at", "updated_at", "parts", ] read_only_fields = ["created_at", "updated_at"] def _sync_parts(self, article_content, parts_data): article_content.parts.all().delete() for part_index, part_data in enumerate(parts_data, start=1): text_sections_data = part_data.pop("text_sections", []) part = ContentPart.objects.create( article_content=article_content, order=part_data.get("order") or part_index, ) for section_index, section_data in enumerate(text_sections_data, start=1): TextSection.objects.create( content_part=part, arabic_text=section_data.get("arabic_text", ""), translation=section_data.get("translation", ""), order=section_data.get("order") or section_index, ) def create(self, validated_data): parts_data = validated_data.pop("parts", []) article_content = super().create(validated_data) self._sync_parts(article_content, parts_data) return article_content def update(self, instance, validated_data): parts_data = validated_data.pop("parts", None) article_content = super().update(instance, validated_data) if parts_data is not None: self._sync_parts(article_content, parts_data) return article_content