DRF Views:APIView 與 ViewSet 完全解析 | Django 教學
在前面的章節中,我們學會了如何用 Serializer(序列化器) 定義 API 的資料格式。但光有序列化器還不夠 — 我們還需要一個元件來接收 HTTP 請求、呼叫序列化器處理資料、並回傳適當的回應。這個元件就是 DRF Views(視圖層)。Django REST Framework 提供了從底層的 @api_view 裝飾器、APIView 類別,到高度抽象的 ViewSet 與 ModelViewSet 的完整視圖階層,讓你可以根據需求選擇合適的抽象程度。本篇將完整解析這四種寫法的差異、適用場景,並透過同一個 Post API 的四種實作方式做對比。
DRF View 階層總覽
在深入各種 View 之前,先了解 DRF 的 View 繼承階層(View Hierarchy),有助於理解每一層抽象做了什麼事:
Django View
└── APIView ← DRF 基礎,處理認證/權限/Content Negotiation
├── GenericAPIView ← 加入 queryset、serializer_class、分頁等支援
│ └── ListAPIView / CreateAPIView / ...(Mixin 組合)
│
└── ViewSet ← 將相關 View 組合成一個類別
└── GenericViewSet
├── ReadOnlyModelViewSet (list + retrieve)
└── ModelViewSet (全部 CRUD)
| View 類型 | 功能 | 適用場景 |
|---|---|---|
@api_view | 函數式視圖(FBV)裝飾器 | 簡單端點、原型開發 |
APIView | 基礎類別式視圖(CBV) | 完全客製化的邏輯 |
ViewSet | 操作集合(搭配 Router) | 需要 Router 但有客製化需求 |
ModelViewSet | 完整 CRUD + Router | 標準資源 CRUD |
從上到下,抽象程度遞增、程式碼量遞減,但客製化彈性也隨之降低。接下來我們逐一介紹。
@api_view 裝飾器(函數式 API 視圖)
@api_view 是 DRF 提供的函數式視圖(Function-Based View, FBV)裝飾器,最適合簡單的 API 端點。它將普通的 Django View 函數包裝成 DRF 視圖,自動處理認證(Authentication)、權限(Permission)與內容協商(Content Negotiation):
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from .models import Post
from .serializers import PostSerializer
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def post_list(request):
"""
GET /api/posts/ → 取得文章列表
POST /api/posts/ → 建立新文章
"""
if request.method == 'GET':
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True,
context={'request': request})
return Response(serializer.data)
elif request.method == 'POST':
serializer = PostSerializer(data=request.data,
context={'request': request})
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view 接受一個 HTTP 方法列表作為參數(如 ['GET', 'POST']),限制該端點只允許指定的方法。未列出的方法會自動回傳 405 Method Not Allowed。
request.data 與 request.query_params
DRF 的 Request 物件與 Django 原生的 HttpRequest 不同,它提供了更強大的請求解析能力:
request.data:解析請求本體(Request Body)中的資料,支援 JSON、Form Data、Multipart 等格式,取代了 Django 原生的request.POST和request.FILES的組合request.query_params:取得 URL 查詢參數(Query Parameters),等同於request.GET,但名稱更語義化
@api_view(['GET'])
def post_search(request):
# 取得查詢參數:GET /api/posts/search/?keyword=django&page=2
keyword = request.query_params.get('keyword', '')
page = request.query_params.get('page', 1)
posts = Post.objects.filter(title__icontains=keyword)
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
APIView 類別(類別式 API 視圖)
APIView 是 DRF 中類別式視圖(Class-Based View, CBV)的基礎。它繼承自 Django 的 View,但額外整合了 DRF 的認證、權限、節流(Throttling)與內容協商機制。你可以透過定義 get()、post()、put()、patch()、delete() 等方法來處理對應的 HTTP 請求:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Post
from .serializers import PostSerializer
class PostListAPIView(APIView):
"""
GET /api/posts/ → 取得文章列表
POST /api/posts/ → 建立新文章
"""
def get(self, request):
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True,
context={'request': request})
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(data=request.data,
context={'request': request})
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class PostDetailAPIView(APIView):
"""
GET /api/posts/<pk>/ → 取得單篇文章
PUT /api/posts/<pk>/ → 完整更新文章
PATCH /api/posts/<pk>/ → 部分更新文章
DELETE /api/posts/<pk>/ → 刪除文章
"""
def get_object(self, pk):
try:
return Post.objects.get(pk=pk)
except Post.DoesNotExist:
return None
def get(self, request, pk):
post = self.get_object(pk)
if post is None:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post, context={'request': request})
return Response(serializer.data)
def put(self, request, pk):
post = self.get_object(pk)
if post is None:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post, data=request.data,
context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk):
post = self.get_object(pk)
if post is None:
return Response(status=status.HTTP_404_NOT_FOUND)
# partial=True 允許只傳部分欄位
serializer = PostSerializer(post, data=request.data,
partial=True,
context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
post = self.get_object(pk)
if post is None:
return Response(status=status.HTTP_404_NOT_FOUND)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Response 物件
DRF 的 Response 物件會根據客戶端的 Accept 標頭自動選擇輸出格式(JSON、Browsable API 等),這就是**內容協商(Content Negotiation)**的機制。你只需傳入 Python 資料結構,DRF 會自動處理序列化:
from rest_framework.response import Response
from rest_framework import status
# 回傳資料(預設 HTTP 200)
return Response(serializer.data)
# 回傳資料並指定狀態碼
return Response(serializer.data, status=status.HTTP_201_CREATED)
# 回傳錯誤訊息
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 只回傳狀態碼(無回應本體)
return Response(status=status.HTTP_204_NO_CONTENT)
ViewSet 概念
ViewSet 是 DRF 的核心設計理念之一:將對同一個資源的所有操作(列表、建立、讀取、更新、刪除)集中在一個類別中。ViewSet 不直接定義 get()、post() 等方法,而是使用語義化的 action(動作) 名稱:
| Action 名稱 | 對應 HTTP 方法 | 說明 |
|---|---|---|
list | GET(集合) | 取得資源列表 |
create | POST | 建立新資源 |
retrieve | GET(單一) | 取得單一資源 |
update | PUT | 完整更新資源 |
partial_update | PATCH | 部分更新資源 |
destroy | DELETE | 刪除資源 |
ViewSet vs APIView 比較
| 比較項目 | APIView | ViewSet |
|---|---|---|
| 方法命名 | get(), post(), put() 等 | list(), create(), retrieve() 等 |
| URL 配置 | 手動在 urls.py 定義 | 搭配 Router 自動產生 |
| 類別數量 | 通常需要兩個類別(List + Detail) | 一個類別處理所有操作 |
| 適用場景 | 高度客製化端點 | 標準 CRUD 資源 |
| 可擴充性 | 透過方法覆寫 | 透過 @action 新增自訂動作 |
使用基礎的 ViewSet,你需要手動實作每個 action:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ViewSet):
"""
手動實作所有 action 的 ViewSet
"""
def list(self, request):
"""GET /api/posts/"""
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True,
context={'request': request})
return Response(serializer.data)
def create(self, request):
"""POST /api/posts/"""
serializer = PostSerializer(data=request.data,
context={'request': request})
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, pk=None):
"""GET /api/posts/<pk>/"""
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post, context={'request': request})
return Response(serializer.data)
def update(self, request, pk=None):
"""PUT /api/posts/<pk>/"""
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post, data=request.data,
context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk=None):
"""DELETE /api/posts/<pk>/"""
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
ModelViewSet:自動提供完整 CRUD
如果每次都要手動寫 list()、create()、retrieve() 等方法,那 ViewSet 就沒什麼優勢了。ModelViewSet 的強大之處在於:只要指定 queryset 和 serializer_class,它就會自動提供完整的 CRUD 操作:
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
class PostModelViewSet(viewsets.ModelViewSet):
"""
只需兩行設定,自動提供 list / create / retrieve / update / partial_update / destroy
"""
queryset = Post.objects.select_related('author').order_by('-created_at')
serializer_class = PostSerializer
沒錯,就這麼簡單。這三行程式碼等同於前面 ViewSet 範例中數十行的手動實作。
覆寫方法(Hooks)
雖然 ModelViewSet 自動處理了大部分邏輯,但你仍然可以透過覆寫特定方法來加入自訂行為:
perform_create() — 在建立資源時注入額外邏輯:
class PostModelViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author').order_by('-created_at')
serializer_class = PostSerializer
def perform_create(self, serializer):
"""建立文章時自動將當前使用者設為作者"""
serializer.save(author=self.request.user)
perform_update() — 在更新資源時執行額外邏輯:
def perform_update(self, serializer):
"""更新時檢查發布狀態變更,若從草稿變為發布則發送通知"""
old_published = serializer.instance.is_published
post = serializer.save()
if not old_published and post.is_published:
notify_subscribers.delay(post.id) # 非同步通知
get_queryset() — 根據請求動態過濾查詢集:
def get_queryset(self):
"""管理員可看全部,一般使用者只看自己的 + 已發布的"""
user = self.request.user
if user.is_staff:
return Post.objects.all()
from django.db.models import Q
return Post.objects.filter(
Q(author=user) | Q(is_published=True)
)
get_serializer_class() — 根據 action 選擇不同的序列化器:
def get_serializer_class(self):
"""列表用輕量版序列化器,詳情用完整版"""
if self.action == 'list':
return PostListSerializer
return PostDetailSerializer
@action 裝飾器(自訂動作)
標準的 CRUD 操作不一定能滿足所有需求。例如「發布文章」或「取得熱門文章」這類自訂功能,可以使用 @action 裝飾器為 ViewSet 新增額外的端點。
detail=True vs detail=False
@action 最關鍵的參數是 detail:
detail=True:動作針對單一物件,URL 包含主鍵(如/api/posts/1/publish/)detail=False:動作針對資源集合,URL 不含主鍵(如/api/posts/trending/)
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
class PostModelViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author').order_by('-created_at')
serializer_class = PostSerializer
@action(detail=True, methods=['post'], url_path='publish')
def publish(self, request, pk=None):
"""
POST /api/posts/{pk}/publish/
將指定文章設為已發布
"""
post = self.get_object() # 自動處理 404
if post.author != request.user:
return Response(
{'error': '只有作者可以發布文章'},
status=status.HTTP_403_FORBIDDEN,
)
post.is_published = True
post.save()
serializer = self.get_serializer(post)
return Response(serializer.data)
@action(detail=False, methods=['get'], url_path='trending')
def trending(self, request):
"""
GET /api/posts/trending/
取得熱門文章 Top 10
"""
trending_posts = self.get_queryset().filter(
is_published=True,
).order_by('-view_count')[:10]
serializer = self.get_serializer(trending_posts, many=True)
return Response(serializer.data)
自訂 URL name 與 methods
@action 還支援更細緻的配置:
@action(
detail=True,
methods=['post', 'delete'], # 允許多個 HTTP 方法
url_path='bookmark', # 自訂 URL 路徑片段
url_name='bookmark', # 自訂 URL name(用於 reverse())
)
def bookmark(self, request, pk=None):
"""
POST /api/posts/{pk}/bookmark/ → 加入書籤
DELETE /api/posts/{pk}/bookmark/ → 移除書籤
"""
post = self.get_object()
if request.method == 'POST':
request.user.bookmarks.add(post)
return Response({'status': '已加入書籤'})
elif request.method == 'DELETE':
request.user.bookmarks.remove(post)
return Response({'status': '已移除書籤'})
搭配 Router 後,@action 產生的 URL name 格式為 {basename}-{url_name},例如上面的 bookmark 動作的 URL name 為 post-bookmark。
ReadOnlyModelViewSet
如果你的 API 只需要提供讀取操作(不允許建立、更新、刪除),可以使用 ReadOnlyModelViewSet。它只包含 list() 和 retrieve() 兩個 action:
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
class PostReadOnlyViewSet(viewsets.ReadOnlyModelViewSet):
"""
只提供 GET /api/posts/(列表)和 GET /api/posts/<pk>/(詳情)
不提供 POST、PUT、PATCH、DELETE
"""
queryset = Post.objects.filter(is_published=True).order_by('-created_at')
serializer_class = PostSerializer
這在公開 API(如文章列表、分類查詢)中非常實用,既方便又安全。
Router 配置
ViewSet 通常搭配 Router(路由器) 使用,自動產生 URL 配置:
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostModelViewSet
router = DefaultRouter()
router.register('posts', PostModelViewSet, basename='post')
urlpatterns = [
path('api/', include(router.urls)),
]
# DefaultRouter 自動產生的 URL:
# GET /api/posts/ → list
# POST /api/posts/ → create
# GET /api/posts/{pk}/ → retrieve
# PUT /api/posts/{pk}/ → update
# PATCH /api/posts/{pk}/ → partial_update
# DELETE /api/posts/{pk}/ → destroy
# POST /api/posts/{pk}/publish/ → publish(@action)
# GET /api/posts/trending/ → trending(@action)
完整範例:Post API 的四種實作方式對比
為了讓你直觀感受不同抽象層級的差異,以下用同一個 Post 資源的 list(列表)和 create(建立)操作做四種實作方式的對比。
方式一:@api_view(FBV)
# views.py
@api_view(['GET', 'POST'])
def post_list(request):
if request.method == 'GET':
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# urls.py
urlpatterns = [
path('api/posts/', post_list),
]
方式二:APIView(CBV)
# views.py
class PostListAPIView(APIView):
def get(self, request):
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# urls.py
urlpatterns = [
path('api/posts/', PostListAPIView.as_view()),
]
方式三:ViewSet
# views.py
class PostViewSet(viewsets.ViewSet):
def list(self, request):
posts = Post.objects.filter(is_published=True)
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
def create(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# urls.py
router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
urlpatterns = [path('api/', include(router.urls))]
方式四:ModelViewSet
# views.py
class PostModelViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(is_published=True)
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# urls.py
router = DefaultRouter()
router.register('posts', PostModelViewSet, basename='post')
urlpatterns = [path('api/', include(router.urls))]
四種方式比較表
| 比較項目 | @api_view | APIView | ViewSet | ModelViewSet |
|---|---|---|---|---|
| 風格 | FBV | CBV | CBV | CBV |
| 程式碼量 | 中 | 中 | 中 | 極少 |
| URL 配置 | 手動 | 手動 | Router | Router |
| CRUD 自動化 | 無 | 無 | 無 | 完整 |
| 客製化彈性 | 高 | 高 | 中 | 中(透過覆寫) |
| 適用場景 | 簡單/一次性端點 | 客製化邏輯 | 需要 Router | 標準 CRUD |
總結
DRF 的 View 層提供了從底層到高層的完整抽象階梯,讓你可以根據實際需求選擇合適的工具。@api_view 裝飾器適合快速建立簡單的函數式端點;APIView 類別給你對每個 HTTP 方法的完全控制權;ViewSet 將同一資源的操作集中管理並搭配 Router 自動產生 URL;ModelViewSet 則是效率之王,只需指定 queryset 和 serializer_class 就能獲得完整的 CRUD 功能。透過 @action 裝飾器,你還可以為 ViewSet 新增任意自訂端點,而 ReadOnlyModelViewSet 則是公開唯讀 API 的最佳選擇。
在實際專案中,建議的策略是:從 ModelViewSet 開始,如果它能滿足需求就直接使用;遇到需要高度客製化的端點再降級到 APIView 或 @api_view。善用 perform_create()、get_queryset()、get_serializer_class() 等覆寫方法,可以在享受 ModelViewSet 便利性的同時保有足夠的客製化空間。