DRF中@action装饰接口提示GET方法不允许且多方法失效问题
DRF ViewSet中@action路由冲突导致请求方法失效的解决
问题现象
在基于Django REST Framework的项目中,通过ArticleAPI视图集的@action装饰器定义了多个处理Comment模型的接口,但测试时所有请求都返回{"detail": "Method \"GET\" not allowed."},POST、PATCH、DELETE等方法也无法正常工作。查看生成的路由发现,所有评论相关接口都指向同一个URL:
<URLPattern '^articles/(?P<pk>[^/.]+)/comments/$' [name='articles-article-create-comment']>, <URLPattern '^articles/(?P<pk>[^/.]+)/comments/$' [name='articles-article-update-comment']>, <URLPattern '^articles/(?P<pk>[^/.]+)/comments/$' [name='articles-article-comments-object']>, <URLPattern '^articles/(?P<pk>[^/.]+)/comments/$' [name='articles-article-comments-list']>, <URLPattern '^articles/(?P<pk>[^/.]+)/comments/$' [name='articles-article-update-comment']>
核心原因
- 路由URL重复:所有
@action的url_path都设置为'comments',DRF在注册路由时会覆盖之前的同名URL,最终只有最后一个注册的路由生效,导致其他方法无法匹配。 - 单个评论接口缺少参数:获取单个评论的方法未在URL中定义评论的
pk参数,无法定位到具体评论实例。 - 视图方法逻辑错误:创建、更新、删除评论的方法中,错误调用了视图集针对Article的
create/partial_update/perform_destroy方法,而非操作Comment实例。
解决方案
1. 调整路由结构,区分列表与单个评论接口
修改@action的url_path,为单个评论接口添加评论pk的URL参数:
- 评论列表(GET/POST):保持
url_path='comments' - 单个评论(GET/PATCH/DELETE):设置
url_path='comments/(?P<comment_pk>[^/.]+)'
2. 修正视图方法逻辑
以下是修正后的ArticleAPI关键代码:
class ArticleAPI(viewsets.ModelViewSet): serializer_class = ArticleSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly] queryset = Article.objects.order_by('number') lookup_field = 'pk' # 获取文章的所有评论(列表) @action(methods=['GET'], detail=True, url_path='comments', url_name='article-comments-list') def get_comments_list(self, request, pk=None): article = self.get_object() serializer = CommentSerializer(article.comment_set.all(), many=True) return Response(serializer.data) # 获取单条评论 @action(methods=['GET'], detail=True, url_path='comments/(?P<comment_pk>[^/.]+)', url_name='article-comment-detail') def get_comment_detail(self, request, pk=None, comment_pk=None): article = self.get_object() comment = article.comment_set.get(pk=comment_pk) serializer = CommentSerializer(comment) return Response(serializer.data) # 创建评论 @action(methods=['POST'], detail=True, url_path='comments', url_name='article-create-comment') def create_comment(self, request, pk=None): article = self.get_object() serializer = CommentSerializer(data=request.data) serializer.is_valid(raise_exception=True) # 关联当前文章和登录用户 serializer.save(article=article, author=request.user) return Response({"comment": serializer.data}, status=201) # 更新评论 @action(methods=['PATCH'], detail=True, url_path='comments/(?P<comment_pk>[^/.]+)', url_name='article-update-comment') def partial_update_comment(self, request, pk=None, comment_pk=None): article = self.get_object() comment = article.comment_set.get(pk=comment_pk) # 检查权限(确保是评论作者或管理员) if not (request.user == comment.author or request.user.is_staff): return Response({"errors": "无权限修改此评论"}, status=403) serializer = CommentSerializer(comment, data=request.data, partial=True) serializer.is_valid(raise_exception=True) serializer.save() return Response({"comment": serializer.data}) # 删除评论 @action(methods=['DELETE'], detail=True, url_path='comments/(?P<comment_pk>[^/.]+)', url_name='article-delete-comment') def delete_comment(self, request, pk=None, comment_pk=None): article = self.get_object() comment = article.comment_set.get(pk=comment_pk) if not (request.user == comment.author or request.user.is_staff): return Response({"errors": "无权限删除此评论"}, status=403) comment.delete() return Response(status=204)
3. 验证路由
修正后生成的路由会区分不同的URL:
- 评论列表:
^articles/(?P<pk>[^/.]+)/comments/$(支持GET/POST) - 单个评论:
^articles/(?P<pk>[^/.]+)/comments/(?P<comment_pk>[^/.]+)/$(支持GET/PATCH/DELETE)
此时再通过Postman测试各方法,即可正常响应。
内容的提问来源于stack exchange,提问作者Sanyokk
相关产品推荐
相关产品推荐

