如何在Django REST Framework中将多API视图绑定到单个端点?
问题描述
我有一个名为Article的模型,以及多个针对该模型的API视图,这些视图按不同用途拆分(例如用ArticleUpdateAPI类处理UPDATE方法,ArticleDeleteAPI类处理DELETE方法等)。目前在urls.py中,它们对应不同的端点(如article/pk/update、/article/pk/delete等)。
我知道这种端点设计不是最佳实践,想把这些视图绑定到单个URL,用不同的类处理不同的HTTP方法。请问是否可行?具体该怎么实现?
现有代码如下:
ArticleAPI.py
class ArticlePostAPI(generics.CreateAPIView): serializer_class = ArticleSerializer permission_classes = [ permissions.IsAuthenticatedOrReadOnly ] def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response({ "comment": CommentSerializer.data }, status=201) class ArticleRetrieveAPI(generics.RetrieveAPIView): serializer_class = ArticleSerializer queryset = Article.objects.all() permission_classes = [ permissions.AllowAny ] class ArticleListAPI(generics.ListAPIView): serializer_class = ArticleSerializer queryset = Article.objects.order_by('number', 'headline') permission_classes = [ permissions.AllowAny ] class ArticleUpdateAPI(generics.UpdateAPIView): serializer_class = ArticleSerializer queryset = Article.objects.all() permission_classes = [ permissions.IsAuthenticated ] lookup_field = 'pk' def update(self, request, *args, **kwargs): instance = self.get_object() if request.user != instance.author: return Response({ "errors": "Logged in user and author must be same" }, status=403) serializer = self.get_serializer(instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) self.perform_update(serializer) return Response(serializer.data)
urls.py
urlpatterns = [ ... # Article API path('article/post/', ArticlePostAPI.as_view(), name='article_creation'), path('article/<int:pk>/', ArticleRetrieveAPI.as_view(), name='article_retrieve'), path('article/', ArticleListAPI.as_view(), name='article_list'), path('article/<int:pk>/update/', ArticleUpdateAPI.as_view(), name='article_update'), ]
解决方案
完全可行,这是RESTful API的标准设计思路——同一个URL根据不同HTTP方法执行不同操作。在Django REST Framework中有两种常用实现方式:
方式一:使用DRF内置组合视图类
DRF提供了预定义的组合视图类,比如ListCreateAPIView(处理GET列表、POST创建)、RetrieveUpdateDestroyAPIView(处理GET详情、PUT/PATCH更新、DELETE删除),可直接复用合并视图,减少重复代码。
修改后视图代码
from rest_framework import generics, permissions from rest_framework.response import Response class ArticleListCreateAPI(generics.ListCreateAPIView): serializer_class = ArticleSerializer permission_classes = [permissions.AllowAny] queryset = Article.objects.order_by('number', 'headline') # 保留原自定义POST逻辑 def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response({ "comment": CommentSerializer.data }, status=201) class ArticleRetrieveUpdateAPI(generics.RetrieveUpdateAPIView): serializer_class = ArticleSerializer queryset = Article.objects.all() lookup_field = 'pk' # 根据请求方法动态设置权限 def get_permissions(self): if self.request.method == 'GET': return [permissions.AllowAny()] return [permissions.IsAuthenticated()] # 保留原自定义更新权限校验逻辑 def update(self, request, *args, **kwargs): instance = self.get_object() if request.user != instance.author: return Response({ "errors": "Logged in user and author must be same" }, status=403) serializer = self.get_serializer(instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) self.perform_update(serializer) return Response(serializer.data)
修改后urls.py
urlpatterns = [ ... # Article API path('article/', ArticleListCreateAPI.as_view(), name='article_list_create'), path('article/<int:pk>/', ArticleRetrieveUpdateAPI.as_view(), name='article_retrieve_update'), ]
绑定后对应关系:
GET /article/→ 列表查询(原ArticleListAPI逻辑)POST /article/→ 创建文章(原ArticlePostAPI逻辑)GET /article/<pk>/→ 详情查询(原ArticleRetrieveAPI逻辑)PUT/PATCH /article/<pk>/→ 更新文章(原ArticleUpdateAPI逻辑)
方式二:自定义视图类继承APIView
如果需要更灵活的控制,可直接继承APIView,手动绑定不同HTTP方法的处理函数,将原多视图逻辑整合到单个类中。
示例代码
from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response class ArticleAPI(APIView): def get_permissions(self): if self.request.method in ['GET']: return [permissions.AllowAny()] elif self.request.method in ['POST', 'PUT', 'PATCH']: return [permissions.IsAuthenticated()] return super().get_permissions() # 处理GET请求:区分列表/详情 def get(self, request, pk=None): if pk: try: article = Article.objects.get(pk=pk) except Article.DoesNotExist: return Response({"error": "Article not found"}, status=status.HTTP_404_NOT_FOUND) serializer = ArticleSerializer(article) return Response(serializer.data) else: articles = Article.objects.order_by('number', 'headline') serializer = ArticleSerializer(articles, many=True) return Response(serializer.data) # 处理POST创建请求 def post(self, request): serializer = ArticleSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response({ "comment": CommentSerializer.data }, status=status.HTTP_201_CREATED) # 统一处理PUT/PATCH更新请求 def put(self, request, pk): return self.update(request, pk) def patch(self, request, pk): return self.update(request, pk, partial=True) def update(self, request, pk, partial=False): try: article = Article.objects.get(pk=pk) except Article.DoesNotExist: return Response({"error": "Article not found"}, status=status.HTTP_404_NOT_FOUND) if request.user != article.author: return Response({ "errors": "Logged in user and author must be same" }, status=status.HTTP_403_FORBIDDEN) serializer = ArticleSerializer(article, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data)
修改后urls.py
urlpatterns = [ ... # Article API path('article/', ArticleAPI.as_view(), name='article_list_create'), path('article/<int:pk>/', ArticleAPI.as_view(), name='article_detail_update'), ]
这种方式自由度更高,适合需要在单个类中统一处理多类逻辑的场景。
内容的提问来源于stack exchange,提问作者Sanyokk
相关产品推荐
相关产品推荐

