Python Django模型与数据库:ORM查询、关联关系与迁移进阶
文章目录
- [Python Django模型与数据库:ORM查询、关联关系与迁移进阶](#Python Django模型与数据库:ORM查询、关联关系与迁移进阶)
-
- 前言
- 一、重新审视模型定义
-
- [1.1 字段选项详解](#1.1 字段选项详解)
- [1.2 三种关联关系](#1.2 三种关联关系)
- 二、ORM查询:从基础到进阶
-
- [2.1 基础查询](#2.1 基础查询)
- [2.2 字段查询(Field Lookups)](#2.2 字段查询(Field Lookups))
- [2.3 关联查询](#2.3 关联查询)
- [2.4 聚合与分组](#2.4 聚合与分组)
- [2.5 性能优化](#2.5 性能优化)
- 三、模型方法与管理器
-
- [3.1 自定义模型方法](#3.1 自定义模型方法)
- [3.2 自定义管理器](#3.2 自定义管理器)
- 四、数据库迁移进阶
-
- [4.1 迁移的日常操作](#4.1 迁移的日常操作)
- [4.2 数据迁移(Data Migration)](#4.2 数据迁移(Data Migration))
- [4.3 迁移注意事项](#4.3 迁移注意事项)
- 总结
- [✅ 亮点总结](#✅ 亮点总结)
- 适用场景
- 扩展方向
前言
数据库操作是Web开发的灵魂,而Django的**ORM(对象关系映射)**将复杂的SQL操作转化为优雅的Python代码。你无需手写SQL,却能处理绝大部分数据库场景------从简单的CRUD到复杂的多表关联查询。本文在上篇Django入门的基础上,深入探讨模型定义的高级技巧、ORM查询的各类方法、数据库迁移的最佳实践,以及三种核心关联关系的实战应用。
ORM的价值不只是"不用写SQL" :更重要的价值是通过Python对象操作数据库,IDE可以提供代码补全和类型检查,重构时改模型名会自动更新所有引用。此外,Django ORM的查询集(QuerySet)是惰性求值的------
Article.objects.filter(status='published')在真正需要数据之前不会执行SQL。这意味着你可以链式叠加多个filter、exclude、order_by,Django只生成一条最优化的SQL语句。这种设计极大减少了数据库访问次数。
一、重新审视模型定义
1.1 字段选项详解
python
from django.db import models
class Product(models.Model):
# CharField 的常用参数
name = models.CharField(
max_length=100,
unique=True, # 唯一约束
db_index=True, # 创建数据库索引
verbose_name='商品名称'
)
# decimal_places 控制小数位数,max_digits 控制总位数
price = models.DecimalField(
max_digits=10, decimal_places=2,
verbose_name='价格'
)
# blank 控制表单验证,null 控制数据库层面
description = models.TextField(
blank=True, # 表单中可以为空
null=True, # 数据库中可以为NULL
verbose_name='描述'
)
# default 设置默认值
is_active = models.BooleanField(
default=True,
verbose_name='是否上架'
)
# auto_now_add 只在创建时设置
created_at = models.DateTimeField(auto_now_add=True)
# auto_now 每次保存都更新
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'shop_product' # 自定义表名
ordering = ['-created_at'] # 默认排序
verbose_name = '商品'
verbose_name_plural = '商品'
indexes = [
models.Index(fields=['name', 'is_active']), # 复合索引
]
db_index 和 Meta.indexes 的区别:前者创建单列索引(简单直接),后者支持多列复合索引和多列排序。
1.2 三种关联关系
关系型数据库的核心就是表之间的关联。Django通过三个字段类型来表达:
一对多(ForeignKey)
最常见的关系。一 方对应多方。例如:一个作者可以写多篇文章。
python
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE, # 作者删除时,他的文章也删除
related_name='posts', # 反向查询的名称
)
on_delete 的选项非常关键:
| 选项 | 含义 |
|---|---|
| CASCADE | 级联删除,关联对象也被删除 |
| PROTECT | 阻止删除,抛出ProtectedError |
| SET_NULL | 设为NULL(需null=True) |
| SET_DEFAULT | 设为默认值(需default) |
| DO_NOTHING | 什么都不做(依赖数据库约束) |
多对多(ManyToManyField)
例如:一篇文章可以有多个标签,一个标签可以对应多篇文章。
python
class Tag(models.Model):
name = models.CharField(max_length=30, unique=True)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(
Tag,
related_name='posts',
blank=True
)
Django会自动创建一张中间表来存储多对多关系。你也可以手动指定中间表来存储额外信息:
python
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(
Tag,
through='PostTag', # 自定义中间表
related_name='posts'
)
class PostTag(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True) # 额外字段
class Meta:
unique_together = ['post', 'tag'] # 联合唯一
一对一(OneToOneField)
例如:每个用户有一个专属的资料扩展表。
python
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
avatar = models.URLField(blank=True)
birthday = models.DateField(null=True, blank=True)
二、ORM查询:从基础到进阶
Django ORM提供了一套链式调用的查询API,语法流畅且Pythonic。
2.1 基础查询
Django ORM的查询API设计得非常接近自然语言------Article.objects.filter(status='published') 可以读作"从Article对象中过滤出状态为已发布的"。这种链式调用风格(filter之后可以继续.exclude、.order_by、.annotate)让你像搭积木一样构建查询,每一步都返回一个QuerySet对象。
QuerySet的惰性求值 (面试高频题):
qs = Article.objects.filter(status='published')这行代码不会立刻执行SQL查询。QuerySet只在实际需要数据时才执行------比如你遍历它、调用len()、或者把它转为列表时。这意味着qs = qs.filter(view_count__gt=100); qs = qs.exclude(author=admin_user)连续叠加多个过滤条件,Django只会生成一条合并后的SQL。这是ORM性能优化的基础认知。
python
# 获取所有对象
all_articles = Article.objects.all()
# 过滤查询
published = Article.objects.filter(status='published')
# 排除查询
not_draft = Article.objects.exclude(status='draft')
# 获取单个对象(不存在或多于一个会抛异常)
article = Article.objects.get(pk=1)
# 获取或创建
article, created = Article.objects.get_or_create(
title='默认标题',
defaults={'content': '默认内容', 'status': 'draft'}
)
# 计数
count = Article.objects.filter(status='published').count()
# 是否存在
has_draft = Article.objects.filter(status='draft').exists()
2.2 字段查询(Field Lookups)
Django通过双下划线语法实现丰富的查询条件:
python
# 等于(默认)
Article.objects.filter(status='published')
# 不等于
Article.objects.exclude(status='draft')
# __gt / __gte / __lt / __lte
Article.objects.filter(view_count__gt=100) # 大于100
Article.objects.filter(view_count__gte=50) # 大于等于
# __in
Article.objects.filter(category__in=[1, 2, 3])
# __range
Article.objects.filter(created_at__range=['2025-01-01', '2025-06-01'])
# __contains / __icontains (不区分大小写)
Article.objects.filter(title__icontains='django')
# __startswith / __endswith
Article.objects.filter(title__startswith='Python')
# __isnull
Article.objects.filter(description__isnull=True)
# 日期查询
Article.objects.filter(created_at__year=2025)
Article.objects.filter(created_at__month=5)
2.3 关联查询
python
# 正向查询:通过外键过滤
articles = Article.objects.filter(category__name='技术')
# 反向查询:通过 related_name
categories = Category.objects.filter(articles__status='published')
# 跨越多层关联
articles = Article.objects.filter(category__parent__name='编程')
# 反向查询统计
from django.db.models import Count
categories = Category.objects.annotate(
article_count=Count('articles')
).filter(article_count__gt=0)
2.4 聚合与分组
python
from django.db.models import Count, Sum, Avg, Max, Min, Q, F
# 聚合函数
result = Article.objects.aggregate(
total=Count('id'),
avg_views=Avg('view_count'),
max_views=Max('view_count')
)
# 返回: {'total': 150, 'avg_views': 235.5, 'max_views': 1200}
# 按分类分组统计
Category.objects.annotate(
article_count=Count('articles'),
total_views=Sum('articles__view_count')
).values('name', 'article_count', 'total_views')
# F表达式:引用字段的值
Article.objects.update(view_count=F('view_count') + 1)
# Q对象:复杂逻辑查询
from django.db.models import Q
articles = Article.objects.filter(
Q(status='published') &
(Q(title__icontains='django') | Q(content__icontains='django'))
)
Q对象 支持 &(AND)、|(OR)、~(NOT)运算符,可以构建任意复杂的查询条件。
2.5 性能优化
python
# select_related:一次性查询外键关联对象(JOIN方式)
articles = Article.objects.select_related('category', 'author')
# prefetch_related:预取多对多/反向一对多关联(额外查询)
articles = Article.objects.prefetch_related('tags')
# only / defer:只查询/延迟查询指定字段
articles = Article.objects.only('title', 'created_at')
articles = Article.objects.defer('content') # 延迟加载大字段
- select_related 适用于ForeignKey和OneToOneField,使用SQL JOIN一次查出
- prefetch_related 适用于ManyToManyField和反向ForeignKey,执行额外查询后在Python中拼接
三、模型方法与管理器
3.1 自定义模型方法
python
class Article(models.Model):
# ... 字段定义 ...
def is_popular(self):
"""是否热门文章"""
return self.view_count > 1000
def short_content(self, length=100):
"""截取摘要"""
return self.content[:length] + '...' if len(self.content) > length else self.content
def publish(self):
"""发布文章"""
self.status = 'published'
self.save(update_fields=['status'])
3.2 自定义管理器
python
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='published')
def popular(self):
return self.get_queryset().filter(view_count__gt=1000)
class Article(models.Model):
# ... 字段定义 ...
objects = models.Manager() # 默认管理器
published = PublishedManager() # 自定义管理器
# 使用
Article.published.all() # 所有已发布的文章
Article.published.popular() # 热门文章
Article.objects.all() # 所有文章(包括草稿)
四、数据库迁移进阶
4.1 迁移的日常操作
数据库迁移(Migration)是Django最强大的功能之一------它让你像管理代码版本一样管理数据库结构的变更。每次模型改动后,运行 makemigrations 生成迁移文件,migrate 应用到数据库。迁移文件是纯Python代码,可以提交到Git,团队成员拉取后执行 migrate 就能保持数据库结构一致------彻底告别"我这边表结构和你不一样"的协作噩梦。
迁移的核心价值 :在没有ORM的传统开发中,DBA需要手动执行ALTER TABLE语句修改数据库结构,而且开发环境和生产环境的变更必须同步。Django的迁移系统将这个过程自动化、可追溯、可回滚------你甚至可以
migrate blog 0002回滚到数据库的历史状态。
bash
# 创建迁移
python manage.py makemigrations blog
# 查看迁移计划(不执行)
python manage.py migrate --plan
# 查看迁移SQL
python manage.py sqlmigrate blog 0003
# 执行到某个迁移
python manage.py migrate blog 0002
# 回滚迁移
python manage.py migrate blog 0001
# 重置迁移历史
python manage.py migrate blog zero
4.2 数据迁移(Data Migration)
有时修改模型需要同时迁移已有数据。创建一个空迁移然后手动填写:
bash
python manage.py makemigrations blog --empty --name add_default_category
python
# blog/migrations/xxxx_add_default_category.py
from django.db import migrations
def create_default_category(apps, schema_editor):
Category = apps.get_model('blog', 'Category')
Category.objects.create(name='未分类')
def reverse_func(apps, schema_editor):
Category = apps.get_model('blog', 'Category')
Category.objects.filter(name='未分类').delete()
class Migration(migrations.Migration):
dependencies = [
('blog', '0002_previous_migration'),
]
operations = [
migrations.RunPython(create_default_category, reverse_func),
]
RunPython 可以执行自定义的Python函数来操作数据,是数据迁移的核心工具。
4.3 迁移注意事项
- 迁移文件要提交到版本控制,团队成员共享同一套迁移历史
- 不要手动修改已应用的迁移文件,应创建新的迁移
- 生产环境迁移前要备份数据库
- 大数据量迁移要分批处理,避免锁表时间过长
总结
本文深入讲解了Django ORM的核心能力:模型定义 阶段要正确选择字段类型和关联关系;查询阶段 利用双下划线语法、Q对象和聚合函数写出高效的查询语句;关联关系 需要理解ForeignKey、ManyToManyField和OneToOneField的语义和适用场景;数据库迁移 是开发的忠实伙伴,既能让版本控制追踪数据库变更,也能安全地执行数据迁移。掌握了这些,你就能游刃有余地应对绝大部分数据库场景了。下一篇我们将转向Python单元测试,学习如何用unittest保障代码质量。
✅ 亮点总结
- Django ORM支持二十余种字段类型和丰富的参数选项,几乎能映射所有常见的数据建模需求
- 双下划线查询语法(
field__lookup)是Django ORM的精髓------精准过滤、范围查询、跨表关联一气呵成 - Q对象通过
|(或)、&(与)、~(非)组合实现复杂逻辑查询,告别拼接SQL字符串 - ForeignKey、ManyToManyField和OneToOneField三种关联关系覆盖了一对多、多对多和一对一的全部场景
- 数据库迁移系统让schema变更可版本控制、可回溯、可自动生成,大幅降低数据库管理的心智负担
适用场景
- 复杂业务建模:电商系统中商品、订单、用户之间的多层级关联关系建模
- 多表关联查询:在一个查询中跨多张表筛选数据,利用
select_related()和prefetch_related()优化性能 - 数据库版本管理:团队协作中通过迁移文件追踪每次数据库结构变更,确保环境一致性
扩展方向
- 数据库索引与查询优化:学习
db_index、index_together和explain()分析,提升高并发下的查询性能 - 原生SQL与ORM混合:在ORM力不从心时使用
raw()或connection.cursor()执行复杂SQL - 推荐阅读下一篇《Python单元测试unittest》,学习如何为Django模型和视图编写测试用例
Django ORM的设计哲学是"能用Python表达的,绝不用SQL"。但偶尔的原生SQL查询也是可以的------通过 Article.objects.raw() 或 connection.cursor()。