Django 模型与数据全解析
本文基于Django4 进行代码测试
Django4 的核心优势之一是其强大的 ORM(对象关系映射)系统,模型(Model)作为 ORM 的核心载体,负责定义数据结构、映射数据库表,并提供简洁的 Python 接口操作底层数据,无需编写原生 SQL 即可完成数据的增删改查。本文将全面覆盖 Django4 中模型与数据相关的所有核心知识,从模型定义到高级操作,层层递进、清晰易懂。
一、模型基础:数据的“蓝图”
1.1 模型的本质与作用
模型是 Django 中用于描述数据结构的 Python 类,每个模型类对应数据库中的一张表,类中的属性对应表中的字段,类的实例对应表中的一条记录。其核心作用是:
-
定义数据结构:明确数据的字段类型、约束条件,规范数据格式;
-
映射数据库表:自动将模型类转换为数据库表(无需手动编写 CREATE TABLE 语句);
-
提供数据操作接口:通过 ORM 方法实现数据的增删改查,屏蔽底层数据库差异;
-
实现数据验证:内置字段验证逻辑,确保存入数据库的数据符合规范。
Django4 中,所有模型都必须继承自 django.db.models.Model 类,这是模型的基类,提供了所有核心功能支持。
1.2 模型的创建与注册
1.2.1 基本创建步骤
模型通常定义在应用的 models.py 文件中,创建一个简单模型的示例如下:
from django.db import models
# 定义用户模型(示例)
class User(models.Model):
# 字段定义:用户名,字符串类型,最大长度50,唯一且不为空
username = models.CharField(max_length=50, unique=True, null=False)
# 字段定义:密码,字符串类型,最大长度128(Django默认密码加密存储)
password = models.CharField(max_length=128)
# 字段定义:邮箱,字符串类型,最大长度100,允许为空
email = models.EmailField(max_length=100, blank=True)
# 字段定义:注册时间,自动添加当前时间(无需手动赋值)
create_time = models.DateTimeField(auto_now_add=True)
# 字段定义:更新时间,每次保存时自动更新为当前时间
update_time = models.DateTimeField(auto_now=True)
# 可选:定义模型的元数据(如表名、排序方式等)
class Meta:
# 自定义数据库表名,默认表名为“应用名_模型类名小写”(此处默认是app_user)
db_table = "user_info"
# 默认排序字段,-表示降序(按注册时间降序排列)
ordering = ["-create_time"]
1.2.2 模型注册
创建模型后,必须将包含模型的应用注册到项目的 settings.py 文件的INSTALLED_APPS 列表中,否则 Django 无法识别模型,也无法生成数据库表。注册示例:
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 注册自定义应用(包含models.py的应用)
'myapp', # 替换为你的应用名称
]
二、模型字段:数据的“具体描述”
字段是模型的核心组成部分,用于定义数据的类型、约束、默认值等属性。Django4 提供了丰富的内置字段类型,同时支持自定义字段,满足不同场景的数据需求。
2.1 核心内置字段类型
以下是 Django4 中最常用的内置字段类型,涵盖大部分开发场景:
| 字段类型 | 描述 | 常用参数 |
|---|---|---|
| CharField | 字符串类型,用于短文本(如用户名、标题),必须指定 max_length | max_length(最大长度)、unique(唯一)、blank(表单允许为空)、null(数据库允许为空) |
| TextField | 长文本类型,用于大段文本(如文章内容、备注) | blank、null |
| IntegerField | 整数类型,用于存储整数(如年龄、数量) | default(默认值)、unique、blank、null、min_value(最小值)、max_value(最大值) |
| FloatField | 浮点数类型,用于存储小数(如价格、评分) | default、blank、null |
| DecimalField | 高精度小数类型,用于金额等需要精确计算的场景 | max_digits(总位数)、decimal_places(小数位数)、default、blank、null |
| BooleanField | 布尔类型,用于存储 True/False | default、blank、null(null=True 时,数据库会存储 NULL,可表示“未定义”) |
| DateTimeField | 日期时间类型,用于存储具体的时间(如注册时间) | auto_now_add(创建时自动赋值当前时间)、auto_now(更新时自动赋值当前时间)、default、blank、null |
| DateField | 日期类型,用于存储日期(如生日) | auto_now_add、auto_now、default、blank、null |
| EmailField | 邮箱类型,自带邮箱格式验证 | max_length、unique、blank、null |
| ForeignKey | 外键,用于建立表与表之间的一对多关系 | to(关联的模型)、on_delete(删除关联数据时的行为)、related_name(反向查询别名) |
| OneToOneField | 一对一关系,用于两个表的一一对应(如用户与用户详情) | to、on_delete、related_name、unique(默认True) |
| ManyToManyField | 多对多关系,用于两个表的多对多关联(如用户与课程) | to、related_name、through(自定义中间表)、symmetrical(是否对称,默认True) |
2.2 字段通用参数
大部分字段都支持以下通用参数,用于设置字段的约束和默认行为,需重点掌握:
-
null:布尔值,默认 False。若为 True,数据库中该字段允许存储 NULL 值(注意:字符串类型字段建议避免设置 null=True,可用 blank=True 表示“空字符串”)。
-
blank:布尔值,默认 False。若为 True,表单验证时允许该字段为空(与 null 不同:blank 是表单层面的约束,null 是数据库层面的约束)。
-
default:字段的默认值,可以是具体值、可调用对象(如
datetime.now),若未赋值,将使用默认值。 -
unique:布尔值,默认 False。若为 True,数据库中该字段的值必须唯一(如用户名、邮箱)。
-
verbose_name:字段的中文显示名称,用于 Django 后台管理界面(如
verbose_name="用户名")。 -
choices:用于设置字段的可选值,格式为元组嵌套元组(如
(('M', '男'), ('F', '女'))),表单中将显示为下拉框,只能选择指定值。
注意:对于字符串类型字段(CharField、TextField),若设置 blank=True 且 null=False,空值将存储为空字符串(“”),而非 NULL;若同时设置 null=True 和 blank=True,则可能出现 NULL 和空字符串两种“空值”,通常不推荐这种设置。
2.3 自定义字段
若内置字段无法满足需求,Django4 支持自定义字段,需继承django.db.models.Field 类,并实现核心方法(如db_type() 定义数据库字段类型、to_python() 转换数据格式)。示例:
from django.db import models
# 自定义手机号字段(验证手机号格式)
class PhoneField(models.Field):
# 定义数据库中字段的类型
def db_type(self, connection):
return 'char(11)' # 数据库中存储为11位字符串
# 数据验证(将前端传入的数据转换为符合要求的格式)
def to_python(self, value):
if not value:
return None
# 验证手机号格式(11位数字)
if not value.isdigit() or len(value) != 11:
raise ValueError("手机号必须是11位数字")
return value
# 使用自定义字段
class User(models.Model):
phone = PhoneField(unique=True, verbose_name="手机号")
三、模型元数据:优化模型配置
模型的 Meta 类用于定义模型的元数据,即模型的“附加配置”,不影响数据结构,但会影响模型的行为(如排序、表名、权限等)。Django4 支持的常用元数据参数如下:
-
db_table:自定义数据库表名,默认表名为“应用名_模型类名小写”(如应用 myapp 中的 User 模型,默认表名是 myapp_user)。
-
ordering:默认排序字段,是一个列表,元素为字段名,前缀“-”表示降序(如
ordering = ["-create_time"]表示按创建时间降序)。 -
verbose_name:模型的中文显示名称(单数),用于后台管理(如
verbose_name="用户")。 -
verbose_name_plural:模型的中文显示名称(复数),默认是 verbose_name + “s”(如
verbose_name_plural="用户")。 -
unique_together:设置联合唯一约束,即多个字段的组合值必须唯一(如
unique_together = [("username", "email")]表示用户名和邮箱组合唯一)。 -
indexes:为字段创建数据库索引,提升查询效率(如
indexes = [models.Index(fields=["username"]),])。 -
permissions:自定义模型的权限(如
permissions = [("view_user", "查看用户"),])。
示例:完整的 Meta 类配置
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
email = models.EmailField(max_length=100)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "user_info" # 自定义表名
verbose_name = "用户"
verbose_name_plural = "用户"
ordering = ["-create_time"] # 按创建时间降序
unique_together = [("username", "email")] # 联合唯一
indexes = [models.Index(fields=["username"]),] # 为用户名创建索引
四、表关系:模型间的关联
实际开发中,数据往往不是孤立的,多个模型(表)之间需要建立关联,Django4 支持三种核心表关系:一对多、一对一、多对多,均通过特定字段实现。
4.1 一对多关系(ForeignKey)
一对多关系是最常见的表关系,指一个模型的一条记录对应另一个模型的多条记录(如“分类”与“文章”:一个分类下有多个文章,一个文章只属于一个分类)。
实现方式:在“多”的一方添加 ForeignKey 字段,关联“一”的一方模型。
# 一的一方:分类模型
class Category(models.Model):
name = models.CharField(max_length=50, verbose_name="分类名称")
def __str__(self):
return self.name
# 多的一方:文章模型(关联分类)
class Article(models.Model):
title = models.CharField(max_length=100, verbose_name="文章标题")
content = models.TextField(verbose_name="文章内容")
# 外键关联分类,on_delete=models.CASCADE 表示删除分类时,关联的文章也删除
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="articles")
def __str__(self):
return self.title
关键参数说明:
-
to:指定关联的模型(可直接写模型类名,若模型在其他应用,需写“应用名.模型类名”,如
to="myapp.Category")。 -
on_delete:删除关联模型记录时,当前模型记录的处理方式,Django4 中必须指定,常用值:
-
models.CASCADE:级联删除(删除关联记录,当前记录也删除); -
models.PROTECT:保护(禁止删除关联记录,若删除会抛出异常); -
models.SET_NULL:将当前外键字段设为 NULL(需确保外键字段 null=True); -
models.SET_DEFAULT:将当前外键字段设为默认值(需设置 default 参数); -
models.SET():将当前外键字段设为指定值(如models.SET(0)); -
models.DO_NOTHING:不做任何处理(不推荐,可能导致数据库外键约束错误)。
-
-
related_name:反向查询别名,用于“一”的一方查询“多”的一方数据(如
category.articles.all()可查询该分类下的所有文章),若不指定,默认别名是“模型类名小写_set”(如category.article_set.all())。
4.2 一对一关系(OneToOneField)
一对一关系指两个模型的记录一一对应(如“用户”与“用户详情”:一个用户只有一个详情,一个详情只属于一个用户)。
实现方式:在任意一方添加 OneToOneField 字段,关联另一方模型,默认添加 unique 约束。
# 用户模型
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
password = models.CharField(max_length=128)
# 用户详情模型(与用户一对一关联)
class UserProfile(models.Model):
# 一对一关联用户,on_delete=models.CASCADE 表示删除用户时,详情也删除
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
age = models.IntegerField(default=0, verbose_name="年龄")
address = models.CharField(max_length=200, blank=True, verbose_name="地址")
def __str__(self):
return f"{self.user.username}的详情"
说明:一对一关系的反向查询无需使用 _set,直接通过 related_name 或模型类名小写即可(如 user.profile 可获取该用户的详情)。
4.3 多对多关系(ManyToManyField)
多对多关系指一个模型的一条记录对应另一个模型的多条记录,反之亦然(如“用户”与“课程”:一个用户可以选多门课程,一门课程可以被多个用户选择)。
实现方式:在任意一方添加 ManyToManyField 字段,关联另一方模型,Django 会自动创建中间表(存储两个模型的关联关系);若需要在中间表中添加额外字段(如“选课时间”),需自定义中间表,并通过 through 参数指定。
4.3.1 基础多对多(无中间表额外字段)
# 课程模型
class Course(models.Model):
name = models.CharField(max_length=100, verbose_name="课程名称")
description = models.TextField(blank=True, verbose_name="课程描述")
def __str__(self):
return self.name
# 用户模型(与课程多对多关联)
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
# 多对多关联课程,Django自动创建中间表(表名:myapp_user_course)
courses = models.ManyToManyField(Course, related_name="users")
def __str__(self):
return self.username
4.3.2 自定义中间表(有额外字段)
# 自定义中间表(存储用户与课程的关联关系,及额外字段)
class Enroll(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
enroll_time = models.DateTimeField(auto_now_add=True, verbose_name="选课时间")
score = models.IntegerField(default=0, verbose_name="成绩")
# 联合唯一约束:一个用户不能重复选同一门课程
class Meta:
unique_together = [("user", "course")]
# 用户模型(通过through参数指定中间表)
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
# 多对多关联课程,指定中间表Enroll
courses = models.ManyToManyField(Course, through=Enroll, related_name="users")
# 课程模型不变
class Course(models.Model):
name = models.CharField(max_length=100, verbose_name="课程名称")
description = models.TextField(blank=True, verbose_name="课程描述")
说明:自定义中间表后,无法使用 add()、remove()、clear() 方法操作多对多关系,需通过中间表的模型实例进行操作(如创建选课记录:Enroll.objects.create(user=user, course=course, score=80))。
五、ORM 数据操作:模型与数据的交互
Django4 的 ORM 提供了简洁的 Python 接口,无需编写原生 SQL,即可完成数据的增删改查(CRUD)操作。所有操作均通过模型的管理器(默认是 objects)实现。
5.1 数据创建(Create)
创建数据有两种常用方式,适用于不同场景:
5.1.1 方式一:实例化模型 + save()
先实例化模型类,赋值字段,再调用 save() 方法保存到数据库(适合需要先处理数据,再保存的场景)。
# 实例化模型(未保存到数据库)
user = User(username="test", password="123456", email="test@example.com")
# 可选:修改字段值
user.email = "test123@example.com"
# 保存到数据库(执行INSERT SQL)
user.save()
5.1.2 方式二:objects.create()
直接通过管理器的 create() 方法创建数据,一步完成,返回创建的模型实例(适合无需额外处理,直接保存的场景)。
# 创建用户,直接保存到数据库
user = User.objects.create(
username="test2",
password="654321",
email="test2@example.com"
)
5.1.3 方式三:get_or_create()
查询数据,若存在则返回该实例;若不存在则创建并返回(适合避免重复创建的场景,如用户注册时检查用户名是否存在)。
# 查询用户名为test3的用户,不存在则创建
user, created = User.objects.get_or_create(
username="test3", # 查询条件(需是唯一字段或组合唯一字段)
defaults={ # 若不存在,创建时的其他字段值
"password": "111222",
"email": "test3@example.com"
}
)
# created 是布尔值,True表示创建成功,False表示查询到已有数据
print(created) # 第一次执行返回True,第二次返回False
5.1.4 批量创建:bulk_create()
当需要创建多条数据时,使用 bulk_create() 比循环调用 create() 更高效(仅执行一次 SQL,减少数据库连接次数)。
# 构建多个模型实例(未保存)
users = [
User(username="test4", password="444555", email="test4@example.com"),
User(username="test5", password="555666", email="test5@example.com"),
User(username="test6", password="666777", email="test6@example.com"),
]
# 批量保存到数据库
User.objects.bulk_create(users)
5.2 数据查询(Read)
查询是 ORM 最常用的操作,Django4 提供了丰富的查询方法,支持基础查询、高级筛选、排序、分页等功能,所有查询方法均返回 QuerySet(查询集)或单个模型实例。
5.2.1 基础查询方法
- all():查询所有数据,返回 QuerySet(可遍历,类似列表)。
`# 查询所有用户
users = User.objects.all()
遍历查询结果
for user in users:
print(user.username)`
- get():查询单个符合条件的数据,返回模型实例;若未找到或找到多条,会抛出异常(
DoesNotExist或MultipleObjectsReturned),需谨慎使用。
`# 根据id查询用户(id是Django自动生成的主键)
user = User.objects.get(id=1)
根据用户名查询(用户名唯一)
user = User.objects.get(username=“test”)`
- filter():查询符合条件的多条数据,返回 QuerySet(若无符合条件的数据,返回空 QuerySet,不抛出异常)。
`# 查询邮箱包含example的用户
users = User.objects.filter(email__contains=“example”)
查询年龄大于18的用户(__gt表示大于,类似的还有__lt小于、__gte大于等于、__lte小于等于)
users = User.objects.filter(age__gt=18)
多条件查询(默认是AND关系)
users = User.objects.filter(username__startswith=“test”, age__gte=20)`
- exclude():排除符合条件的数据,返回 QuerySet(与 filter() 相反)。
# 查询用户名不是test的用户 users = User.objects.exclude(username="test")
5.2.2 高级查询操作
(1)排序
使用 order_by() 方法排序,字段名前加“-”表示降序,可指定多个排序字段。
# 按注册时间升序(旧→新)
users = User.objects.all().order_by("create_time")
# 按注册时间降序(新→旧)
users = User.objects.all().order_by("-create_time")
# 先按年龄降序,再按注册时间升序
users = User.objects.all().order_by("-age", "create_time")
若模型的 Meta 类中设置了 ordering,则默认按该字段排序,无需再调用 order_by()。
(2)限制查询结果数量
使用切片操作限制结果数量(类似 Python 列表切片,不支持负索引),适合分页场景。
# 获取前5条用户记录
users = User.objects.all()[:5]
# 获取第6-10条用户记录(偏移5条,取5条)
users = User.objects.all()[5:10]
(3)判断数据是否存在
使用 exists() 方法判断是否有符合条件的数据,返回布尔值(比 len() 更高效,无需加载所有数据)。
# 判断是否存在用户名为test的用户
has_test = User.objects.filter(username="test").exists()
print(has_test) # 存在返回True,不存在返回False
(4)统计数据数量
使用 count() 方法统计符合条件的数据数量,返回整数(比 len(QuerySet) 更高效,直接执行 COUNT(*) SQL)。
# 统计所有用户数量
total_users = User.objects.count()
# 统计年龄大于18的用户数量
adult_users = User.objects.filter(age__gt=18).count()
(5)字段筛选(只查询指定字段)
使用 values() 或 values_list() 方法,只查询指定字段,减少数据传输,提升效率。
# values():返回字典列表,键是字段名,值是字段值
users = User.objects.filter(age__gt=18).values("username", "email")
# 结果示例:[{'username': 'test', 'email': 'test@example.com'}, ...]
# values_list():返回元组列表,元素是字段值
users = User.objects.filter(age__gt=18).values_list("username", "email")
# 结果示例:[('test', 'test@example.com'), ...]
# 若只查询单个字段,可设置flat=True,返回普通列表
usernames = User.objects.filter(age__gt=18).values_list("username", flat=True)
# 结果示例:['test', 'test2', ...]
(6)模糊查询
Django ORM 支持通过双下划线(__)实现模糊查询,常用模糊查询方式:
-
__contains:包含指定字符串(区分大小写); -
__icontains:包含指定字符串(不区分大小写); -
__startswith:以指定字符串开头; -
__endswith:以指定字符串结尾; -
__isnull:判断字段是否为 NULL(如email__isnull=True表示查询邮箱为空的用户)。
# 查询用户名包含“test”的用户(不区分大小写)
users = User.objects.filter(username__icontains="test")
# 查询邮箱以“example.com”结尾的用户
users = User.objects.filter(email__endswith="example.com")
(7)关联查询(多表查询)
基于表关系的查询,分为正向查询(从“关联字段所在的模型”查询“被关联模型”)和反向查询(从“被关联模型”查询“关联字段所在的模型”)。
# 示例模型:Article(多)关联 Category(一),Article有category外键
# 1. 正向查询(从Article查询Category)
article = Article.objects.get(id=1)
category = article.category # 获取文章所属的分类
# 2. 反向查询(从Category查询Article)
# 方式一:使用默认别名(模型类名小写_set)
category = Category.objects.get(id=1)
articles = category.article_set.all() # 获取该分类下的所有文章
# 方式二:使用related_name(若设置了related_name="articles")
articles = category.articles.all()
# 3. 多对多关联查询(用户与课程)
user = User.objects.get(id=1)
courses = user.courses.all() # 正向查询:获取用户选的所有课程
course = Course.objects.get(id=1)
users = course.users.all() # 反向查询:获取选该课程的所有用户
5.3 数据更新(Update)
更新数据有两种方式,适用于单条数据更新和批量数据更新。
5.3.1 方式一:实例化 + save()
查询出模型实例,修改字段值,再调用 save() 方法保存(适合单条数据更新,会触发模型的 save() 方法和信号)。
# 查询用户
user = User.objects.get(id=1)
# 修改字段值
user.username = "new_test"
user.email = "new_test@example.com"
# 保存更新(执行UPDATE SQL)
user.save()
5.3.2 方式二:QuerySet.update()
通过 QuerySet 的 update() 方法批量更新数据,返回更新的记录数(适合多条数据更新,效率更高,不触发模型的 save() 方法和信号)。
# 将所有用户名包含“test”的用户,邮箱改为“update@example.com”
updated_count = User.objects.filter(username__icontains="test").update(email="update@example.com")
print(updated_count) # 输出更新的记录数
5.4 数据删除(Delete)
删除数据同样支持单条删除和批量删除,删除后无法恢复,需谨慎操作。
5.4.1 方式一:实例化 + delete()
查询出模型实例,调用 delete() 方法删除单条数据。
# 查询用户并删除
user = User.objects.get(id=1)
user.delete() # 执行DELETE SQL,删除该用户
5.4.2 方式二:QuerySet.delete()
通过 QuerySet 的 delete() 方法批量删除数据,返回删除的记录数(包含关联数据的级联删除)。
# 删除所有用户名包含“test”的用户
deleted_count, deleted_details = User.objects.filter(username__icontains="test").delete()
print(deleted_count) # 输出删除的总记录数
print(deleted_details) # 输出各模型删除的记录数(如{'myapp.User': 3})
说明:删除操作会触发外键的 on_delete 行为(如级联删除关联数据),需提前确认on_delete 的配置。
六、模型迁移:同步模型与数据库
当模型发生修改(如添加字段、修改字段类型、删除模型)时,需要通过“迁移”(Migrations)将修改同步到数据库,确保模型与数据库表结构一致。Django4 提供了完整的迁移工具,无需手动编写 SQL 语句。
6.1 迁移的核心命令
迁移相关的命令均通过 manage.py 执行,常用命令如下:
- makemigrations:基于模型的修改,生成迁移文件(存储在应用的
migrations目录下,记录模型的修改记录)。
`# 生成所有应用的迁移文件
python manage.py makemigrations
生成指定应用的迁移文件(推荐,更精准)
python manage.py makemigrations myapp
为迁移文件指定自定义名称(便于区分)
python manage.py makemigrations myapp --name add_age_field`
- migrate:执行迁移文件,将模型修改同步到数据库(核心命令)。`# 执行所有未执行的迁移文件
python manage.py migrate
执行指定应用的迁移文件
python manage.py migrate myapp
回滚到指定迁移版本(如回滚到0001_initial)
python manage.py migrate myapp 0001_initial`
- showmigrations:查看所有迁移文件的执行状态(是否已执行)。
`python manage.py showmigrations
查看指定应用的迁移状态
python manage.py showmigrations myapp`
- sqlmigrate:查看指定迁移文件对应的 SQL 语句(用于调试,不执行 SQL)。
# 查看myapp应用0002_add_age_field迁移文件的SQL python manage.py sqlmigrate myapp 0002_add_age_field
6.2 迁移的工作流程
标准的迁移工作流程如下,适用于所有模型修改场景:
-
修改模型(如添加字段、修改字段类型、删除模型);
-
执行
python manage.py makemigrations,生成迁移文件(确认迁移内容是否正确); -
执行
python manage.py migrate,将修改同步到数据库; -
(可选)执行
python manage.py showmigrations,确认迁移已执行。
6.3 迁移的注意事项
-
迁移文件是项目的一部分,需提交到版本控制(如 Git),确保团队成员、测试环境、生产环境的迁移文件一致。
-
修改已存在的字段(如修改
max_length、将null=False改为null=True)时,需确保数据库中已有数据符合新的约束(否则迁移会失败)。 -
删除模型或字段时,迁移会同步删除数据库中的表或字段,删除前需备份数据(避免数据丢失)。
-
不同数据库(如 MySQL、PostgreSQL、SQLite)对迁移的支持不同:
-
PostgreSQL:对架构变更支持最好,支持 DDL 事务,迁移失败可回滚;
-
MySQL:不支持 DDL 事务,迁移失败需手动恢复;
-
SQLite:缺乏架构变更支持,Django 会通过“创建新表→复制数据→删除旧表”模拟迁移,适合开发环境,不推荐生产环境使用。
-
-
若迁移文件出错,可删除对应迁移文件(需确保该迁移未执行),重新执行
makemigrations生成新的迁移文件;若已执行,需回滚迁移后再删除。
七、模型管理器:自定义数据操作接口
模型管理器(Manager)是 Django 模型与数据库交互的接口,每个模型默认有一个名为 objects 的管理器,通过它调用 all()、filter()、create() 等方法。Django4 支持自定义管理器,用于添加自定义数据操作方法、修改默认查询集等。
7.1 管理器的基础使用
默认管理器 objects 是 django.db.models.Manager 类的实例,若想重命名管理器(如避免 objects 与字段名冲突),可在模型中直接定义管理器实例。
from django.db import models
class Person(models.Model):
# 重命名管理器为people,替代默认的objects
people = models.Manager()
name = models.CharField(max_length=50)
age = models.IntegerField(default=0)
# 使用自定义管理器查询数据
persons = Person.people.all() # 正确
# Person.objects.all() 会抛出AttributeError异常
7.2 自定义管理器
自定义管理器需继承 models.Manager 类,通过重写方法或添加新方法,实现自定义数据操作逻辑。常见场景有两种:添加额外的管理器方法、修改默认查询集。
7.2.1 添加额外的管理器方法
添加额外方法用于实现“表级”操作(如统计特定条件的数据、批量处理数据),区别于“行级”操作(模型实例的方法)。
from django.db import models
from django.db.models.functions import Coalesce
# 自定义管理器
class PollManager(models.Manager):
# 自定义方法:获取带有回复数量的投票
def with_counts(self):
# annotate()用于添加额外字段(此处统计回复数量)
return self.annotate(num_responses=Coalesce(models.Count("response"), 0))
# 投票模型(关联回复模型)
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
# 使用自定义管理器替代默认objects
objects = PollManager()
# 回复模型
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
content = models.TextField()
# 使用自定义管理器方法
polls = OpinionPoll.objects.with_counts()
for poll in polls:
print(f"投票问题:{poll.question},回复数量:{poll.num_responses}")
说明:自定义管理器方法可以返回任意类型的数据,不一定是 QuerySet;方法中可通过 self.model 获取关联的模型类。
7.2.2 修改默认查询集
通过重写 get_queryset() 方法,修改管理器默认返回的查询集(如默认只查询有效数据、按特定条件排序)。
from django.db import models
# 自定义管理器:只查询Roald Dahl写的书籍
class DahlBookManager(models.Manager):
def get_queryset(self):
# 调用父类方法,再添加筛选条件
return super().get_queryset().filter(author="Roald Dahl")
# 书籍模型
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
# 默认管理器(返回所有书籍)
objects = models.Manager()
# 自定义管理器(只返回Roald Dahl的书籍)
dahl_objects = DahlBookManager()
# 使用默认管理器:查询所有书籍
all_books = Book.objects.all()
# 使用自定义管理器:只查询Roald Dahl的书籍
dahl_books = Book.dahl_objects.all()
八、模型高级特性
8.1 模型方法
模型方法是定义在模型类中的方法,用于实现“行级”操作(操作单个模型实例的数据),常用场景:数据验证、自定义实例行为。
class User(models.Model):
username = models.CharField(max_length=50, unique=True)
password = models.CharField(max_length=128)
age = models.IntegerField(default=0)
# 自定义模型方法:判断用户是否为成年人
def is_adult(self):
return self.age >= 18
# 重写save()方法:保存前加密密码(示例)
def save(self, *args, **kwargs):
# 这里可添加密码加密逻辑(如使用Django内置的make_password)
from django.contrib.auth.hashers import make_password
self.password = make_password(self.password)
# 调用父类的save()方法,完成保存
super().save(*args, **kwargs)
说明:重写 save() 方法时,需调用 super().save(*args, **kwargs),否则无法完成数据保存;模型方法的第一个参数必须是 self(指向当前模型实例)。
8.2 模型信号
Django4 提供了模型信号(Signal)机制,用于在模型的特定生命周期(如保存、删除、更新)触发自定义逻辑,无需修改模型本身的代码,实现解耦。
常用的模型信号:
-
pre_save:模型实例保存前触发; -
post_save:模型实例保存后触发; -
pre_delete:模型实例删除前触发; -
post_delete:模型实例删除后触发。
示例:使用 post_save 信号,用户创建后自动创建用户详情:
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile # 导入用户详情模型
# receiver装饰器:接收post_save信号,指定sender为User模型(只有User模型保存时才触发)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""
当User模型实例保存后触发:
- sender:发送信号的模型(此处是User)
- instance:保存的User实例(当前创建/更新的用户)
- created:布尔值,True表示是新创建的用户,False表示是更新现有用户
"""
# 只有新创建用户时,才自动创建对应的用户详情
if created:
UserProfile.objects.create(user=instance) # 关联当前用户实例
# 可选:用户更新时,自动更新用户详情(如需)
@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if not created:
# 若用户详情已存在,则更新;若不存在,则创建(避免异常)
UserProfile.objects.get_or_create(user=instance)
# 注意:信号需在应用的apps.py中注册,确保Django能识别
# 示例(apps.py):
from django.apps import AppConfig
class MyappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
# 应用就绪时,导入信号模块(触发信号注册)
def ready(self):
import myapp.signals # 替换为你的信号所在路径
信号使用说明:
-
信号的核心作用是“解耦”:无需在User模型的save()方法中添加创建UserProfile的逻辑,只需通过信号监听,降低代码耦合度。
-
@receiver装饰器用于绑定信号和处理函数,sender参数指定触发信号的模型,避免其他模型触发该逻辑。
-
created参数是关键:区分“创建用户”和“更新用户”,避免每次更新用户都重复创建用户详情。
-
信号必须注册:需在应用的apps.py中ready()方法导入信号模块,否则Django无法检测到信号,处理函数不会触发。
8.3 模型继承
Django4 支持模型继承,类似Python类的继承,用于提取多个模型的公共字段和方法,减少代码冗余。常用的继承方式有三种:抽象基类继承、多表继承、代理模型。
8.3.1 抽象基类继承(最常用)
抽象基类本身不生成数据库表,仅用于被其他模型继承,将公共字段和方法提取到基类中,子类继承后会拥有基类的所有字段和方法,并生成包含所有字段的数据库表。
from django.db import models
# 抽象基类(添加abstract=True,表明是抽象类)
class BaseModel(models.Model):
# 公共字段:所有子类都会继承
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
is_delete = models.BooleanField(default=False, verbose_name="是否删除") # 软删除字段
# 公共方法:所有子类都会继承
def soft_delete(self):
# 软删除:不真正删除数据,仅标记is_delete为True
self.is_delete = True
self.save()
# 元数据:设置为抽象类
class Meta:
abstract = True # 关键:标记该模型为抽象基类,不生成数据库表
# 子类1:文章模型,继承BaseModel
class Article(BaseModel):
title = models.CharField(max_length=100, verbose_name="文章标题")
content = models.TextField(verbose_name="文章内容")
# 子类2:评论模型,继承BaseModel
class Comment(BaseModel):
article = models.ForeignKey(Article, on_delete=models.CASCADE, verbose_name="关联文章")
content = models.TextField(verbose_name="评论内容")
user = models.CharField(max_length=50, verbose_name="评论人")
说明:
-
抽象基类必须设置
abstract=True,否则Django会将其视为普通模型,生成数据库表。 -
子类会自动继承基类的所有字段和方法,无需重复定义;子类可添加自己的独有字段和方法。
-
每个子类会生成独立的数据库表,表中包含基类的所有字段和自身的独有字段(如Article表包含create_time、update_time、is_delete、title、content)。
8.3.2 多表继承
多表继承是指父类和子类都生成独立的数据库表,子类通过一对一关系与父类关联,子类不仅继承父类的字段和方法,还能通过父类实例访问子类数据(反向也可)。
from django.db import models
# 父类:Person模型(生成数据库表person)
class Person(models.Model):
name = models.CharField(max_length=50, verbose_name="姓名")
age = models.IntegerField(default=0, verbose_name="年龄")
# 子类:Student模型(生成数据库表student,与person表一对一关联)
class Student(Person):
student_id = models.CharField(max_length=20, unique=True, verbose_name="学号")
school = models.CharField(max_length=100, verbose_name="学校")
# 子类:Teacher模型(生成数据库表teacher,与person表一对一关联)
class Teacher(Person):
teacher_id = models.CharField(max_length=20, unique=True, verbose_name="工号")
subject = models.CharField(max_length=50, verbose_name="教授科目")
说明:
-
父类(Person)和子类(Student、Teacher)都会生成独立的数据库表,子类表中会自动添加一个一对一外键,关联父类表。
-
可通过父类实例访问子类数据(如
person.student获取该人员对应的学生信息),也可通过子类实例访问父类数据(如student.name获取学生姓名)。 -
多表继承适用于“父类和子类都需要独立存储数据”的场景,缺点是查询时会产生关联查询,效率略低于抽象基类继承。
8.3.3 代理模型
代理模型不生成新的数据库表,仅为原模型(父类)创建一个“代理”,用于修改原模型的查询集、管理器等行为,不添加新的字段。
from django.db import models
# 原模型:Book
class Book(models.Model):
title = models.CharField(max_length=100, verbose_name="书名")
author = models.CharField(max_length=50, verbose_name="作者")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格")
# 代理模型:CheapBook(代理Book,不生成新表)
class CheapBook(Book):
# 元数据:设置为代理模型,指定代理的原模型
class Meta:
proxy = True # 关键:标记为代理模型
ordering = ["price"] # 修改默认排序方式(原模型不变)
# 自定义方法(仅代理模型可用,原模型不受影响)
def is_cheap(self):
return self.price < 50
# 使用代理模型
cheap_books = CheapBook.objects.all() # 查询的是Book表的数据,默认按价格升序
for book in cheap_books:
print(f"{book.title}:{book.price}元,是否廉价:{book.is_cheap()}")
说明:
-
代理模型必须设置
proxy=True,且必须继承自一个非抽象的原模型。 -
代理模型不生成新表,所有操作(增删改查)都会作用于原模型的数据库表。
-
代理模型可修改原模型的默认排序、管理器、添加自定义方法,但不能添加新的字段。
九、模型实战技巧与避坑指南
9.1 实战技巧
-
软删除替代硬删除:避免使用Django默认的硬删除(delete()方法),通过添加
is_delete布尔字段实现软删除(标记删除状态,不真正删除数据),便于数据恢复和历史查询(如前文抽象基类中的soft_delete方法)。 -
合理使用索引:对频繁用于查询、筛选、排序的字段(如username、email、foreignkey字段)添加索引(通过Meta类的indexes参数),提升查询效率;避免对频繁更新的字段添加索引(索引会降低更新效率)。
-
优化关联查询:多表关联查询时,使用
select_related()(用于一对一、一对多关系)和prefetch_related()(用于多对多关系)减少数据库查询次数,避免“N+1查询”问题。
`# 优化一对多关联查询(查询文章时,同时查询关联的分类)
articles = Article.objects.select_related(“category”).all()
无需再执行额外查询,直接获取分类:article.category.name
优化多对多关联查询(查询用户时,同时查询关联的课程)
users = User.objects.prefetch_related(“courses”).all() # 无需再执行额外查询,直接获取课程:user.courses.all()`
- 使用choices字段规范枚举值:对于状态、类型等固定枚举值(如用户性别、订单状态),使用CharField+choices参数,避免存入无效值,且表单中会自动生成下拉框,提升开发效率。
`class Order(models.Model):订单状态:待支付、已支付、已取消、已完成
ORDER_STATUS = (
(“UNPAID”, “待支付”),
(“PAID”, “已支付”),
(“CANCELLED”, “已取消”),
(“COMPLETED”, “已完成”),
)status = models.CharField(max_length=20, choices=ORDER_STATUS, default=“UNPAID”, verbose_name=“订单状态”)`
9.2 常见坑点与避坑指南
-
坑点1:null与blank的混淆解决方案:明确两者的区别——null是数据库层面的约束(允许存储NULL),blank是表单层面的约束(允许为空);字符串类型字段(CharField、TextField)优先设置
blank=True,避免设置null=True,防止出现NULL和空字符串两种空值。 -
坑点2:ForeignKey未指定on_delete参数解决方案:Django4中,ForeignKey必须指定on_delete参数,根据业务需求选择合适的删除行为(如级联删除CASCADE、保护PROTECT),避免迁移失败或数据不一致。
-
坑点3:迁移文件未提交版本控制解决方案:迁移文件是项目的核心组成部分,必须提交到Git等版本控制工具,确保团队成员、测试环境、生产环境的迁移文件一致,避免迁移冲突。
-
坑点4:批量操作(bulk_create、update)不触发save()和信号解决方案:bulk_create、update等批量操作效率高,但不会触发模型的save()方法和post_save等信号;若需要触发信号或执行save()中的逻辑,需手动处理(如循环调用save(),或在批量操作后手动触发信号)。
-
坑点5:多对多关系自定义中间表后,使用add()/remove()方法解决方案:自定义中间表(通过through参数)后,Django不允许使用add()、remove()、clear()方法操作多对多关系,需通过中间表的模型实例(如Enroll.objects.create())进行操作。
十、总结
Django4 的模型与 ORM 系统是其核心竞争力之一,本文从模型基础、字段定义、元数据配置、表关系、ORM数据操作、迁移、管理器、高级特性等方面,全面解析了模型与数据相关的核心知识,覆盖从基础到实战的所有关键要点。
核心要点回顾:
-
模型是数据的“蓝图”,必须继承
models.Model,字段是模型的核心,需根据业务需求选择合适的字段类型和参数。 -
表关系分为一对多(ForeignKey)、一对一(OneToOneField)、多对多(ManyToManyField),需掌握每种关系的实现方式和查询方法。
-
ORM 提供了简洁的 CRUD 操作,无需编写原生 SQL,同时支持高级查询、批量操作等优化方式。
-
迁移是同步模型与数据库的关键,需熟练掌握迁移命令和注意事项,避免迁移冲突和数据丢失。
-
模型高级特性(信号、继承、自定义管理器)可提升代码复用性和开发效率,需根据业务场景合理使用。
掌握 Django4 模型与数据操作,能快速实现数据的存储、查询和管理,为 Django 项目开发奠定坚实基础。在实际开发中,需结合业务需求灵活运用各类知识点,同时注意避坑,确保代码的高效性和可维护性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)