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注册嵌套路由时,会自动生成两类路由规则:
- 列表路由:
/projects/<project_pk>/users/,仅绑定GET(获取贡献者列表)、POST(新增贡献者)两个方法,也就是当前能正常调用的创建接口对应的路由 - 详情路由:
/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
相关产品推荐
相关产品推荐

