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 小结与下一步
- 模型是 Python 类,字段是列;变更通过
makemigrations+migrate演进。 - QuerySet 惰性求值,用字段查找、Q 对象、关系跨越构造复杂条件。
- 聚合
aggregate统计全表,注解annotate逐行附加;用 select/prefetch_related 治 N+1。 - 下一章进入 View——把 QuerySet 变成 HTTP 响应,对比 FBV 与 CBV。