Django REST Framework — Django 的 RESTful API 构建工具包
一句话说明
DRF(Django REST Framework)给 Django 加上序列化器、视图集、路由器、权限系统,让你用极少代码就能构建完整的 RESTful API,是 Django 生态做 API 的标准选择。
安装与配置
# pip 安装
pip install djangorestframework # 当前版本 3.15+
pip install django # Django 5.2 LTS
# 在 settings.py 注册
# INSTALLED_APPS = [..., 'rest_framework']
# settings.py 配置
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication", # Session 认证
"rest_framework.authentication.BasicAuthentication", # Basic 认证
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated", # 默认需要登录
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20, # 每页20条
}
核心用法
序列化器(Serializer)
# serializers.py
from rest_framework import serializers
from .models import Patient
class PatientSerializer(serializers.ModelSerializer):
"""患者模型序列化器(自动从 Model 生成)"""
# 添加只读计算字段
age_group = serializers.SerializerMethodField()
class Meta:
model = Patient # 对应的 Django 模型
fields = ["id", "name", "age", "diagnosis", "age_group", "created_at"]
read_only_fields = ["id", "created_at"] # 只读字段(不接受用户修改)
def get_age_group(self, obj) -> str:
"""计算年龄段"""
return "老年" if obj.age >= 60 else "中青年"
def validate_age(self, value: int) -> int:
"""字段级验证"""
if not 0 <= value <= 150:
raise serializers.ValidationError("年龄必须在 0-150 之间")
return value
视图集(ViewSet)
# views.py
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Patient
from .serializers import PatientSerializer
class PatientViewSet(viewsets.ModelViewSet):
"""患者 CRUD 接口(自动提供 list/create/retrieve/update/destroy)"""
queryset = Patient.objects.all().order_by("-created_at") # 默认查询集
serializer_class = PatientSerializer
permission_classes = [permissions.IsAuthenticated] # 需要登录
# 支持过滤和搜索
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ["name", "diagnosis"] # 搜索字段
ordering_fields = ["age", "created_at"] # 可排序字段
def get_queryset(self):
"""根据查询参数动态过滤"""
qs = super().get_queryset()
group = self.request.query_params.get("group") # ?group=T2D
if group:
qs = qs.filter(diagnosis=group)
return qs
# 自定义动作(额外路由)
@action(detail=False, methods=["GET"], url_path="stats")
def statistics(self, request):
"""GET /api/patients/stats/ — 统计信息"""
from django.db.models import Avg, Count
stats = Patient.objects.aggregate(
total=Count("id"),
avg_age=Avg("age"),
)
return Response(stats)
路由注册
# urls.py
from rest_framework.routers import DefaultRouter
from .views import PatientViewSet
router = DefaultRouter()
router.register(r"patients", PatientViewSet, basename="patient")
# 自动生成:GET/POST /patients/, GET/PUT/DELETE /patients/{id}/
# 额外:GET /patients/stats/(自定义 action)
urlpatterns = router.urls
实战案例
带 Token 认证的 API
# settings.py 添加 Token 认证
# pip install djangorestframework
# python manage.py migrate (生成 authtoken 表)
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication", # Token 认证
],
}
INSTALLED_APPS = [..., "rest_framework.authtoken"]
# views.py 登录获取 Token
from rest_framework.authtoken.views import obtain_auth_token
# urls.py 添加:path("api/login/", obtain_auth_token)
# 客户端使用:
# curl -H "Authorization: Token abc123..." http://localhost:8000/api/patients/
常见报错与解决
| 报错 | 原因 | 解决 |
|---|
401 Authentication credentials not provided | 未传 Token | 请求头加 Authorization: Token xxx |
403 You do not have permission | 权限不足 | 检查 permission_classes 设置 |
400 This field is required | 必填字段未传 | 检查序列化器的 required 字段 |
ImproperlyConfigured | 忘记注册 app | INSTALLED_APPS 加 'rest_framework' |
速查表
| 操作 | 代码/说明 |
|---|
| 模型序列化器 | class S(ModelSerializer): class Meta: model=M |
| 完整 CRUD 视图集 | class V(ModelViewSet): queryset=..., serializer_class=... |
| 注册路由 | router.register("path", ViewSet) |
| 自定义动作 | @action(detail=False, methods=["GET"]) |
| 查询参数过滤 | request.query_params.get("key") |
| 字段验证 | def validate_field(self, value): ... |
| Token 认证 | TokenAuthentication + obtain_auth_token |
| 搜索过滤 | filter_backends=[SearchFilter], search_fields=["name"] |