02-drf-restful-api

知识库
知识库文档
/tech-stacks/django/examples/02-drf-restful-api.md

文档

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://127.0.0.1:8000/api/posts/

# 过滤 + 搜索
curl "http://127.0.0.1:8000/api/posts/?status=published&search=Django&ordering=-views_count"

# 创建文章
curl -X POST http://127.0.0.1:8000/api/posts/ \
  -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 路由

信息

路径
/tech-stacks/django/examples/02-drf-restful-api.md
更新时间
2026/5/30