feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra
This commit is contained in:
+94
-88
@@ -1,49 +1,55 @@
|
||||
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(serializers.ModelSerializer):
|
||||
user_id = serializers.IntegerField(source='user.id', read_only=True)
|
||||
user_name = serializers.CharField(source='user.first_name', read_only=True)
|
||||
user_avatar = serializers.ImageField(source='user.avatar', read_only=True)
|
||||
user_location = serializers.CharField(source='user.location', read_only=True)
|
||||
parent_user_name = serializers.SerializerMethodField()
|
||||
replies = serializers.SerializerMethodField()
|
||||
is_liked = serializers.SerializerMethodField()
|
||||
created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M')
|
||||
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']
|
||||
|
||||
def get_parent_user_name(self, obj):
|
||||
if obj.parent:
|
||||
return obj.parent.user.first_name or obj.parent.user.username
|
||||
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 ''
|
||||
|
||||
def get_replies(self, obj):
|
||||
if obj.parent is None:
|
||||
replies = obj.replies.all()
|
||||
return ArticleCommentSerializer(replies, many=True, context=self.context).data
|
||||
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 []
|
||||
|
||||
def get_is_liked(self, obj):
|
||||
async def get_is_liked(self, obj):
|
||||
request = self.context.get('request')
|
||||
if request and request.user.is_authenticated:
|
||||
return ArticleCommentLike.objects.filter(user=request.user, comment=obj).exists()
|
||||
return await ArticleCommentLike.objects.filter(user=request.user, comment=obj).aexists()
|
||||
return False
|
||||
|
||||
|
||||
class ArticleListSerializer(serializers.ModelSerializer):
|
||||
author_name = serializers.CharField(source='author.first_name', read_only=True)
|
||||
author_avatar = serializers.ImageField(source='author.avatar', read_only=True)
|
||||
comments_count = serializers.SerializerMethodField()
|
||||
read_time = serializers.SerializerMethodField()
|
||||
is_hot = serializers.SerializerMethodField()
|
||||
is_new = serializers.SerializerMethodField()
|
||||
publish_date = serializers.DateTimeField(source='created_at', format='%Y-%m-%d', read_only=True)
|
||||
image = serializers.SerializerMethodField()
|
||||
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
|
||||
@@ -53,25 +59,25 @@ class ArticleListSerializer(serializers.ModelSerializer):
|
||||
'read_time', 'is_featured', 'is_hot', 'is_new', 'is_top', 'status',
|
||||
]
|
||||
|
||||
def get_comments_count(self, obj):
|
||||
return obj.comments.count()
|
||||
async def get_comments_count(self, obj):
|
||||
return await obj.comments.acount()
|
||||
|
||||
def get_read_time(self, obj):
|
||||
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 分钟'
|
||||
|
||||
def get_is_hot(self, obj):
|
||||
async def get_is_hot(self, obj):
|
||||
return obj.views > 1000 or obj.likes > 100
|
||||
|
||||
def get_is_new(self, obj):
|
||||
async def get_is_new(self, obj):
|
||||
from django.utils import timezone
|
||||
import datetime
|
||||
return (timezone.now() - obj.created_at).days < 7
|
||||
|
||||
def get_image(self, obj):
|
||||
async def get_image(self, obj):
|
||||
if obj.cover_image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
@@ -80,23 +86,23 @@ class ArticleListSerializer(serializers.ModelSerializer):
|
||||
return ''
|
||||
|
||||
|
||||
class ArticleDetailSerializer(serializers.ModelSerializer):
|
||||
author_name = serializers.CharField(source='author.first_name', read_only=True)
|
||||
author_avatar = serializers.ImageField(source='author.avatar', read_only=True)
|
||||
author_bio = serializers.CharField(source='author.bio', read_only=True, default='')
|
||||
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 = serializers.SerializerMethodField()
|
||||
favorites_count = serializers.SerializerMethodField()
|
||||
read_time = serializers.SerializerMethodField()
|
||||
publish_date = serializers.DateTimeField(source='created_at', format='%Y-%m-%d %H:%M', read_only=True)
|
||||
update_date = serializers.DateTimeField(source='updated_at', format='%Y-%m-%d %H:%M', read_only=True)
|
||||
image = serializers.SerializerMethodField()
|
||||
is_liked = serializers.SerializerMethodField()
|
||||
is_favorited = serializers.SerializerMethodField()
|
||||
is_following = serializers.SerializerMethodField()
|
||||
related_articles = serializers.SerializerMethodField()
|
||||
comments = serializers.SerializerMethodField()
|
||||
comments_pagination = serializers.SerializerMethodField()
|
||||
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
|
||||
@@ -108,20 +114,20 @@ class ArticleDetailSerializer(serializers.ModelSerializer):
|
||||
'is_liked', 'is_favorited', 'is_following', 'related_articles', 'comments', 'comments_pagination',
|
||||
]
|
||||
|
||||
def get_comments_count(self, obj):
|
||||
return obj.comments.count()
|
||||
async def get_comments_count(self, obj):
|
||||
return await obj.comments.acount()
|
||||
|
||||
def get_favorites_count(self, obj):
|
||||
return obj.article_favorites.count()
|
||||
async def get_favorites_count(self, obj):
|
||||
return await obj.article_favorites.acount()
|
||||
|
||||
def get_read_time(self, obj):
|
||||
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 分钟'
|
||||
|
||||
def get_image(self, obj):
|
||||
async def get_image(self, obj):
|
||||
if obj.cover_image:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
@@ -129,77 +135,77 @@ class ArticleDetailSerializer(serializers.ModelSerializer):
|
||||
return obj.cover_image.url
|
||||
return ''
|
||||
|
||||
def get_is_liked(self, obj):
|
||||
async def get_is_liked(self, obj):
|
||||
request = self.context.get('request')
|
||||
if request and request.user.is_authenticated:
|
||||
return ArticleLike.objects.filter(user=request.user, article=obj).exists()
|
||||
return await ArticleLike.objects.filter(user=request.user, article=obj).aexists()
|
||||
return False
|
||||
|
||||
def get_is_favorited(self, obj):
|
||||
async def get_is_favorited(self, obj):
|
||||
request = self.context.get('request')
|
||||
if request and request.user.is_authenticated:
|
||||
return ArticleFavorite.objects.filter(user=request.user, article=obj).exists()
|
||||
return await ArticleFavorite.objects.filter(user=request.user, article=obj).aexists()
|
||||
return False
|
||||
|
||||
def get_is_following(self, obj):
|
||||
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 Follow.objects.filter(follower=request.user, following=obj.author).exists()
|
||||
return await Follow.objects.filter(follower=request.user, following=obj.author_id).aexists()
|
||||
return False
|
||||
|
||||
def get_related_articles(self, obj):
|
||||
async def get_related_articles(self, obj):
|
||||
queryset = Article.objects.filter(status='published').exclude(id=obj.id)
|
||||
related = queryset.filter(category=obj.category)[:4]
|
||||
if related.count() < 4:
|
||||
remaining = 4 - related.count()
|
||||
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=list(related.values_list('id', flat=True))
|
||||
id__in=[a.id for a in related_list]
|
||||
)[:remaining]
|
||||
related = list(related) + list(extra)
|
||||
return ArticleListSerializer(related, many=True, context=self.context).data
|
||||
related_list = related_list + [a async for a in extra]
|
||||
return [await ArticleListSerializer(a, context=self.context).adata for a in related_list]
|
||||
|
||||
def get_comments(self, obj):
|
||||
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 = top_comments[start:end]
|
||||
serializer = ArticleCommentSerializer(comments_page, many=True, context=self.context)
|
||||
return serializer.data
|
||||
comments_page = [c async for c in top_comments[start:end]]
|
||||
return [await ArticleCommentSerializer(c, context=self.context).adata for c in comments_page]
|
||||
|
||||
def get_comments_pagination(self, obj):
|
||||
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 = obj.comments.filter(parent__isnull=True).count()
|
||||
top_count = await obj.comments.filter(parent__isnull=True).acount()
|
||||
return {
|
||||
'count': top_count,
|
||||
'next': top_count > page_size,
|
||||
}
|
||||
|
||||
|
||||
class ArticleCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
tags = serializers.JSONField(required=False, default=list)
|
||||
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']
|
||||
|
||||
def create(self, validated_data):
|
||||
async def acreate(self, validated_data):
|
||||
validated_data['author'] = self.context['request'].user
|
||||
return super().create(validated_data)
|
||||
return await super().acreate(validated_data)
|
||||
|
||||
|
||||
class ArticleManageSerializer(serializers.ModelSerializer):
|
||||
author_name = serializers.CharField(source='author.first_name', read_only=True)
|
||||
comments_count = serializers.SerializerMethodField()
|
||||
favorites_count = serializers.SerializerMethodField()
|
||||
summary = serializers.CharField(source='excerpt', read_only=True)
|
||||
publish_date = serializers.DateTimeField(source='created_at', format='%Y-%m-%d', read_only=True)
|
||||
update_date = serializers.DateTimeField(source='updated_at', format='%Y-%m-%d', read_only=True)
|
||||
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
|
||||
@@ -209,8 +215,8 @@ class ArticleManageSerializer(serializers.ModelSerializer):
|
||||
'category', 'tags', 'is_top', 'author_name',
|
||||
]
|
||||
|
||||
def get_comments_count(self, obj):
|
||||
return obj.comments.count()
|
||||
async def get_comments_count(self, obj):
|
||||
return await obj.comments.acount()
|
||||
|
||||
def get_favorites_count(self, obj):
|
||||
return obj.article_favorites.count()
|
||||
async def get_favorites_count(self, obj):
|
||||
return await obj.article_favorites.acount()
|
||||
|
||||
Reference in New Issue
Block a user