631 lines
26 KiB
Python
631 lines
26 KiB
Python
from rest_framework import generics, 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
|
||
from drf_yasg import openapi
|
||
|
||
from utils.response_codes import ResponseCode, create_standardized_response, create_standardized_error_response
|
||
from message.utils import create_message
|
||
from chunyu_project.common_schemas import success_response, error_response, unauthorized_response, not_found_response
|
||
from user.models import FUser
|
||
from user.utils import track_task
|
||
from .models import Article, ArticleComment, ArticleLike, ArticleFavorite, ArticleCommentLike
|
||
from .serializers import (
|
||
ArticleListSerializer, ArticleDetailSerializer,
|
||
ArticleCreateUpdateSerializer, ArticleCommentSerializer,
|
||
ArticleManageSerializer,
|
||
)
|
||
|
||
|
||
class ArticlePagination(PageNumberPagination):
|
||
page_size = 10
|
||
page_size_query_param = 'page_size'
|
||
max_page_size = 100
|
||
|
||
def get_paginated_response(self, data):
|
||
return Response({
|
||
'code': 10000,
|
||
'message': 'Success',
|
||
'data': {
|
||
'count': self.page.paginator.count,
|
||
'next': self.get_next_link(),
|
||
'previous': self.get_previous_link(),
|
||
'results': data,
|
||
}
|
||
})
|
||
|
||
|
||
class ArticleListCreateView(generics.ListCreateAPIView):
|
||
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
||
pagination_class = ArticlePagination
|
||
|
||
def get_permissions(self):
|
||
if self.request.method == 'GET':
|
||
return [AllowAny()]
|
||
return [IsAuthenticated()]
|
||
|
||
def get_serializer_class(self):
|
||
if self.request.method == 'POST':
|
||
return ArticleCreateUpdateSerializer
|
||
return ArticleListSerializer
|
||
|
||
def get_queryset(self):
|
||
qs = Article.objects.filter(status='published')
|
||
category = self.request.query_params.get('category')
|
||
if category and category != 'all':
|
||
qs = qs.filter(category=category)
|
||
|
||
tag = self.request.query_params.get('tag')
|
||
if tag:
|
||
qs = qs.filter(tags__contains=[tag])
|
||
|
||
search = self.request.query_params.get('search')
|
||
if search:
|
||
from django.db.models import Q
|
||
qs = qs.filter(
|
||
Q(title__icontains=search) |
|
||
Q(excerpt__icontains=search) |
|
||
Q(tags__overlap=[search])
|
||
)
|
||
|
||
ordering = self.request.query_params.get('ordering', '-created_at')
|
||
return qs.order_by('-is_top', ordering)
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='获取文章列表',
|
||
operation_description='分页获取已发布文章列表,支持按分类和排序筛选,登录用户可查看收藏状态',
|
||
manual_parameters=[
|
||
openapi.Parameter('category', openapi.IN_QUERY, description='文章分类筛选', type=openapi.TYPE_STRING),
|
||
openapi.Parameter('tag', openapi.IN_QUERY, description='文章标签筛选', type=openapi.TYPE_STRING),
|
||
openapi.Parameter('search', openapi.IN_QUERY, description='搜索关键词(标题、摘要、标签)', type=openapi.TYPE_STRING),
|
||
openapi.Parameter('ordering', openapi.IN_QUERY, description='排序方式,默认 -created_at', type=openapi.TYPE_STRING),
|
||
openapi.Parameter('page', openapi.IN_QUERY, description='页码', type=openapi.TYPE_INTEGER),
|
||
openapi.Parameter('page_size', openapi.IN_QUERY, description='每页数量,默认10,最大100', type=openapi.TYPE_INTEGER),
|
||
],
|
||
responses={200: success_response}
|
||
)
|
||
def list(self, request, *args, **kwargs):
|
||
queryset = self.filter_queryset(self.get_queryset())
|
||
page = self.paginate_queryset(queryset)
|
||
if page is not None:
|
||
serializer = self.get_serializer(page, many=True)
|
||
data = serializer.data
|
||
if request.user.is_authenticated:
|
||
from .models import ArticleFavorite
|
||
fav_ids = set(
|
||
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:
|
||
for item in data:
|
||
item['is_favorited'] = False
|
||
return self.get_paginated_response(data)
|
||
serializer = self.get_serializer(queryset, many=True)
|
||
data = serializer.data
|
||
if request.user.is_authenticated:
|
||
from .models import ArticleFavorite
|
||
fav_ids = set(
|
||
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:
|
||
for item in data:
|
||
item['is_favorited'] = False
|
||
return Response({'code': 10000, 'message': 'Success', 'data': data})
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='创建文章',
|
||
operation_description='创建新文章,需要登录',
|
||
request_body=ArticleCreateUpdateSerializer,
|
||
responses={
|
||
201: success_response,
|
||
400: error_response,
|
||
401: unauthorized_response,
|
||
}
|
||
)
|
||
def create(self, request, *args, **kwargs):
|
||
serializer = self.get_serializer(data=request.data)
|
||
if serializer.is_valid():
|
||
serializer.save()
|
||
track_task(request.user, 'post')
|
||
return create_standardized_response(
|
||
data=serializer.data,
|
||
code=ResponseCode.SUCCESS,
|
||
message='文章创建成功',
|
||
status_code=status.HTTP_201_CREATED
|
||
)
|
||
return create_standardized_error_response(
|
||
data=serializer.errors,
|
||
code=ResponseCode.VALIDATION_ERROR,
|
||
message='提交失败',
|
||
status_code=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
|
||
class ArticleDetailView(APIView):
|
||
parser_classes = [JSONParser, MultiPartParser, FormParser]
|
||
permission_classes = [IsAuthenticatedOrReadOnly]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='获取文章详情',
|
||
operation_description='获取指定文章的详细信息,同时增加浏览量,登录用户可查看收藏状态',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
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()
|
||
serializer = ArticleDetailSerializer(article, context={'request': request})
|
||
data = serializer.data
|
||
if request.user.is_authenticated:
|
||
data['is_favorited'] = ArticleFavorite.objects.filter(
|
||
user=request.user, article=article
|
||
).exists()
|
||
else:
|
||
data['is_favorited'] = False
|
||
data['favorites_count'] = ArticleFavorite.objects.filter(article=article).count()
|
||
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='更新文章',
|
||
operation_description='更新指定文章,仅文章作者可操作',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
request_body=ArticleCreateUpdateSerializer,
|
||
responses={
|
||
200: success_response,
|
||
400: error_response,
|
||
401: unauthorized_response,
|
||
403: error_response,
|
||
404: not_found_response,
|
||
}
|
||
)
|
||
def put(self, request, pk):
|
||
article = get_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
|
||
)
|
||
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='文章更新成功')
|
||
return create_standardized_error_response(
|
||
data=serializer.errors,
|
||
code=ResponseCode.VALIDATION_ERROR,
|
||
status_code=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='删除文章',
|
||
operation_description='删除指定文章,仅文章作者可操作',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
204: success_response,
|
||
401: unauthorized_response,
|
||
403: error_response,
|
||
404: not_found_response,
|
||
}
|
||
)
|
||
def delete(self, request, pk):
|
||
article = get_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()
|
||
return create_standardized_response(code=ResponseCode.SUCCESS, message='文章删除成功', status_code=status.HTTP_204_NO_CONTENT)
|
||
|
||
|
||
class ArticleCommentListCreateView(generics.ListCreateAPIView):
|
||
parser_classes = [JSONParser, MultiPartParser]
|
||
pagination_class = ArticlePagination
|
||
|
||
def get_permissions(self):
|
||
if self.request.method == 'GET':
|
||
return [AllowAny()]
|
||
return [IsAuthenticated()]
|
||
|
||
serializer_class = ArticleCommentSerializer
|
||
|
||
def get_queryset(self):
|
||
article_id = self.kwargs['article_id']
|
||
return ArticleComment.objects.filter(article_id=article_id, parent__isnull=True)
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='获取文章评论列表',
|
||
operation_description='获取指定文章的顶级评论列表,包含子评论',
|
||
manual_parameters=[
|
||
openapi.Parameter('article_id', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
openapi.Parameter('page', openapi.IN_QUERY, description='页码', type=openapi.TYPE_INTEGER),
|
||
openapi.Parameter('page_size', openapi.IN_QUERY, description='每页数量,默认10', type=openapi.TYPE_INTEGER),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
404: not_found_response,
|
||
}
|
||
)
|
||
def list(self, request, *args, **kwargs):
|
||
queryset = self.get_queryset()
|
||
page = self.paginate_queryset(queryset)
|
||
if page is not None:
|
||
serializer = self.get_serializer(page, many=True)
|
||
return self.get_paginated_response(serializer.data)
|
||
serializer = self.get_serializer(queryset, many=True)
|
||
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='发表文章评论',
|
||
operation_description='对指定文章发表评论,支持回复其他评论,需要登录',
|
||
manual_parameters=[
|
||
openapi.Parameter('article_id', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
request_body=ArticleCommentSerializer,
|
||
responses={
|
||
201: success_response,
|
||
400: error_response,
|
||
401: unauthorized_response,
|
||
404: not_found_response,
|
||
}
|
||
)
|
||
def create(self, request, *args, **kwargs):
|
||
article_id = self.kwargs['article_id']
|
||
article = get_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
|
||
|
||
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
|
||
))
|
||
|
||
track_task(request.user, 'post')
|
||
|
||
return create_standardized_response(
|
||
data=serializer.data,
|
||
code=ResponseCode.SUCCESS,
|
||
message='评论成功',
|
||
status_code=status.HTTP_201_CREATED
|
||
)
|
||
return create_standardized_error_response(
|
||
data=serializer.errors,
|
||
code=ResponseCode.VALIDATION_ERROR,
|
||
status_code=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
def _send_notification_messages(self, article_id, article_title, article_author_id,
|
||
comment_id, comment_content, parent_comment_id,
|
||
parent_comment_user_id, user_id, user_nickname):
|
||
"""异步发送通知消息"""
|
||
import re as re_module
|
||
from user.models import FUser
|
||
|
||
try:
|
||
sender_user = FUser.objects.get(id=user_id)
|
||
except FUser.DoesNotExist:
|
||
return
|
||
|
||
if parent_comment_id and parent_comment_user_id and parent_comment_user_id != user_id:
|
||
try:
|
||
parent_user = FUser.objects.get(id=parent_comment_user_id)
|
||
create_message(
|
||
recipient=parent_user,
|
||
sender=sender_user,
|
||
msg_type='reply',
|
||
title=f'{user_nickname} 回复了你的评论',
|
||
content=comment_content,
|
||
extra_info=f'在《{article_title}》文章下',
|
||
target_link=f'/article/{article_id}#comment-{comment_id}',
|
||
target_type='comment',
|
||
target_id=comment_id,
|
||
)
|
||
except FUser.DoesNotExist:
|
||
pass
|
||
|
||
if article_author_id != user_id and (not parent_comment_id or parent_comment_user_id != article_author_id):
|
||
try:
|
||
article_author = FUser.objects.get(id=article_author_id)
|
||
create_message(
|
||
recipient=article_author,
|
||
sender=sender_user,
|
||
msg_type='reply',
|
||
title=f'{user_nickname} 评论了你的文章',
|
||
content=comment_content,
|
||
extra_info=f'在《{article_title}》文章下',
|
||
target_link=f'/article/{article_id}#comment-{comment_id}',
|
||
target_type='comment',
|
||
target_id=comment_id,
|
||
)
|
||
except FUser.DoesNotExist:
|
||
pass
|
||
|
||
at_matches = re_module.findall(r'@(\w+)', comment_content)
|
||
if at_matches:
|
||
mentioned_users = FUser.objects.filter(username__in=at_matches).exclude(id=user_id)
|
||
for mentioned_user in mentioned_users:
|
||
if mentioned_user.id != article_author_id:
|
||
create_message(
|
||
recipient=mentioned_user,
|
||
sender=sender_user,
|
||
msg_type='at_me',
|
||
title=f'{user_nickname} @了你',
|
||
content=comment_content,
|
||
extra_info=f'在《{article_title}》文章下',
|
||
target_link=f'/article/{article_id}#comment-{comment_id}',
|
||
target_type='comment',
|
||
target_id=comment_id,
|
||
)
|
||
|
||
|
||
class ArticleLikeToggleView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='点赞/取消点赞文章',
|
||
operation_description='切换文章点赞状态,已点赞则取消,未点赞则点赞,需要登录',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
401: unauthorized_response,
|
||
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)
|
||
if not created:
|
||
like.delete()
|
||
Article.objects.filter(pk=pk).update(likes=F("likes") - 1)
|
||
article.refresh_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()
|
||
if created and article.author != request.user:
|
||
create_message(
|
||
recipient=article.author,
|
||
sender=request.user,
|
||
msg_type='like',
|
||
title=f'{request.user.nickname or request.user.username} 赞了你的文章',
|
||
content=f'《{article.title}》',
|
||
extra_info='',
|
||
target_link=f'/article/{pk}',
|
||
target_type='article',
|
||
target_id=pk,
|
||
)
|
||
return create_standardized_response(
|
||
data={'liked': True, 'likes_count': article.likes},
|
||
code=ResponseCode.SUCCESS
|
||
)
|
||
|
||
|
||
class ArticleFavoriteToggleView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='收藏/取消收藏文章',
|
||
operation_description='切换文章收藏状态,已收藏则取消,未收藏则收藏,需要登录',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
401: unauthorized_response,
|
||
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)
|
||
if not created:
|
||
fav.delete()
|
||
count = article.article_favorites.count()
|
||
return create_standardized_response(
|
||
data={'favorited': False, 'favorites_count': count},
|
||
code=ResponseCode.SUCCESS
|
||
)
|
||
count = article.article_favorites.count()
|
||
return create_standardized_response(
|
||
data={'favorited': True, 'favorites_count': count},
|
||
code=ResponseCode.SUCCESS
|
||
)
|
||
|
||
|
||
class ArticleCommentLikeToggleView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='点赞/取消点赞评论',
|
||
operation_description='切换文章评论点赞状态,已点赞则取消,未点赞则点赞,需要登录',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='评论ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
401: unauthorized_response,
|
||
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)
|
||
if not created:
|
||
like.delete()
|
||
ArticleComment.objects.filter(pk=pk).update(likes=F("likes") - 1)
|
||
comment.refresh_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'])
|
||
return create_standardized_response(
|
||
data={'liked': True, 'likes_count': comment.likes},
|
||
code=ResponseCode.SUCCESS
|
||
)
|
||
|
||
|
||
class MyArticleListView(generics.ListAPIView):
|
||
permission_classes = [IsAuthenticated]
|
||
serializer_class = ArticleManageSerializer
|
||
parser_classes = [JSONParser]
|
||
|
||
def get_queryset(self):
|
||
qs = Article.objects.filter(author=self.request.user)
|
||
st = self.request.query_params.get('status')
|
||
if st:
|
||
qs = qs.filter(status=st)
|
||
return qs.order_by('-updated_at')
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='获取我的文章列表',
|
||
operation_description='获取当前登录用户的所有文章,支持按状态筛选',
|
||
manual_parameters=[
|
||
openapi.Parameter('status', openapi.IN_QUERY, description='文章状态筛选(published/draft)', type=openapi.TYPE_STRING),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
401: unauthorized_response,
|
||
}
|
||
)
|
||
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)
|
||
|
||
|
||
class MyArticleBatchView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
parser_classes = [JSONParser]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='批量操作文章',
|
||
operation_description='批量发布、转为草稿或删除文章,需要登录',
|
||
request_body=openapi.Schema(
|
||
type=openapi.TYPE_OBJECT,
|
||
required=['ids', 'action'],
|
||
properties={
|
||
'ids': openapi.Schema(
|
||
type=openapi.TYPE_ARRAY,
|
||
items=openapi.Schema(type=openapi.TYPE_INTEGER),
|
||
description='文章ID列表',
|
||
),
|
||
'action': openapi.Schema(
|
||
type=openapi.TYPE_STRING,
|
||
enum=['publish', 'draft', 'delete'],
|
||
description='操作类型:publish-发布,draft-转草稿,delete-删除',
|
||
),
|
||
},
|
||
),
|
||
responses={
|
||
200: success_response,
|
||
400: error_response,
|
||
401: unauthorized_response,
|
||
}
|
||
)
|
||
def post(self, request):
|
||
ids = request.data.get('ids', [])
|
||
action = request.data.get('action', '')
|
||
if not ids or action not in ('publish', 'draft', 'delete'):
|
||
return create_standardized_error_response(
|
||
code=ResponseCode.VALIDATION_ERROR,
|
||
message='参数错误',
|
||
status_code=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
qs = Article.objects.filter(id__in=ids, author=request.user)
|
||
if action == 'delete':
|
||
count = qs.delete()[0]
|
||
elif action == 'publish':
|
||
count = qs.update(status='published')
|
||
elif action == 'draft':
|
||
count = qs.update(status='draft')
|
||
return create_standardized_response(
|
||
data={'affected': count},
|
||
code=ResponseCode.SUCCESS,
|
||
message=f'批量操作成功,影响 {count} 篇文章'
|
||
)
|
||
|
||
|
||
class ArticleToggleTopView(APIView):
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
@swagger_auto_schema(
|
||
tags=['文章'],
|
||
operation_summary='切换文章置顶状态',
|
||
operation_description='切换文章的置顶状态,仅文章作者可操作,需要登录',
|
||
manual_parameters=[
|
||
openapi.Parameter('pk', openapi.IN_PATH, description='文章ID', type=openapi.TYPE_INTEGER, required=True),
|
||
],
|
||
responses={
|
||
200: success_response,
|
||
401: unauthorized_response,
|
||
403: error_response,
|
||
404: not_found_response,
|
||
}
|
||
)
|
||
def post(self, request, pk):
|
||
article = get_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.is_top = not article.is_top
|
||
article.save(update_fields=['is_top'])
|
||
return create_standardized_response(
|
||
data={'is_top': article.is_top},
|
||
code=ResponseCode.SUCCESS,
|
||
message='置顶状态已更新'
|
||
) |