feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra

This commit is contained in:
async-upgrade
2026-09-06 14:26:17 +08:00
parent 9a6577f71e
commit 8f488fcaaa
55 changed files with 2224 additions and 1513 deletions
+119 -92
View File
@@ -1,10 +1,13 @@
from rest_framework import generics, status
from adrf import generics
from adrf.generics import aget_object_or_404
from adrf.mixins import get_data
from adrf.views import APIView
from asgiref.sync import sync_to_async
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAuthenticatedOrReadOnly
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.pagination import PageNumberPagination
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from django.db.models import F
from django.db import transaction
from drf_yasg.utils import swagger_auto_schema
@@ -77,6 +80,12 @@ class ArticleListCreateView(generics.ListCreateAPIView):
ordering = self.request.query_params.get('ordering', '-created_at')
return qs.order_by('-is_top', ordering)
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
async def post(self, request, *args, **kwargs):
return await self.create(request, *args, **kwargs)
@swagger_auto_schema(
tags=['文章'],
operation_summary='获取文章列表',
@@ -91,17 +100,17 @@ class ArticleListCreateView(generics.ListCreateAPIView):
],
responses={200: success_response}
)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
async def list(self, request, *args, **kwargs):
queryset = await self.afilter_queryset(self.get_queryset())
page = await self.apaginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
data = serializer.data
data = await get_data(serializer)
if request.user.is_authenticated:
from .models import ArticleFavorite
fav_ids = set(
ArticleFavorite.objects.filter(user=request.user).values_list('article_id', flat=True)
)
fav_ids = set([
v async for v in ArticleFavorite.objects.filter(user=request.user).values_list('article_id', flat=True)
])
for item in data:
item['is_favorited'] = item['id'] in fav_ids
else:
@@ -109,12 +118,12 @@ class ArticleListCreateView(generics.ListCreateAPIView):
item['is_favorited'] = False
return self.get_paginated_response(data)
serializer = self.get_serializer(queryset, many=True)
data = serializer.data
data = await get_data(serializer)
if request.user.is_authenticated:
from .models import ArticleFavorite
fav_ids = set(
ArticleFavorite.objects.filter(user=request.user).values_list('article_id', flat=True)
)
fav_ids = set([
v async for v in ArticleFavorite.objects.filter(user=request.user).values_list('article_id', flat=True)
])
for item in data:
item['is_favorited'] = item['id'] in fav_ids
else:
@@ -133,13 +142,14 @@ class ArticleListCreateView(generics.ListCreateAPIView):
401: unauthorized_response,
}
)
def create(self, request, *args, **kwargs):
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
track_task(request.user, 'post')
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
await sync_to_async(track_task)(request.user, 'post')
data = await get_data(serializer)
return create_standardized_response(
data=serializer.data,
data=data,
code=ResponseCode.SUCCESS,
message='文章创建成功',
status_code=status.HTTP_201_CREATED
@@ -168,19 +178,19 @@ class ArticleDetailView(APIView):
404: not_found_response,
}
)
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
Article.objects.filter(pk=pk).update(views=F('views') + 1)
article.refresh_from_db()
async def get(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
await Article.objects.filter(pk=pk).aupdate(views=F('views') + 1)
await article.arefresh_from_db()
serializer = ArticleDetailSerializer(article, context={'request': request})
data = serializer.data
data = await get_data(serializer)
if request.user.is_authenticated:
data['is_favorited'] = ArticleFavorite.objects.filter(
data['is_favorited'] = await ArticleFavorite.objects.filter(
user=request.user, article=article
).exists()
).aexists()
else:
data['is_favorited'] = False
data['favorites_count'] = ArticleFavorite.objects.filter(article=article).count()
data['favorites_count'] = await ArticleFavorite.objects.filter(article=article).acount()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
@@ -199,8 +209,8 @@ class ArticleDetailView(APIView):
404: not_found_response,
}
)
def put(self, request, pk):
article = get_object_or_404(Article, pk=pk)
async def put(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
if article.author != request.user:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
@@ -208,9 +218,10 @@ class ArticleDetailView(APIView):
status_code=status.HTTP_403_FORBIDDEN
)
serializer = ArticleCreateUpdateSerializer(article, data=request.data, partial=True, context={'request': request})
if serializer.is_valid():
serializer.save()
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, message='文章更新成功')
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='文章更新成功')
return create_standardized_error_response(
data=serializer.errors,
code=ResponseCode.VALIDATION_ERROR,
@@ -231,15 +242,15 @@ class ArticleDetailView(APIView):
404: not_found_response,
}
)
def delete(self, request, pk):
article = get_object_or_404(Article, pk=pk)
async def delete(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
if article.author != request.user:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
message='无权操作',
status_code=status.HTTP_403_FORBIDDEN
)
article.delete()
await article.adelete()
return create_standardized_response(code=ResponseCode.SUCCESS, message='文章删除成功', status_code=status.HTTP_204_NO_CONTENT)
@@ -258,6 +269,12 @@ class ArticleCommentListCreateView(generics.ListCreateAPIView):
article_id = self.kwargs['article_id']
return ArticleComment.objects.filter(article_id=article_id, parent__isnull=True)
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
async def post(self, request, *args, **kwargs):
return await self.create(request, *args, **kwargs)
@swagger_auto_schema(
tags=['文章'],
operation_summary='获取文章评论列表',
@@ -272,14 +289,16 @@ class ArticleCommentListCreateView(generics.ListCreateAPIView):
404: not_found_response,
}
)
def list(self, request, *args, **kwargs):
async def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
page = self.paginate_queryset(queryset)
page = await self.apaginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
data = await get_data(serializer)
return self.get_paginated_response(data)
serializer = self.get_serializer(queryset, many=True)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['文章'],
@@ -296,32 +315,36 @@ class ArticleCommentListCreateView(generics.ListCreateAPIView):
404: not_found_response,
}
)
def create(self, request, *args, **kwargs):
async def create(self, request, *args, **kwargs):
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, pk=article_id)
article = await aget_object_or_404(Article, pk=article_id)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
with transaction.atomic():
comment = serializer.save(user=request.user, article=article)
comment_id = comment.id
comment_content = serializer.data.get('content', '')
article_title = article.title
article_author_id = article.author_id
parent_comment_id = comment.parent_id
parent_comment_user_id = comment.parent.user_id if comment.parent else None
user_id = request.user.id
user_nickname = request.user.nickname or request.user.username
if await sync_to_async(serializer.is_valid)():
def _save_and_notify():
with transaction.atomic():
comment = serializer.save(user=request.user, article=article)
comment_id = comment.id
comment_content = serializer.data.get('content', '')
article_title = article.title
article_author_id = article.author_id
parent_comment_id = comment.parent_id
parent_comment_user_id = comment.parent.user_id if comment.parent else None
user_id = request.user.id
user_nickname = request.user.nickname or request.user.username
transaction.on_commit(lambda: self._send_notification_messages(
article_id, article_title, article_author_id, comment_id,
comment_content, parent_comment_id, parent_comment_user_id,
user_id, user_nickname
))
transaction.on_commit(lambda: self._send_notification_messages(
article_id, article_title, article_author_id, comment_id,
comment_content, parent_comment_id, parent_comment_user_id,
user_id, user_nickname
))
return comment
await sync_to_async(_save_and_notify)()
track_task(request.user, 'post')
await sync_to_async(track_task)(request.user, 'post')
data = await get_data(serializer)
return create_standardized_response(
data=serializer.data,
data=data,
code=ResponseCode.SUCCESS,
message='评论成功',
status_code=status.HTTP_201_CREATED
@@ -412,23 +435,23 @@ class ArticleLikeToggleView(APIView):
404: not_found_response,
}
)
def post(self, request, pk):
article = get_object_or_404(Article, pk=pk)
like, created = ArticleLike.objects.get_or_create(user=request.user, article=article)
async def post(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
like, created = await ArticleLike.objects.aget_or_create(user=request.user, article=article)
if not created:
like.delete()
Article.objects.filter(pk=pk).update(likes=F("likes") - 1)
article.refresh_from_db()
await like.adelete()
await Article.objects.filter(pk=pk).aupdate(likes=F("likes") - 1)
await article.arefresh_from_db()
article.likes = max(0, article.likes)
return create_standardized_response(
data={'liked': False, 'likes_count': article.likes},
code=ResponseCode.SUCCESS
)
article.likes = F('likes') + 1
article.save(update_fields=['likes'])
article.refresh_from_db()
await article.asave(update_fields=['likes'])
await article.arefresh_from_db()
if created and article.author != request.user:
create_message(
await sync_to_async(create_message)(
recipient=article.author,
sender=request.user,
msg_type='like',
@@ -461,17 +484,17 @@ class ArticleFavoriteToggleView(APIView):
404: not_found_response,
}
)
def post(self, request, pk):
article = get_object_or_404(Article, pk=pk)
fav, created = ArticleFavorite.objects.get_or_create(user=request.user, article=article)
async def post(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
fav, created = await ArticleFavorite.objects.aget_or_create(user=request.user, article=article)
if not created:
fav.delete()
count = article.article_favorites.count()
await fav.adelete()
count = await article.article_favorites.acount()
return create_standardized_response(
data={'favorited': False, 'favorites_count': count},
code=ResponseCode.SUCCESS
)
count = article.article_favorites.count()
count = await article.article_favorites.acount()
return create_standardized_response(
data={'favorited': True, 'favorites_count': count},
code=ResponseCode.SUCCESS
@@ -494,20 +517,20 @@ class ArticleCommentLikeToggleView(APIView):
404: not_found_response,
}
)
def post(self, request, pk):
comment = get_object_or_404(ArticleComment, pk=pk)
like, created = ArticleCommentLike.objects.get_or_create(user=request.user, comment=comment)
async def post(self, request, pk):
comment = await aget_object_or_404(ArticleComment, pk=pk)
like, created = await ArticleCommentLike.objects.aget_or_create(user=request.user, comment=comment)
if not created:
like.delete()
ArticleComment.objects.filter(pk=pk).update(likes=F("likes") - 1)
comment.refresh_from_db()
await like.adelete()
await ArticleComment.objects.filter(pk=pk).aupdate(likes=F("likes") - 1)
await comment.arefresh_from_db()
comment.likes = max(0, comment.likes)
return create_standardized_response(
data={'liked': False, 'likes_count': comment.likes},
code=ResponseCode.SUCCESS
)
comment.likes = comment.comment_likes.count()
comment.save(update_fields=['likes'])
comment.likes = await comment.comment_likes.acount()
await comment.asave(update_fields=['likes'])
return create_standardized_response(
data={'liked': True, 'likes_count': comment.likes},
code=ResponseCode.SUCCESS
@@ -526,6 +549,9 @@ class MyArticleListView(generics.ListAPIView):
qs = qs.filter(status=st)
return qs.order_by('-updated_at')
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
@swagger_auto_schema(
tags=['文章'],
operation_summary='获取我的文章列表',
@@ -538,10 +564,11 @@ class MyArticleListView(generics.ListAPIView):
401: unauthorized_response,
}
)
def list(self, request, *args, **kwargs):
async def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
class MyArticleBatchView(APIView):
@@ -574,7 +601,7 @@ class MyArticleBatchView(APIView):
401: unauthorized_response,
}
)
def post(self, request):
async def post(self, request):
ids = request.data.get('ids', [])
action = request.data.get('action', '')
if not ids or action not in ('publish', 'draft', 'delete'):
@@ -585,11 +612,11 @@ class MyArticleBatchView(APIView):
)
qs = Article.objects.filter(id__in=ids, author=request.user)
if action == 'delete':
count = qs.delete()[0]
count = (await qs.adelete())[0]
elif action == 'publish':
count = qs.update(status='published')
count = await qs.aupdate(status='published')
elif action == 'draft':
count = qs.update(status='draft')
count = await qs.aupdate(status='draft')
return create_standardized_response(
data={'affected': count},
code=ResponseCode.SUCCESS,
@@ -614,8 +641,8 @@ class ArticleToggleTopView(APIView):
404: not_found_response,
}
)
def post(self, request, pk):
article = get_object_or_404(Article, pk=pk)
async def post(self, request, pk):
article = await aget_object_or_404(Article, pk=pk)
if article.author != request.user:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
@@ -623,9 +650,9 @@ class ArticleToggleTopView(APIView):
status_code=status.HTTP_403_FORBIDDEN
)
article.is_top = not article.is_top
article.save(update_fields=['is_top'])
await article.asave(update_fields=['is_top'])
return create_standardized_response(
data={'is_top': article.is_top},
code=ResponseCode.SUCCESS,
message='置顶状态已更新'
)
)