feat: 100% async - native async serializers (adata/asave/acreate), async cache (redis.asyncio), view call-site migration

This commit is contained in:
async-upgrade
2026-09-06 14:36:41 +08:00
parent 8f488fcaaa
commit 571641e539
18 changed files with 81 additions and 81 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
import asyncio
import aiohttp
from utils.async_cache import aget_cache, aset_cache, adelete_cache
from asgiref.sync import sync_to_async
from rest_framework.permissions import AllowAny
@@ -106,7 +107,7 @@ class GetIPDataView(APIView):
cache_key = f"ip_location:{ip}"
# 先尝试从缓存获取(django-redis 为同步客户端,用 sync_to_async 兜底)
cached_data = await sync_to_async(default_cache.get)(cache_key)
cached_data = await aget_cache(cache_key)
if cached_data:
return Response(
{"code": 200, "message": "success", "data": cached_data},
@@ -143,7 +144,7 @@ class GetIPDataView(APIView):
}
# 写入缓存(sync_to_async 兜底)
await sync_to_async(default_cache.set)(cache_key, ip_info, IP_CACHE_TIMEOUT)
await aset_cache(cache_key, ip_info, IP_CACHE_TIMEOUT)
return Response(
{"code": 200, "message": "success", "data": ip_info},
+3 -3
View File
@@ -26,7 +26,7 @@ class CategoryListView(APIView):
return JsonResponse({
'code': 200,
'message': 'success',
'data': serializer.data
'data': await serializer.adata
})
@@ -74,7 +74,7 @@ class ApiItemListView(APIView):
return JsonResponse({
'code': 200,
'message': 'success',
'data': serializer.data
'data': await serializer.adata
})
@@ -94,7 +94,7 @@ class ApiItemDetailView(APIView):
return JsonResponse({
'code': 200,
'message': 'success',
'data': serializer.data
'data': await serializer.adata
})
except ApiItem.DoesNotExist:
return JsonResponse({
+5 -5
View File
@@ -144,8 +144,8 @@ class ArticleListCreateView(generics.ListCreateAPIView):
)
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
await sync_to_async(track_task)(request.user, 'post')
data = await get_data(serializer)
return create_standardized_response(
@@ -218,8 +218,8 @@ class ArticleDetailView(APIView):
status_code=status.HTTP_403_FORBIDDEN
)
serializer = ArticleCreateUpdateSerializer(article, data=request.data, partial=True, context={'request': request})
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='文章更新成功')
return create_standardized_error_response(
@@ -319,7 +319,7 @@ class ArticleCommentListCreateView(generics.ListCreateAPIView):
article_id = self.kwargs['article_id']
article = await aget_object_or_404(Article, pk=article_id)
serializer = self.get_serializer(data=request.data)
if await sync_to_async(serializer.is_valid)():
if serializer.is_valid():
def _save_and_notify():
with transaction.atomic():
comment = serializer.save(user=request.user, article=article)
+7 -7
View File
@@ -44,7 +44,7 @@ class BugReportListCreateAPIView(generics.ListCreateAPIView):
items = [x async for x in queryset]
serializer = self.get_serializer(items, many=True)
# 兜底:user_name 触发 user 外键懒加载、images_count 内部有同步 ORM count()
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(
code=ResponseCode.SUCCESS,
data=data,
@@ -60,13 +60,13 @@ class BugReportListCreateAPIView(generics.ListCreateAPIView):
)
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
is_valid = await sync_to_async(serializer.is_valid)()
is_valid = serializer.is_valid()
if is_valid:
# 兜底:serializer.create 内部有同步 ORM(BugReport/BugReportImage/BugReportAttachment 的 create)
bug_report = await sync_to_async(serializer.save)(user=request.user)
bug_report = await serializer.asave(user=request.user)
detail_serializer = BugReportDetailSerializer(bug_report)
# 兜底:detail 序列化会触发 user/images/attachments/comments 的同步懒加载
data = await sync_to_async(lambda: detail_serializer.data)()
data = await detail_serializer.adata
return create_standardized_response(
code=ResponseCode.SUCCESS,
data=data,
@@ -104,7 +104,7 @@ class BugReportDetailAPIView(generics.RetrieveAPIView):
instance = await self.get_object()
serializer = self.get_serializer(instance)
# 兜底:detail 序列化会触发 user/images/attachments/comments 的同步懒加载
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(
code=ResponseCode.SUCCESS,
data=data,
@@ -144,7 +144,7 @@ class BugReportCommentCreateAPIView(generics.CreateAPIView):
)
serializer = self.get_serializer(data=request.data)
is_valid = await sync_to_async(serializer.is_valid)()
is_valid = serializer.is_valid()
if is_valid:
# 兜底:serializer.create 内部有同步 ORM(comment 及其图片/附件的 create)
comment = await sync_to_async(serializer.save)(
@@ -154,7 +154,7 @@ class BugReportCommentCreateAPIView(generics.CreateAPIView):
)
comment_serializer = BugReportCommentSerializer(comment)
# 兜底:comment 序列化会触发 user 外键与 images/attachments 的同步懒加载
data = await sync_to_async(lambda: comment_serializer.data)()
data = await comment_serializer.adata
return create_standardized_response(
code=ResponseCode.SUCCESS,
data=data,
+3 -3
View File
@@ -292,7 +292,7 @@ class ConversationListView(APIView):
conversations = [p.conversation async for p in participations]
serializer = ConversationSerializer(conversations, many=True, context={'request': request})
# 兜底:ConversationSerializer 的 SerializerMethodField 内部有同步 ORM 查询
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
@@ -334,7 +334,7 @@ class ConversationListView(APIView):
serializer = ConversationSerializer(conversation, context={'request': request})
# 兜底:ConversationSerializer 的 SerializerMethodField 内部有同步 ORM 查询
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, status_code=status.HTTP_201_CREATED)
@@ -372,7 +372,7 @@ class ConversationMessageView(APIView):
serializer = ChatMessageSerializer(page_messages, many=True, context={'request': request})
# 兜底:'conversation' 外键未预加载,serializer 取值会触发同步 ORM
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(data={
'results': data,
'total': total,
+4 -4
View File
@@ -71,10 +71,10 @@ class BrowsingHistoryListCreateView(generics.ListCreateAPIView):
page = await sync_to_async(self.paginate_queryset)(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
return self.get_paginated_response(await serializer.adata)
results = [x async for x in queryset]
serializer = self.get_serializer(results, many=True)
return Response({'code': 10000, 'message': 'Success', 'data': serializer.data})
return Response({'code': 10000, 'message': 'Success', 'data': await serializer.adata})
@swagger_auto_schema(
tags=['历史'],
@@ -85,10 +85,10 @@ class BrowsingHistoryListCreateView(generics.ListCreateAPIView):
)
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
is_valid = await sync_to_async(serializer.is_valid)()
is_valid = serializer.is_valid()
if is_valid:
# 兜底:serializer.create 内部有同步 ORM(filter/first/save/create)
instance = await sync_to_async(serializer.save)()
instance = await serializer.asave()
return create_standardized_response(
data=BrowsingHistorySerializer(instance).data,
code=ResponseCode.SUCCESS,
+9 -9
View File
@@ -117,8 +117,8 @@ class CourseListCreateView(generics.ListCreateAPIView):
)
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
data = await get_data(serializer)
return create_standardized_response(
data=data,
@@ -188,8 +188,8 @@ class CourseDetailView(APIView):
status_code=status.HTTP_403_FORBIDDEN
)
serializer = CourseCreateUpdateSerializer(course, data=request.data, partial=True, context={'request': request})
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='课程更新成功')
return create_standardized_error_response(
@@ -278,7 +278,7 @@ class ChapterListCreateView(generics.ListCreateAPIView):
course_id = self.kwargs['course_id']
course = await aget_object_or_404(Course, pk=course_id)
serializer = self.get_serializer(data=request.data)
if await sync_to_async(serializer.is_valid)():
if serializer.is_valid():
await sync_to_async(serializer.save)(course=course)
data = await get_data(serializer)
return create_standardized_response(
@@ -336,8 +336,8 @@ class ChapterDetailView(APIView):
async def put(self, request, pk):
chapter = await aget_object_or_404(Chapter, pk=pk)
serializer = ChapterSerializer(chapter, data=request.data, partial=True)
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='章节更新成功')
return create_standardized_error_response(
@@ -414,8 +414,8 @@ class ChapterContentView(APIView):
defaults={'content_md': '', 'content_html': ''}
)
serializer = ChapterContentSerializer(content, data=request.data, partial=True)
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
if serializer.is_valid():
await serializer.asave()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='内容保存成功')
return create_standardized_error_response(
+9 -11
View File
@@ -42,9 +42,9 @@ class MessageListView(APIView):
if msg_type == 'system':
queryset = SystemMessage.objects.all()
def _paginate_system():
async def _paginate_system():
paginator = MessagePagination()
page = paginator.paginate_queryset(queryset, request)
page = await sync_to_async(paginator.paginate_queryset)(queryset, request)
read_ids = set(
SystemMessageRead.objects.filter(
user=request.user,
@@ -52,10 +52,9 @@ class MessageListView(APIView):
).values_list('system_message_id', flat=True)
)
serializer = SystemMessageSerializer(page, many=True, context={'request': request, 'read_ids': read_ids})
return paginator.get_paginated_response(serializer.data).data
return paginator.get_paginated_response(await serializer.adata).data
# 兜底:DRF 分页器与 SystemMessageSerializer 内部为同步调用
data = await sync_to_async(_paginate_system)()
data = await _paginate_system()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
valid_types = ['reply', 'at_me', 'like']
@@ -64,14 +63,13 @@ class MessageListView(APIView):
else:
queryset = Message.objects.filter(recipient=request.user)
def _paginate_messages():
async def _paginate_messages():
paginator = MessagePagination()
page = paginator.paginate_queryset(queryset, request)
page = await sync_to_async(paginator.paginate_queryset)(queryset, request)
serializer = MessageListSerializer(page, many=True, context={'request': request})
return paginator.get_paginated_response(serializer.data).data
return paginator.get_paginated_response(await serializer.adata).data
# 兜底:DRF 分页器与 MessageListSerializer(obj.sender 外键访问)内部为同步调用
data = await sync_to_async(_paginate_messages)()
data = await _paginate_messages()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@@ -294,5 +292,5 @@ class SystemMessageDetailView(APIView):
serializer = SystemMessageDetailSerializer(system_message, context={'request': request})
# 兜底:SystemMessageDetailSerializer.get_is_read 内部有同步 ORM exists() 查询
data = await sync_to_async(lambda: serializer.data)()
data = await serializer.adata
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
+3 -3
View File
@@ -25,7 +25,7 @@ class ColorHistoryListView(APIView):
created_at__gte=cutoff
).order_by('-created_at')[:50]]
serializer = ColorHistorySerializer(histories, many=True)
return Response(serializer.data)
return Response(await serializer.adata)
async def post(self, request):
color = request.data.get('color', '')
@@ -43,13 +43,13 @@ class ColorHistoryListView(APIView):
existing.created_at = timezone.now()
await existing.asave()
serializer = ColorHistorySerializer(existing)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(await serializer.adata, status=status.HTTP_200_OK)
history = await ColorHistory.objects.acreate(
user=request.user,
color=color
)
serializer = ColorHistorySerializer(history)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(await serializer.adata, status=status.HTTP_201_CREATED)
class ColorHistoryDetailView(APIView):
+3 -3
View File
@@ -35,7 +35,7 @@ class CompressionHistoryListView(APIView):
return JsonResponse({
'success': True,
'data': serializer.data,
'data': await serializer.adata,
'total': total,
'page': page,
'page_size': page_size
@@ -50,10 +50,10 @@ class CompressionHistoryListView(APIView):
serializer = CompressionHistorySerializer(data=data)
if serializer.is_valid():
# DRF 序列化器 save() 为同步 ORM 操作,sync_to_async 兜底
await sync_to_async(serializer.save)()
await serializer.asave()
return JsonResponse({
'success': True,
'data': serializer.data
'data': await serializer.adata
}, status=status.HTTP_201_CREATED)
return JsonResponse({
+3 -3
View File
@@ -24,7 +24,7 @@ class ToolCategoryListView(APIView):
serializer = ToolCategorySerializer(categories, many=True)
return JsonResponse({
'success': True,
'data': serializer.data
'data': await serializer.adata
})
@@ -70,7 +70,7 @@ class ToolListView(APIView):
tool_list = [t async for t in tools]
serializer = ToolSerializer(tool_list, many=True)
data = serializer.data
data = await serializer.adata
# 构建 favorites_count 映射
favorites_count_map = {
@@ -111,7 +111,7 @@ class ToolDetailView(APIView):
try:
tool = await Tool.objects.select_related('category').aget(pk=pk, is_enabled=True)
serializer = ToolSerializer(tool)
data = serializer.data
data = await serializer.adata
if request.user.is_authenticated:
data['is_favorited'] = await ToolFavorite.objects.filter(
user=request.user, tool=tool
+2 -2
View File
@@ -90,11 +90,11 @@ class BlacklistAddAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
blacklist = await sync_to_async(serializer.save)()
blacklist = await serializer.asave()
result_serializer = BlacklistSerializer(blacklist)
return create_standardized_response(
data=result_serializer.data,
data=await result_serializer.adata,
code=ResponseCode.SUCCESS,
status_code=status.HTTP_201_CREATED
)
+3 -3
View File
@@ -102,7 +102,7 @@ class SendChangeEmailCodeAPIView(APIView):
try:
# save() 内部触发验证码邮件发送(Celery/cache/SMTP 链路),线程池兜底
email = await sync_to_async(serializer.save)()
email = await serializer.asave()
await sync_to_async(reset_failures)(operation, identifier)
return create_standardized_response(
data={'email_sent': True},
@@ -157,11 +157,11 @@ class ChangeEmailAPIView(APIView):
)
try:
updated_user = await sync_to_async(serializer.save)()
updated_user = await serializer.asave()
user_serializer = UserSerializer(updated_user)
return create_standardized_response(
data={'user': user_serializer.data},
data={'user': await user_serializer.adata},
code=ResponseCode.EMAIL_CHANGED,
status_code=status.HTTP_200_OK
)
+1 -1
View File
@@ -76,7 +76,7 @@ class LoginRecordListAPIView(APIView):
return create_standardized_response(
data={
'records': serializer.data,
'records': await serializer.adata,
'total': total,
'page': page,
'page_size': page_size,
+3 -3
View File
@@ -41,7 +41,7 @@ class SendPhoneCodeAPIView(APIView):
)
try:
phone = await sync_to_async(serializer.save)()
phone = await serializer.asave()
return create_standardized_response(
data={'phone_sent': True},
code=ResponseCode.SUCCESS,
@@ -81,11 +81,11 @@ class ChangePhoneAPIView(APIView):
)
try:
updated_user = await sync_to_async(serializer.save)()
updated_user = await serializer.asave()
user_serializer = UserSerializer(updated_user)
return create_standardized_response(
data={'user': user_serializer.data},
data={'user': await user_serializer.adata},
code=ResponseCode.SUCCESS,
status_code=status.HTTP_200_OK
)
+1 -1
View File
@@ -81,6 +81,6 @@ class RegionListView(APIView):
regions = [r async for r in queryset]
serializer = RegionSimpleSerializer(regions, many=True)
return Response(
{"code": 200, "message": "success", "data": serializer.data},
{"code": 200, "message": "success", "data": await serializer.adata},
status=status.HTTP_200_OK
)
+18 -17
View File
@@ -2,6 +2,7 @@ import os
import uuid
import logging
from utils.async_cache import aget_cache, aset_cache, adelete_cache
from asgiref.sync import sync_to_async
from utils.email_utils import validate_email_mx
from rest_framework.decorators import permission_classes
@@ -93,7 +94,7 @@ class SendUserEmailAPIView(APIView):
if not user_exists_result:
code = RandCode.get_digit_characters_code_8()
await sync_to_async(default_cache.set)(f"register_{to_email}", code, timeout=600)
await aset_cache(f"register_{to_email}", code, timeout=600)
result = await sync_to_async(submit_task)(send_verification_email_task, to_email, code, 'register')
if result is None:
@@ -108,7 +109,7 @@ class SendUserEmailAPIView(APIView):
)
else:
code = RandCode.get_digit_characters_code_8()
await sync_to_async(default_cache.set)(f"login_{to_email}", code, timeout=600)
await aset_cache(f"login_{to_email}", code, timeout=600)
result = await sync_to_async(submit_task)(send_verification_email_task, to_email, code, 'login')
if result is None:
@@ -162,7 +163,7 @@ class UserLoginOrRegisterAPIView(APIView):
if user is None:
# Registration flow
vcode = await sync_to_async(default_cache.get)(f"register_{to_email}")
vcode = await aget_cache(f"register_{to_email}")
if vcode is None:
return create_standardized_error_response(
@@ -173,9 +174,9 @@ class UserLoginOrRegisterAPIView(APIView):
if code == vcode:
user_serializer = UserSerializer(data=request.data)
if await sync_to_async(user_serializer.is_valid)():
user = await sync_to_async(user_serializer.create_by_email)(request.data)
user = await user_serializer.acreate_by_email(request.data)
refresh = RefreshToken.for_user(user)
user_data = await sync_to_async(_serialize_user)(user)
user_data = await UserSerializer(user).adata
# Prepare response data
response_data = {
@@ -205,7 +206,7 @@ class UserLoginOrRegisterAPIView(APIView):
)
else:
# Login flow
vcode = await sync_to_async(default_cache.get)(f"login_{to_email}")
vcode = await aget_cache(f"login_{to_email}")
if vcode is None:
return create_standardized_error_response(
@@ -215,7 +216,7 @@ class UserLoginOrRegisterAPIView(APIView):
if code == vcode:
refresh = RefreshToken.for_user(user)
user_data = await sync_to_async(_serialize_user)(user)
user_data = await UserSerializer(user).adata
# Prepare response data
response_data = {
@@ -322,7 +323,7 @@ class ForgotPasswordSendCodeAPIView(APIView):
)
code = RandCode.get_digit_characters_code_8()
await sync_to_async(default_cache.set)(f"reset_password_{to_email}", code, timeout=600)
await aset_cache(f"reset_password_{to_email}", code, timeout=600)
result = await sync_to_async(submit_task)(send_reset_password_email_task, to_email, code)
if result is None:
@@ -433,7 +434,7 @@ class ForgotPasswordResetAPIView(APIView):
)
try:
vcode = await sync_to_async(default_cache.get)(f"reset_password_{to_email}")
vcode = await aget_cache(f"reset_password_{to_email}")
if vcode is None:
await sync_to_async(record_failure)(operation, to_email)
@@ -461,7 +462,7 @@ class ForgotPasswordResetAPIView(APIView):
await sync_to_async(user.set_password)(new_password)
await user.asave()
await sync_to_async(default_cache.delete)(f"reset_password_{to_email}")
await adelete_cache(f"reset_password_{to_email}")
await sync_to_async(reset_failures)(operation, to_email)
return create_standardized_response(
@@ -487,7 +488,7 @@ class UserUpdateAPIView(APIView):
)
async def get(self, request):
user = request.user
user_data = await sync_to_async(_serialize_user)(user)
user_data = await UserSerializer(user).adata
return create_standardized_response(
data={'user': user_data},
@@ -526,8 +527,8 @@ class UserUpdateAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
updated_user = await sync_to_async(serializer.save)()
user_data = await sync_to_async(_serialize_user)(updated_user)
updated_user = await serializer.asave()
user_data = await UserSerializer(updated_user).adata
try:
await sync_to_async(log_event)(
@@ -632,8 +633,8 @@ class ChangePasswordAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
user = await sync_to_async(serializer.save)()
user_data = await sync_to_async(_serialize_user)(user)
user = await serializer.asave()
user_data = await UserSerializer(user).adata
is_new_set = not request.user.has_usable_password() or request.data.get('old_password', '') == ''
@@ -682,7 +683,7 @@ class UserLoginAPIView(APIView):
if user is not None:
if user.is_active:
refresh = RefreshToken.for_user(user)
user_data = await sync_to_async(_serialize_user)(user)
user_data = await UserSerializer(user).adata
response_data = {
'user': user_data,
@@ -797,7 +798,7 @@ class LoginView(APIView):
if user.is_active:
await sync_to_async(reset_failures)(operation, identifier)
refresh = RefreshToken.for_user(user)
user_data = await sync_to_async(_serialize_user)(user)
user_data = await UserSerializer(user).adata
response_data = {
'user': user_data,
+1 -1
View File
@@ -81,7 +81,7 @@ class WalletTransactionsAPIView(APIView):
return create_standardized_response(
data={
'transactions': serializer.data,
'transactions': await serializer.adata,
'total': total,
'page': page,
'page_size': page_size,