feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra
This commit is contained in:
+119
-92
@@ -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='置顶状态已更新'
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user