数据模型
前面我们介绍过Django是大而全的框架,它甚至内置了一个强大的ORM功能。Django ORM(对象关系映射)是Django框架提供的用于与数据库交互的工具,它允许开发者使用Python代码定义数据模型并直接创建、读取、更新和删除数据库中的记录,而无需直接编写具体的SQL语句。
数据库配置
Django支持许多关系型数据库,包括SQLite3、MySQL、PostgreSQL、Oracle等,对于轻量级的个人项目推荐使用SQLite3,对于有一定性能要求的项目推荐使用MySQL或PostgreSQL,下面我们简单了解下如何配置Sqlite3和MySQL数据库作为Django的数据源。
SQLite3数据库配置
对于没有性能要求的小型应用,如个人博客系统等,使用单文件存储的嵌入式数据库SQLite3是很好的选择。
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
- ENGINE:要使用的持久层后端,这里指定为SQLite3
- NAME:SQLite3对应的数据库文件位置,如果不存在会自动创建
MySQL数据库配置
Django如果使用MySQL需要额外安装mysqlclient这个库作为其后端(backend)。
pip install mysqlclient
实际上,Python连接MySQL数据库的后端有好几种,Django支持作为后端的也不止一种,但目前版本最新的Python3只支持mysqlclient。配置文件如下,注意这里我们要预先手动在MySQL中创建好数据库(Schema)。
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '数据库名',
'USER': '用户名',
'PASSWORD': '密码',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'autocommit': True
}
}
}
注意:Django的ORM自动建表后,默认使用的文本编码就是取的MySQL数据库默认编码,为了方便,创建新数据库时我们可以统一将数据库的字符编码设定为utf8mb4,否则默认为latin1,插入中文字段会报错。
数据模型和关联关系定义
下面例子中我们编写了4个数据模型,分别是ClassRoom(教室)、Student(学生)、Desk(课桌)、Teacher(教师),其中教室和学生是一对多关系,学生和课桌是一对一关系,学生和教师的多对多关系。
models.py
from django.db import models
class ClassRoom(models.Model):
class Meta:
verbose_name = '教室'
verbose_name_plural = '教室'
room_code = models.CharField(max_length=10, verbose_name='教室号')
class Student(models.Model):
class Meta:
verbose_name = '学生'
verbose_name_plural = '学生'
stu_code = models.CharField(max_length=10, unique=True, verbose_name='学号')
name = models.CharField(max_length=20, verbose_name='学生姓名')
age = models.IntegerField(default=-1, verbose_name='年龄')
class_room = models.ForeignKey(ClassRoom, null=True, on_delete=models.DO_NOTHING, verbose_name='所在教室')
class Desk(models.Model):
class Meta:
verbose_name = '课桌'
verbose_name_plural = '课桌'
desk_code = models.CharField(max_length=10, db_index=True, verbose_name='桌号')
student = models.OneToOneField(Student, null=True, on_delete=models.DO_NOTHING, verbose_name='归属学生')
class Teacher(models.Model):
class Meta:
verbose_name = '教师'
verbose_name_plural = '教师'
name = models.CharField(max_length=20, default='匿名', verbose_name='教师姓名')
subject = models.CharField(max_length=20, verbose_name='教授科目')
students = models.ManyToManyField(Student, verbose_name='关联学生')
Django ORM的开发流程是Model First,即先定义数据模型,再根据数据模型自动创建数据迁移脚本,数据迁移脚本负责具体执行DDL语句操作数据库,这里我们可以使用python manage.py makemigrations demo01 && python manage.py migrate创建数据迁移脚本并执行,将数据表创建到数据库。有关数据迁移的说明将在后文介绍。
如下图所示是数据模型对应生成数据表的ER图,这里我们的APP名为demo01,数据表名默认是APP名_数据模型名(小写)。

持久化类要继承models.Model类,类属性可以是model.CharField、models.IntegerField、models.DateTimeField等,对应文本数据、整型数据、日期等数据类型,类属性名约定为数据库字段名。对于每个属性,我们还可以指定一些约束,如max_length等,用于辅助生成数据表时指定长度;verbose_name也是一个常用的属性,它其实原本用于在django-admin扩展中显示字段说明,我们主要将其当作字段的注释使用,防止我们忘记某个字段是什么意思。
每个数据模型类中还有一个Meta子类,它用于定义数据模型的一些辅助元信息,这里我们设置了verbose_name字段,它和字段的verbose_name一样也是用于django-admin中显示数据模型名的,我们其实主要将其当作注释使用,没有特别的用途;至于verbose_name_plural它是verbose_name的复数表示,默认值是verbose_name加字母s,这种写法仅适用于英文,因此我们手动指定verbose_name_plural使其符合中文的习惯。
Django ORM字段
有关具体有哪些类型的字段,可以参考下表。
| ORM字段类型 | 数据库字段类型(以MySQL为例) | 说明 |
|---|---|---|
| AutoField | INT AUTO_INCREMENT | 自增整数,用于自增主键字段,通常无需指定,Django数据模型对应的表默认就会添加该类型主键。 |
| BigAutoField | BIGINT AUTO_INCREMENT | 类似自增整数,但使用BIGINT数据库类型。 |
| BigIntegerField | BIGINT | 存储大整数,范围为-9223372036854775808到9223372036854775807。 |
| BinaryField | LONG BLOB | 二进制数据。 |
| BooleanField | TINYINT(1) | 存储布尔值,使用0表示False,1表示True。 |
| CharField | VARCHAR(n) | 存储字符串,根据max_length属性设置数据库变长字符串的长度限制。 |
| DateField | DATE | 存储日期。 |
| DateTimeField | DATETIME | 存储日期和时间。 |
| DecimalField | DECIMAL(p, s) | 存储定点数,p为总位数,s为小数位数,这两个值取自数据模型中的max_digits和decimal_places属性定义。 |
| DurationField | BIGINT | 存储时间间隔。 |
| EmailField | VARCHAR(254) | 存储电子邮件地址,默认为最大254个字符。 |
| FileField | VARCHAR(100) | 存储文件路径,用于文件上传。 |
| FilePathField | VARCHAR(100) | 存储文件系统中的文件路径。 |
| FloatField | DOUBLE | 存储浮点数。 |
| GenericIPAddressField | CHAR(39) | 存储IPv4或IPv6地址。 |
| ImageField | VARCHAR(100) | 存储图像文件路径,用于图像上传。 |
| JSONField | JSON | 存储JSON格式数据,该数据类型在MySQL 5.7及以上版本支持。 |
| PositiveBigIntegerField | BIGINT | 存储正的大整数。 |
| PositiveIntegerField | INT | 存储正整数。 |
| PositiveSmallIntegerField | SMALLINT | 存储正小整数。 |
| SlugField | VARCHAR(50) | 存储用于URL的简短、唯一的字符串。 |
| SmallAutoField | SMALLINT AUTO_INCREMENT | 自增小整数,适合记录数量较少的场景。 |
| SmallIntegerField | SMALLINT | 存储小整数,范围为-32768到32767。 |
| TextField | LONG TEXT | 存储大文本,LONG TEXT长度大到可以简单认为不受限。 |
| TimeField | TIME | 存储时间数据。 |
| URLField | VARCHAR(200) | 存储URL地址,默认最大200个字符。 |
| UUIDField | CHAR(32) | 存储UUID。 |
除了常见的数据字段,上表中还有FileField、ImageField等用于存储文件和图片的字段,不过注意它们并不是存储实际文件的二进制数据,而是存储文件在磁盘上的路径,在数据库中存储二进制数据一般都被认为不是个好主意,这些字段的使用涉及用Django实现文件上传,将在后续章节介绍。
模型关联关系
模型类中,除了具体的数据字段,我们还使用models.OneToOneField、models.ForeignKey和models.ManyToManyField定义了各种关联关系。其中models.OneToOneField和models.ForeignKey会在数据库中定义外键关联,models.ForeignKey属性需要放在一对多中“多”的一方,例如教室和学生有一对多的关联关系,关联属性定义在学生数据模型中,外键字段也会在学生表中建立;对于models.ManyToManyField,根据我们对数据库的了解,我们知道多对多关系需要使用中间表实现,因此多对多的models.ManyToManyField可以定义在任意一方,只要方便我们的业务逻辑实现即可;至于models.OneToOneField它可以理解为一种有限制的一对多关系,一对一外键也可以放在双方的任意一方。
定义一对一和一对多关联关系时,on_delete指定外键触发逻辑(即“一”的一方删除时对于“多”的一方的处理逻辑),以学生-教室关系为例,如果定义on_delete=models.CASCADE,那么教室删除时将触发外键删除,所有该教室下的学生也将被删除;如果定义on_delete=models.DO_NOTHING则学生不会被删除,外键字段会保持不变。
指定字段长度
前面我们已经使用过max_length属性了,具体来说,底层使用VARCHAR类型存储的字段,其长度将被设置为max_length属性值。对于这类字段,max_length通常是必须指定的。
name = models.CharField(max_length=20, verbose_name='教师姓名')
可空字段
Django中字段默认是不可为空的,即数据库表中不允许设置为NULL值。如果希望允许设置NULL值,可指定null=True,此外对于底层采用字符串的数据字段,指定blank=True表示允许存入空字符串。
name = models.CharField(max_length=20, verbose_name='教师姓名', null=True, blank=True)
字段默认值
default属性可以设置字段的默认值,即插入数据未指定时设置的默认值。
name = models.CharField(max_length=20, default='匿名', verbose_name='教师姓名')
唯一性约束
指定unique=True可以为字段添加唯一性索引。下面例子中,显然学号就很适合设置为唯一字段。
stu_code = models.CharField(max_length=10, unique=True, verbose_name='学号')
添加索引
指定db_index=True可以为字段添加索引,MySQL中默认会添加BTREE索引。
desk_code = models.CharField(max_length=10, db_index=True, verbose_name='桌号')
注意:如果字段唯一,我们应该添加UNIQUE索引,而不是BTREE索引,前者通常有更好的查找性能。
choices约束
Django支持choices约束,它是在代码层面实现的而非底层数据库,choices约束类似枚举类型,限制字段只能指定某些值之一。下面例子中,我们限制卡券类型字段的值必须取自coupon_type_choices,即仅能取1或2。
coupon_type_choices = (
(1, '无限制卡券'),
(2, '一次性卡券'),
)
class Coupon(models.Model):
class Meta:
verbose_name = '卡券'
verbose_name_plural = '卡券'
type = models.IntegerField(choices=coupon_type_choices, verbose_name='卡券类型')
coupon_code = models.CharField(max_length=255, verbose_name='卡券码')
创建和执行数据迁移
前面我们创建并执行过数据迁移脚本,在Django中,它实际上是两个步骤。这里我们的APP名为demo01,执行下面的命令将创建该APP模块的数据迁移。
python manage.py makemigrations demo01
由于我们在INSTALLED_APPS里配置了很多模块,包括内置模块和我们自己编写的模块,这些模块需要很多数据库表的支持,这条指令会创建对应demo01模块需要的数据库迁移操作语句。命令执行后,我们可以看到demo01目录下生成了一个migrations代码包,其中包含了具体操作数据库的数据迁移脚本,数据迁移脚本在99%的情况下都是自动生成的,我们几乎不需要手动修改它,这也意味着我们其实并不需要直接操作数据库,数据库是由Django自动管理的,我们甚至可以完全不关心底层数据库,这种开发模式被称为Model First。
补充知识:与Model First相对的是Table First,即先在数据库建表,然后再手动或使用某种自动化工具生成代码中的数据模型类,Django确实支持这种开发模式,manage.py管理命令工具中提供了从数据表逆向生成数据模型的方法,我们可以执行python manage.py inspectdb > models.py实现这一过程,不过这种开发模式在规模庞大的企业级项目中才比较常见(尤其是Java领域,这里所谓的“规模庞大”是指代码规模、开发人员、支撑团队的“大”,以及研发和运维都有严格的企业级流程和标准),使用Django的小项目还是推荐Model First开发模式。这里我们了解即可,有关两种开发模式的对比这里不做过多讨论。
前面我们创建了数据迁移脚本,使用下面的命令可以查看APP模块中第0001次数据迁移的具体SQL操作。
python manage.py sqlmigrate demo01 0001
最后,我们执行下面命令,真正的将所有SQL操作写入数据库。
python manage.py migrate