前言
Django REST framework(通常简称 DRF)是 Django 生态里最主流的 REST API 框架。它不是 Django 自带的,而是一个独立的第三方包,要单独安装。这一点常被误解——很多人以为 Django 生来就能写 REST 接口,实际上不加 DRF 也完全可以手写接口,只是要自己处理序列化、内容协商、状态码、分页,很啰嗦。
另一个常见误解是「DRF 只有一种写法」。实际上它提供了三种粒度不同的视图:APIView(最原始)、泛型视图(针对单个模型的常用操作)、ViewSet+ 路由器(把一组相关操作打包)。新手常见的两种极端是:要么全部手写APIView,把 DRF 当普通 Django 用;要么一上来就ModelViewSet,却不清楚它到底暴露了哪几个端点、哪些该关掉。
本文用一个博客文章(Post)的例子,从序列化器到路由完整走一遍。示例代码以 Django 5.x / 6.x 搭配当前稳定版 DRF 为背景,DRF 的具体 API 以官方文档为准;Python 版本要求取决于你选的 Django 版本(如 Django 6.x 需要 Python 3.12 及以上)。
一、安装与最小配置
pip install djangorestframework然后在settings.py里注册,并给一组全局默认值:
# settings.py
INSTALLED_APPS = [
# ... Django 自带应用
"rest_framework",
"blog", # 你自己的应用
]
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
}两个要点:REST_FRAMEWORK是一个字典,键名全大写;分页要同时设置DEFAULT_PAGINATION_CLASS和PAGE_SIZE,因为两者的默认值都是None,只设一个不会生效。
二、被拆解的数据模型
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title改完模型照例要python manage.py makemigrations blog再python manage.py migrate。
三、序列化器:模型与 JSON 之间的翻译层
序列化器(serializer)负责两件事:把模型实例变成可以返回的 JSON(序列化),把请求里的 JSON 变成经过校验的 Python 数据(反序列化)。
# blog/serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ["id", "title", "body", "created"]
read_only_fields = ["created"] # 只读,不接受客户端传入
def validate_title(self, value):
# 针对单个字段的校验钩子:名字必须是 validate_字段名
if len(value.strip()) < 3:
raise serializers.ValidationError("标题至少 3 个字符")
return value
def validate(self, attrs):
# 跨字段校验:attrs 是已校验的字段字典
if attrs.get("title") == attrs.get("body"):
raise serializers.ValidationError("标题和正文不能一模一样")
return attrs用ModelSerializer时,fields列表决定了哪些字段会被暴露。这里有个重要的安全习惯:永远显式列出fields,不要图省事写fields = "__all__"——数据库里的敏感字段(密码哈希、内部标记)会随着模型演进而自动泄露出去。
手动使用序列化器的流程:
# 适用于 DRF 当前稳定版
from blog.serializers import PostSerializer
# 反序列化:校验请求数据
ser = PostSerializer(data={"title": "第一篇", "body": "正文内容"})
ser.is_valid(raise_exception=True) # 校验失败直接抛 400
post = ser.save() # 调用 create()
# 序列化:把对象转成可返回的数据
print(PostSerializer(post).data)is_valid()返回布尔值;加了raise_exception=True后,校验失败会直接抛出 DRF 的异常,由框架转成 HTTP 400,省去手写判断。校验通过的数据在ser.validated_data,错误信息在ser.errors。
四、视图的三种粒度
| 写法 | 抽象程度 | 适合 |
|---|
APIView | 最低 | 非 CRUD 的特殊逻辑,完全自定义 |
泛型视图(ListCreateAPIView等) | 中 | 针对单模型的常见操作 |
ViewSet(ModelViewSet) | 最高 | 标准 CRUD,配合路由器自动生成 URL |
最省事的是ModelViewSet。官方文档写明它提供.list()、.retrieve()、.create()、.update()、.partial_update()、.destroy()六个动作,也就是标准的「增删改查」全套。
# blog/views.py
from rest_framework import permissions, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by("-created")
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
@action(detail=False, methods=["get"])
def recent(self, request):
"""额外动作:GET /posts/recent/,只返回最新 5 篇。"""
qs = self.filter_queryset(self.get_queryset())[:5]
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)@action用来给 ViewSet 加「不标准」的端点。两个关键参数:detail=True表示针对单个对象(URL 里带主键),detail=False表示针对整个集合;methods指定允许的 HTTP 方法。注册后,recent会挂到/posts/recent/上。
官方文档有一条提醒值得记住:不要对@action方法用.as_view()——那会绕过路由器的设置,导致permission_classes之类的动作配置被忽略。
五、路由:交给路由器自动生成
# blog/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register(r"posts", PostViewSet, basename="post")
urlpatterns = [
path("", include(router.urls)),
]# 根 urls.py
from django.contrib import admin
from django.urls import include, path
from rest_framework.authtoken import views as token_views
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("blog.urls")),
path("api-token-auth/", token_views.obtain_auth_token),
]DefaultRouter自动生成的端点大致是:
| HTTP 方法 | URL | 动作 |
|---|
| GET | /api/posts/ | 列表 |
| POST | /api/posts/ | 新建 |
| GET | /api/posts/{id}/ | 详情 |
| PUT | /api/posts/{id}/ | 整体更新 |
| PATCH | /api/posts/{id}/ | 局部更新 |
| DELETE | /api/posts/{id}/ | 删除 |
| GET | /api/posts/recent/ | 自定义动作 |
register方法支持可选的basename参数。当 ViewSet 没有定义queryset属性时,必须显式给basename,否则路由器无法推断 URL 名称。
六、认证与权限
DRF 把「认证」(你是谁)和「权限」(你能不能做)分成两层,可以分别配置。
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}用TokenAuthentication需要额外做两件事:把rest_framework.authtoken加进INSTALLED_APPS,并执行python manage.py migrate(该应用自带数据库迁移)。之后客户端用请求头发送令牌:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b默认权限类很关键。DEFAULT_PERMISSION_CLASSES不设置时,DRF 默认是AllowAny——任何人都能读写你的接口。新项目上线前务必确认这一项已改成IsAuthenticated或IsAuthenticatedOrReadOnly。
官方文档还有一条硬性要求:在传输层使用TokenAuthentication时,必须保证 API 只通过 HTTPS 暴露,否则令牌等于明文裸奔。
获得令牌可以用内置视图(把用户名密码 POST 上去):
from rest_framework.authtoken import views as token_views
urlpatterns += [
path("api-token-auth/", token_views.obtain_auth_token),
]常见坑点
1. 忘了把rest_framework加进INSTALLED_APPS
❌ 直接from rest_framework import serializers后页面报配置错误。 ✅ 先pip install djangorestframework,再加进INSTALLED_APPS。
2.fields = "__all__"埋下泄露隐患
❌class Meta: model = User; fields = "__all__",密码哈希也被暴露。 ✅ 显式列出fields = ["id", "username", "email"]。
3. 以为 DRF 默认要求登录
❌ 不配DEFAULT_PERMISSION_CLASSES,接口对全网开放还不自知。 ✅ 显式设置权限类;默认是AllowAny。
4. ViewSet 没有queryset也没给basename
❌router.register(r"posts", PostViewSet)抛路由命名错误。 ✅ 补上basename="post",或给 ViewSet 定义queryset。
5. 只设了PAGE_SIZE却没设分页类
❌ 以为配个PAGE_SIZE就自动分页,结果返回全量数据。 ✅DEFAULT_PAGINATION_CLASS和PAGE_SIZE两个都要设。
6. 用HttpResponse返回 DRF 的数据
❌ 在 DRF 视图里写return HttpResponse(serializer.data),收到一个str的字典。 ✅ 用rest_framework.response.Response。
7. 忘记raise_exception=True
❌if ser.is_valid(): ...校验失败时静默跳过,客户端收到 200。 ✅ser.is_valid(raise_exception=True),失败自动返回 400。
8. 用 TokenAuthentication 却跑在 HTTP 上
❌ 明文 HTTP 传Authorization: Token ...,令牌可被截获。 ✅ 生产必须走 HTTPS;或用更适合浏览器的会话认证。
总结
| 组件 | 作用 | 关键设置 |
|---|
ModelSerializer | 模型与 JSON 互转 | 显式列fields,用validate_*校验 |
ModelViewSet | 一套 CRUD 动作 | 提供 list/retrieve/create/update/destroy |
@action | 加自定义端点 | detail决定是否带主键 |
DefaultRouter | 自动生成 URL | 无queryset时必须给basename |
| 认证 / 权限 | 分层控制访问 | 默认AllowAny,上线要改 |
| 分页 | 控制返回数量 | 分页类与PAGE_SIZE同时设 |
DRF 的核心价值是把「序列化 + 校验 + 标准 CRUD + 路由」这套重复劳动模板化。上手时按Serializer → ViewSet → Router的顺序理解,再回头记牢两件事:fields要显式列,权限默认是开放。做到这两点,你已经能写出既简洁又不至于把数据泄露出去的 API 了。