from adrf.serializers import ( ModelSerializer, CharField, IntegerField, ImageField, DateTimeField, JSONField, SerializerMethodField, ) from rest_framework import serializers from .models import Article, ArticleComment, ArticleLike, ArticleFavorite, ArticleCommentLike class ArticleCommentSerializer(ModelSerializer): user_id = IntegerField(source='user.id', read_only=True) user_name = CharField(source='user.first_name', read_only=True) user_avatar = ImageField(source='user.avatar', read_only=True) user_location = CharField(source='user.location', read_only=True) parent_user_name = SerializerMethodField() replies = SerializerMethodField() is_liked = SerializerMethodField() created_at = DateTimeField(format='%Y-%m-%d %H:%M') class Meta: model = ArticleComment fields = ['id', 'user_id', 'user_name', 'user_avatar', 'user_location', 'content', 'parent', 'parent_user_name', 'likes', 'created_at', 'replies', 'is_liked'] read_only_fields = ['likes', 'created_at'] async def get_parent_user_name(self, obj): if obj.parent_id: # 异步加载父评论作者,避免外键懒加载 parent = await ArticleComment.objects.select_related('user').aget(pk=obj.parent_id) return parent.user.first_name or parent.user.username return '' async def get_replies(self, obj): if obj.parent_id is None: replies = [r async for r in obj.replies.all()] return [await ArticleCommentSerializer(r, context=self.context).adata for r in replies] return [] async def get_is_liked(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return await ArticleCommentLike.objects.filter(user=request.user, comment=obj).aexists() return False class ArticleListSerializer(ModelSerializer): author_name = CharField(source='author.first_name', read_only=True) author_avatar = ImageField(source='author.avatar', read_only=True) comments_count = SerializerMethodField() read_time = SerializerMethodField() is_hot = SerializerMethodField() is_new = SerializerMethodField() publish_date = DateTimeField(source='created_at', format='%Y-%m-%d', read_only=True) image = SerializerMethodField() class Meta: model = Article fields = [ 'id', 'title', 'excerpt', 'category', 'author_name', 'author_avatar', 'views', 'likes', 'comments_count', 'publish_date', 'image', 'tags', 'read_time', 'is_featured', 'is_hot', 'is_new', 'is_top', 'status', ] async def get_comments_count(self, obj): return await obj.comments.acount() async def get_read_time(self, obj): if obj.content: word_count = len(obj.content) minutes = max(1, word_count // 300) return f'{minutes} 分钟' return '1 分钟' async def get_is_hot(self, obj): return obj.views > 1000 or obj.likes > 100 async def get_is_new(self, obj): from django.utils import timezone import datetime return (timezone.now() - obj.created_at).days < 7 async def get_image(self, obj): if obj.cover_image: request = self.context.get('request') if request: return request.build_absolute_uri(obj.cover_image.url) return obj.cover_image.url return '' class ArticleDetailSerializer(ModelSerializer): author_name = CharField(source='author.first_name', read_only=True) author_avatar = ImageField(source='author.avatar', read_only=True) author_bio = CharField(source='author.bio', read_only=True, default='') author_user_id = serializers.PrimaryKeyRelatedField(source='author', read_only=True) comments_count = SerializerMethodField() favorites_count = SerializerMethodField() read_time = SerializerMethodField() publish_date = DateTimeField(source='created_at', format='%Y-%m-%d %H:%M', read_only=True) update_date = DateTimeField(source='updated_at', format='%Y-%m-%d %H:%M', read_only=True) image = SerializerMethodField() is_liked = SerializerMethodField() is_favorited = SerializerMethodField() is_following = SerializerMethodField() related_articles = SerializerMethodField() comments = SerializerMethodField() comments_pagination = SerializerMethodField() class Meta: model = Article fields = [ 'id', 'title', 'excerpt', 'content', 'category', 'tags', 'image', 'author_name', 'author_avatar', 'author_bio', 'author_user_id', 'views', 'likes', 'favorites_count', 'comments_count', 'publish_date', 'update_date', 'read_time', 'is_featured', 'is_top', 'status', 'editor_mode', 'is_liked', 'is_favorited', 'is_following', 'related_articles', 'comments', 'comments_pagination', ] async def get_comments_count(self, obj): return await obj.comments.acount() async def get_favorites_count(self, obj): return await obj.article_favorites.acount() async def get_read_time(self, obj): if obj.content: word_count = len(obj.content) minutes = max(1, word_count // 300) return f'{minutes} 分钟' return '1 分钟' async def get_image(self, obj): if obj.cover_image: request = self.context.get('request') if request: return request.build_absolute_uri(obj.cover_image.url) return obj.cover_image.url return '' async def get_is_liked(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return await ArticleLike.objects.filter(user=request.user, article=obj).aexists() return False async def get_is_favorited(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return await ArticleFavorite.objects.filter(user=request.user, article=obj).aexists() return False async def get_is_following(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: from user.models import Follow return await Follow.objects.filter(follower=request.user, following=obj.author_id).aexists() return False async def get_related_articles(self, obj): queryset = Article.objects.filter(status='published').exclude(id=obj.id) related = queryset.filter(category=obj.category)[:4] related_list = [a async for a in related] if len(related_list) < 4: remaining = 4 - len(related_list) extra = queryset.filter(tags__overlap=obj.tags or []).exclude( id__in=[a.id for a in related_list] )[:remaining] related_list = related_list + [a async for a in extra] return [await ArticleListSerializer(a, context=self.context).adata for a in related_list] async def get_comments(self, obj): request = self.context.get('request') page_size = int(request.query_params.get('comment_page_size', 10)) if request else 10 page = int(request.query_params.get('comment_page', 1)) if request else 10 top_comments = obj.comments.filter(parent__isnull=True) start = (page - 1) * page_size end = start + page_size comments_page = [c async for c in top_comments[start:end]] return [await ArticleCommentSerializer(c, context=self.context).adata for c in comments_page] async def get_comments_pagination(self, obj): request = self.context.get('request') page_size = int(request.query_params.get('comment_page_size', 10)) if request else 10 top_count = await obj.comments.filter(parent__isnull=True).acount() return { 'count': top_count, 'next': top_count > page_size, } class ArticleCreateUpdateSerializer(ModelSerializer): tags = JSONField(required=False, default=list) class Meta: model = Article fields = ['title', 'excerpt', 'content', 'category', 'tags', 'cover_image', 'status', 'editor_mode'] read_only_fields = ['status'] async def acreate(self, validated_data): validated_data['author'] = self.context['request'].user return await super().acreate(validated_data) class ArticleManageSerializer(ModelSerializer): author_name = CharField(source='author.first_name', read_only=True) comments_count = SerializerMethodField() favorites_count = SerializerMethodField() summary = CharField(source='excerpt', read_only=True) publish_date = DateTimeField(source='created_at', format='%Y-%m-%d', read_only=True) update_date = DateTimeField(source='updated_at', format='%Y-%m-%d', read_only=True) class Meta: model = Article fields = [ 'id', 'title', 'summary', 'status', 'views', 'likes', 'comments_count', 'favorites_count', 'publish_date', 'update_date', 'category', 'tags', 'is_top', 'author_name', ] async def get_comments_count(self, obj): return await obj.comments.acount() async def get_favorites_count(self, obj): return await obj.article_favorites.acount()