Django REST Framework — 构建生产级 RESTful API
目标
使用 Django REST Framework (DRF) 构建完整的 RESTful API,实现对"文章"资源的 CRUD:
- 理解 Model → Serializer → ViewSet → Router 的 DRF 管线
- 掌握序列化器(Serializer)的验证与输出控制
- 掌握 ViewSet 与 GenericAPIView 的使用
- 实现分页、过滤、搜索、排序
完整代码
步骤 1:安装 DRF
pip install djangorestframework django-filter
步骤 2:配置
# mysite/settings.py
INSTALLED_APPS = [
# ... Django 默认应用
"rest_framework",
"django_filters",
"blog", # 我们的应用
]
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
],
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
}
步骤 3:定义模型
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""文章分类"""
name = models.CharField("分类名", max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.name
class Post(models.Model):
"""博客文章"""
STATUS_CHOICES = [
("draft", "草稿"),
("published", "已发布"),
("archived", "已归档"),
]
title = models.CharField("标题", max_length=200)
slug = models.SlugField(max_length=200, unique=True)
content = models.TextField("内容")
status = models.CharField("状态", max_length=20, choices=STATUS_CHOICES, default="draft")
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name="posts")
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
views_count = models.IntegerField("浏览量", default=0)
created_at = models.DateTimeField("创建时间", auto_now_add=True)
updated_at = models.DateTimeField("更新时间", auto_now=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return self.title
步骤 4:编写序列化器
# blog/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
post_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ["id", "name", "slug", "post_count", "created_at"]
read_only_fields = ["id", "created_at"]
def get_post_count(self, obj):
return obj.posts.count()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email"]
class PostListSerializer(serializers.ModelSerializer):
"""列表用 —— 只返回摘要,不包含全文"""
author = UserSerializer(read_only=True)
category_name = serializers.CharField(source="category.name", read_only=True)
class Meta:
model = Post
fields = ["id", "title", "slug", "status", "author", "category_name",
"views_count", "created_at"]
read_only_fields = ["id", "author", "views_count", "created_at"]
class PostDetailSerializer(serializers.ModelSerializer):
"""详情用 —— 返回完整内容"""
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(), source="category", write_only=True
)
class Meta:
model = Post
fields = "__all__"
read_only_fields = ["id", "author", "views_count", "slug", "created_at", "updated_at"]
def validate_title(self, value):
"""自定义字段级验证"""
if len(value) < 3:
raise serializers.ValidationError("标题至少需要3个字符")
return value
步骤 5:编写 ViewSet
# blog/views.py
from rest_framework import viewsets, permissions, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Post, Category
from .serializers import (
PostListSerializer, PostDetailSerializer, CategorySerializer
)
class PostViewSet(viewsets.ModelViewSet):
"""文章 CRUD ViewSet"""
queryset = Post.objects.select_related("author", "category").all()
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ["status", "category__slug"]
search_fields = ["title", "content"]
ordering_fields = ["created_at", "views_count", "title"]
def get_serializer_class(self):
if self.action == "list":
return PostListSerializer
return PostDetailSerializer
def get_permissions(self):
if self.action in ["create", "update", "partial_update", "destroy"]:
return [permissions.IsAuthenticated()]
return [permissions.AllowAny()]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# 自定义 action
@action(detail=True, methods=["post"])
def increment_views(self, request, pk=None):
"""自定义动作:增加浏览量"""
post = self.get_object()
post.views_count += 1
post.save(update_fields=["views_count"])
return Response({"views_count": post.views_count})
@action(detail=False, methods=["get"])
def my_posts(self, request):
"""获取当前用户的文章"""
posts = self.queryset.filter(author=request.user)
serializer = self.get_serializer(posts, many=True)
return Response(serializer.data)
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
"""分类 —— 只读"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = "slug" # 用 slug 而非 id 查找
步骤 6:配置路由
# blog/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r"posts", views.PostViewSet, basename="post")
router.register(r"categories", views.CategoryViewSet, basename="category")
urlpatterns = [
path("api/", include(router.urls)),
]
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
步骤 7:迁移与运行
python manage.py makemigrations blog
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
API 端点一览
| 方法 |
URL |
说明 |
| GET |
/api/posts/ |
文章列表(分页+过滤+搜索) |
| POST |
/api/posts/ |
创建文章(需认证) |
| GET |
/api/posts/{id}/ |
文章详情 |
| PUT |
/api/posts/{id}/ |
全量更新(需认证) |
| PATCH |
/api/posts/{id}/ |
部分更新(需认证) |
| DELETE |
/api/posts/{id}/ |
删除(需认证) |
| POST |
/api/posts/{id}/increment_views/ |
自增浏览量 |
| GET |
/api/posts/my_posts/ |
我的文章(需认证) |
| GET |
/api/categories/ |
分类列表 |
| GET |
/api/categories/{slug}/ |
分类详情 |
测试命令
curl http:
curl "http://127.0.0.1:8000/api/posts/?status=published&search=Django&ordering=-views_count"
curl -X POST http:
-H "Content-Type: application/json" \
-u admin:password \
-d '{"title":"Hello DRF","content":"# 我的第一篇文章","status":"published","category_id":1}'
关键要点
| 概念 |
说明 |
ModelSerializer |
根据模型自动生成序列化字段,减少样板代码 |
ModelViewSet |
一次性提供 list/create/retrieve/update/destroy 全套 |
@action |
在 ViewSet 上添加自定义端点 |
select_related |
预加载外键关联,避免 N+1 查询 |
lookup_field |
将默认的 pk 查找改为其他字段(如 slug) |
DefaultRouter |
自动按 REST 惯例生成 URL 路由 |