Chapter 03

模型 Model 与 ORM

用 Python 类描述数据库,用迁移演进结构,用 QuerySet 优雅查询——这是 Django「自带电池」最核心的一块。

3.1 定义模型

# blog/models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    published = models.BooleanField(default=False)
    views = models.IntegerField(default=0)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="posts")
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created"]

    def __str__(self):
        return self.title
Model
继承 models.Model 的 Python 类,一个类对应一张表,一个类属性对应一个字段(列)。
Field
字段类型,如 CharField/TextField/IntegerField/BooleanField/DateTimeField/DecimalField,决定列类型与校验规则。
Migration(迁移)
模型变更的版本化脚本,记录「如何把数据库从上一状态改到当前状态」,可前进可回滚。
QuerySet
惰性求值的查询集合,代表一组数据库记录。链式调用(filter/exclude/order_by)不会立即执行 SQL,遍历时才求值。
ForeignKey(多对一)
一方持有外键指向另一方,如多篇文章属于一个分类。on_delete 定义级联行为。
ManyToManyField(多对多)
双向多对多,如文章与标签。Django 自动创建中间关联表。
OneToOneField(一对一)
一对一关系,如 User 与 Profile,常用于扩展现有模型。

3.2 迁移工作流

# 1. 根据模型变更生成迁移脚本
$ python manage.py makemigrations blog

# 2. 应用迁移到数据库
$ python manage.py migrate

# 查看某迁移将执行的 SQL
$ python manage.py sqlmigrate blog 0001

# 查看迁移状态
$ python manage.py showmigrations
两步走的意义

makemigrations 只是「记录变更意图」(生成脚本、可入 Git 供 review),migrate 才真正改数据库。团队协作时脚本随代码提交,人人 migrate 得到一致结构。

3.3 QuerySet 基础查询

from blog.models import Post

Post.objects.all()                       # 全部
Post.objects.filter(published=True)      # 过滤
Post.objects.exclude(views=0)           # 排除
Post.objects.get(pk=1)                  # 取单条(不存在抛异常)
Post.objects.filter(title__icontains="django")   # 字段查找
Post.objects.order_by("-views")[:10]     # 排序 + 切片
Post.objects.filter(views__gte=100).count()

字段查找(Field Lookups)

查找含义示例
exact / iexact精确 / 忽略大小写title__iexact="X"
contains / icontains包含body__icontains="orm"
gt / gte / lt / lte大于/小于views__gte=100
in在集合内id__in=[1,2,3]
startswith / endswith前缀/后缀title__startswith="A"
isnull是否为空category__isnull=True

3.4 关系查询与 Q 对象

from django.db.models import Q

# 跨关系过滤:分类名为 Python 的文章
Post.objects.filter(category__name="Python")

# 反向关系:某分类下的所有文章(related_name)
cat.posts.all()

# Q 对象组合 OR / AND / NOT
Post.objects.filter(Q(views__gt=100) | Q(published=True))
Post.objects.filter(~Q(category__name="草稿"))

3.5 聚合与注解

from django.db.models import Count, Avg, Sum, Max

# 聚合:全表统计
Post.objects.aggregate(total=Sum("views"), avg=Avg("views"))
# => {"total": 4200, "avg": 88.5}

# 注解:给每个分类附加文章数
Category.objects.annotate(n=Count("posts")).order_by("-n")
警惕 N+1 查询

循环里访问关系字段会触发额外查询。用 select_related(外键,JOIN)与 prefetch_related(多对多,二次查询)预加载,把 N+1 压成常数次查询。

# 一次 JOIN 取出文章及其分类,循环访问 post.category 不再查库
for post in Post.objects.select_related("category"):
    print(post.title, post.category.name)

3.6 增删改

# 创建
post = Post.objects.create(title="ORM 入门", body="...", category=cat)

# 更新
post.views = 10
post.save()
Post.objects.filter(published=False).update(published=True)  # 批量

# 删除
post.delete()

3.7 小结与下一步