前面我们介绍过Django是大而全的框架,它甚至内置了一个强大的ORM功能。Django ORM(对象关系映射)是Django框架提供的用于与数据库交互的工具,它允许开发者使用Python代码定义数据模型并直接创建、读取、更新和删除数据库中的记录,而无需直接编写具体的SQL语句。
Django支持许多关系型数据库,包括SQLite3、MySQL、PostgreSQL、Oracle等,对于轻量级的个人项目推荐使用SQLite3,对于有一定性能要求的项目推荐使用MySQL或PostgreSQL,下面我们简单了解下如何配置Sqlite3和MySQL数据库作为Django的数据源。
对于没有性能要求的小型应用,如个人博客系统等,使用单文件存储的嵌入式数据库SQLite3是很好的选择。
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Django如果使用MySQL需要额外安装mysqlclient
这个库作为其后端(backend)。
pip3 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语句操作数据库,这里我们可以使用python3 manage.py makemigrations demo01 && python3 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
使其符合中文的习惯。
有关具体有哪些类型的字段,可以参考下表。
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索引,前者通常有更好的查找性能。
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模块的数据迁移。
python3 manage.py makemigrations demo01
由于我们在INSTALLED_APPS
里配置了很多模块,包括内置模块和我们自己编写的模块,这些模块需要很多数据库表的支持,这条指令会创建对应demo01
模块需要的数据库迁移操作语句。命令执行后,我们可以看到demo01
目录下生成了一个migrations
代码包,其中包含了具体操作数据库的数据迁移脚本,数据迁移脚本在99%的情况下都是自动生成的,我们几乎不需要手动修改它,这也意味着我们其实并不需要直接操作数据库,数据库是由Django自动管理的,我们甚至可以完全不关心底层数据库,这种开发模式被称为Model First。
补充知识:与Model First相对的是Table First,即先在数据库建表,然后再手动或使用某种自动化工具生成代码中的数据模型类,Django确实支持这种开发模式,manage.py
管理命令工具中提供了从数据表逆向生成数据模型的方法,我们可以执行python3 manage.py inspectdb > models.py
实现这一过程,不过这种开发模式在规模庞大的企业级项目中才比较常见(尤其是Java领域,这里所谓的“规模庞大”是指代码规模、开发人员、支撑团队的“大”,以及研发和运维都有严格的企业级流程和标准),使用Django的小项目还是推荐Model First开发模式。这里我们了解即可,有关两种开发模式的对比这里不做过多讨论。
前面我们创建了数据迁移脚本,使用下面的命令可以查看APP模块中第0001次数据迁移的具体SQL操作。
python3 manage.py sqlmigrate demo01 0001
最后,我们执行下面命令,真正的将所有SQL操作写入数据库。
python3 manage.py migrate