ViewSet gom logic CRUD vào một class, mỗi method ứng với một action (list, retrieve, create, update, partial_update, destroy). Router đọc viewset rồi sinh URL tự động kèm name tương ứng.
# viewsets.ModelViewSet đã wrap sẵn 6 method CRUD
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author')
serializer_class = PostSerializer
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
post = self.get_object()
post.publish()
return Response(PostSerializer(post).data)
router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
# Sinh ra:
# GET /posts/ → list
# POST /posts/ → create
# GET /posts/{pk}/ → retrieve
# PUT /posts/{pk}/ → update
# PATCH /posts/{pk}/ → partial_update
# DELETE /posts/{pk}/ → destroy
# POST /posts/{pk}/publish/ → custom actionKhi cần custom queryset riêng cho từng action (ví dụ list filter theo user, retrieve cho admin xem được hết), override get_queryset() rồi check self.action.
Và đừng nhồi 5 action không liên quan vào cùng 1 viewset — tách 2 viewset nhỏ luôn đọc rõ hơn.
ViewSet bundles CRUD logic into one class, each method maps to one action (list, retrieve, create, update, partial_update, destroy). Router reads the viewset and auto-generates URLs and matching names.
# viewsets.ModelViewSet already wraps the six CRUD methods
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author')
serializer_class = PostSerializer
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
post = self.get_object()
post.publish()
return Response(PostSerializer(post).data)
router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
# Generates:
# GET /posts/ → list
# POST /posts/ → create
# GET /posts/{pk}/ → retrieve
# PUT /posts/{pk}/ → update
# PATCH /posts/{pk}/ → partial_update
# DELETE /posts/{pk}/ → destroy
# POST /posts/{pk}/publish/ → custom actionPitfall: When you need per-action querysets (e.g. list filtered by user, retrieve open to admins), override get_queryset() and inspect self.action.
Do not jam five unrelated actions into one viewset — splitting into two small viewsets reads better.