You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DRF接口DELETE/UPDATE请求返回405 Method Not Allowed

问题现象

用Postman测试接口时,同路径下创建数据的POST请求可正常执行,但DELETE、UPDATE(PUT/PATCH)请求返回DELETE method not allowed报错,移除自定义权限配置后问题仍复现。

关联代码

模型定义

class Contributor(models.Model):
    """Model defining a contributor"""
    user = models.ForeignKey(to=User, on_delete=models.CASCADE, blank=True, null=True)
    project = models.ForeignKey(to=Project, related_name="project_contributor", on_delete=models.CASCADE, blank=True, null=True)
    permission = models.CharField(max_length=16, choices=[(permission.name, permission.value) for permission in enums.ProjectPermission], default="ALL")
    role = models.CharField(max_length=16, choices=[(role.name, role.value) for role in enums.ProjectRole], default="AUTHOR")
    def __str__(self) -> str:
        return f"Contributor: {str(self.user)}"

视图代码

class ContributorViewSet(viewsets.ModelViewSet):
    serializer_class = ContributorSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """return the contributors list of the project"""
        return Contributor.objects.filter(project=self.kwargs.get('project_pk'))

    def create(self, request, *args, **kwargs) -> Response:
        """
        Add contributor to a project
        If user added already in this project return 400
        """
        project = Project.objects.get(pk=self.kwargs.get('project_pk'))
        serializer = ContributorSerializer(data=request.data)
        user = request.data['user']

        if Contributor.objects.filter(user=user).filter(project=project).exists():
            return Response('This user is already a contributor of this project', status=status.HTTP_400_BAD_REQUEST)
        else:
            if serializer.is_valid():
                serializer.save(project=project)
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

路由配置

router = DefaultRouter()
router.register(r"projects", views.ProjectViewSet)

project_router = routers.NestedSimpleRouter(router, r"projects", lookup="project")
project_router.register(r"issues", views.IssueViewSet, basename="issues")
project_router.register(r'users', views.ContributorViewSet, basename='users')

issue_router = routers.NestedSimpleRouter(project_router, r"issues", lookup="issue")
issue_router.register(r"comments", views.CommentViewSet, basename="comments")


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
    path('', include(project_router.urls)),
    path('', include(issue_router.urls)),
    path('signup/', RegisterView.as_view(), name='signup'),
    path('login/', TokenObtainPairView.as_view(), name='obtain_tokens'),
    path('login/refresh/', TokenRefreshView.as_view(), name='refresh_token'),
]
问题根因

报错核心原因是请求URL与路由规则不匹配:
使用NestedSimpleRouter注册嵌套路由时,会自动生成两类路由规则:

  1. 列表路由:/projects/<project_pk>/users/,仅绑定GET(获取贡献者列表)、POST(新增贡献者)两个方法,也就是当前能正常调用的创建接口对应的路由
  2. 详情路由:/projects/<project_pk>/users/<pk>/,其中<pk>是Contributor模型的主键ID,只有这个路由才绑定GET(获取单个贡献者)、PUT/PATCH(更新贡献者)、DELETE(删除贡献者)方法

发起DELETE、UPDATE请求时,大概率只请求到了列表路由层级,没有拼接待操作的贡献者ID,列表路由本身没有注册对应请求方法,自然返回405 Method Not Allowed错误。

排查与解决方案
  • 首先核对请求URL格式:所有针对单个贡献者的更新、删除、详情查询操作,必须使用完整的详情路径,格式为/projects/<项目ID>/users/<贡献者ID>/,不能只写到/projects/<项目ID>/users/层级。
  • 确认请求方法与路径的对应关系:
    • POST /projects/<project_id>/users/:新增项目贡献者
    • GET /projects/<project_id>/users/:获取项目下所有贡献者列表
    • GET /projects/<project_id>/users/<contributor_id>/:查询单个贡献者详情
    • PUT/PATCH /projects/<project_id>/users/<contributor_id>/:更新指定贡献者的权限、角色等信息
    • DELETE /projects/<project_id>/users/<contributor_id>/:移除指定项目贡献者
  • 如果已经携带了贡献者ID仍报错,检查请求URL末尾的斜杠:Django默认开启APPEND_SLASH=True,Postman发请求时建议带上路径末尾的斜杠,避免PUT/DELETE请求被301重定向时丢失请求方法、请求体导致异常。
  • 额外优化点:当前重写的create方法直接用Project.objects.get()查询项目,传入不存在的项目ID时会直接抛出500错误,建议增加异常捕获,查询不到对应项目时直接返回404响应;同时给序列化器传入request上下文,方便后续扩展权限校验逻辑。

注:ContributorViewSet继承自ModelViewSet,本身已经内置了更新、删除、单条查询、列表查询的实现逻辑,只要路由匹配正确,不需要额外编写这些方法的代码即可正常响应。

内容的提问来源于stack exchange,提问作者papiex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:18:22