ORM与模型
# ORM与模型
# ORM
对象关系映射(Object Relational Mapping,简称ORM)!简单的说就是用面向对象的方式,描述数据库,操作数据库,达到不用编写SQL语句就能对数据库进行增删改查。
django内置了一套ORM框架,它的映射关系是:
然后通过对类,类属性,实例的各种操作,达到操作数据库的功能,底层是生成原生sql语句进行数据库操作。
# 安装数据库
我们的项目选择使用MariaDB
,它是MySQL的一个分支,开源免费,越来越多的web项目开始使用它。直接安装数据库相对比较麻烦和难以维护,推荐使用docker安装数据库。安装docker的方案如下:
- 方案一:
windows直接安装Docker Desktop,不推荐,个人感觉还是很影响系统的使用,且需要开启虚拟服务,会与某些软件冲突。
mac可以直接安装Docker Desktop。
- 方案二:
windows系统,安装虚拟机,然后安装linux的虚拟机,再到虚拟机中安装docker环境。
虚拟机软件有,virtualbox(免费开源),vimware(收费),安装简单,使用稍复杂,资源占用大。
用过虚拟机的童靴可以选择此方案,从来没用过的可以忽略。
mac不建议。
- 方案三:
买一台云服务器,在云服务器中安装docker。
阿里云,百度云,腾讯云,华为云,都有新用户优惠,几十块钱一年,推荐使用。
最后项目的部署也会使用云服务器。
阿里云新人优惠连接 (opens new window)
注意:系统选择ubuntu
或者centos
。
docker命令:
docker run --name mariadb --restart=always -d -v mariadb:/var/lib/mysql -e MARIADB_ROOT_PASSWORD=pythonvip -p 4000:3306 -e MARIADB_DATABASE=easytest mariadb:latest
python
上面这条命令会根据mariadb数据库镜像mariadb:latest
,创建一个名为mariadb
的容器,并创建一个名为easytest
的数据库,设置root
账号的密码为pythonvip
,映射3306端口到宿主机4000端口。
# django配置数据库
# 安装驱动
django官方支持一下数据库:
- PostgreSQL (opens new window)
- MariaDB (opens new window)
- MySQL (opens new window)
- Oracle (opens new window)
- SQLite (opens new window)
不同的数据库都需要对应的python数据库驱动程序。
我们接下来使用MariaDB数据库,它是MySQL的一个分支,在Django中它的配置和MySQL一致。
django推荐使用mysqlclient
作为MySQL
或MariaDB
的数据库驱动。
# windows环境
在网站https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient
(opens new window)下载与python版本对应的mysqlclient
本地安装文件,再使用pip命令安装,例如:
pip install mysqlclient‑1.4.6‑cp38‑cp38‑win_amd64.whl # py3.8 64位
# mac环境
mac环境下依赖mysql客户端。
$ brew install mysql-client
$ echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile
$ source .bash_profile
$ pip install mysqlclient
2
3
4
# linux环境
linux环境下需要对应的依赖,根据环境不同依赖有所不同,下面的只是基本的步骤,不能保证在所有的环境上都有效。
Debian/Ubuntu
$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential
$ pip install mysqlclient
2
Red Hat /CentOS
sudo yum install python3-devel mysql-devel
pip install mysqlclient
2
# 连接配置
安装好数据库和数据库驱动后,还需要在settings.py
配置模块中进行连接配置:
- 直接配置
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 对应数据库引擎
'NAME': 'easytest', # 数据库名
'USER': 'root', # 用户名
'PASSWORD': '12345678', # 密码
'HOST': '127.0.0.1', # 主机
'PORT': '3306', # 端口
}
}
2
3
4
5
6
7
8
9
10
11
- 通过配置文件配置
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {
'read_default_file': '/path/to/my.cnf', # 配置文件路径
},
}
}
# my.cnf
database = easytest # 数据库名
user = root # 用户名
password = 12345678 # 密码
host = 127.0.0.1 # 主机
port = 3306 # 端口
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
配置好后,运行python manage.py runserver
如果可以正常运行说明数据库配置成功,项目成功连接数据库。
# 模型
Django中模型准确且唯一的描述了数据。它包含储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。
- 每个模型都是一个 Python 的类,这些类继承
django.db.models.Model
- 模型类的每个属性都相当于一个数据库的字段
- 利用这些,Django 提供了一个自动生成访问数据库的 API
# 创建模型
在创建模型前,先为我们的crm
系统设计一张student
表,表结构如下:
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NUll,
`age` tinyint(1) DEFAULT NULL,
`sex` tinyint(1) DEFAULT 1,
`phone` varchar(20) DEFAULT NULL UNIQUE,
`c_time` datetime(6) NOT NULL
2
3
4
5
6
然后根据表设计,在crm/models.py
中编写如下模型类:
# models.py
class Student(models.Model):
name = models.CharField('姓名', max_length=20, help_text='姓名')
age = models.SmallIntegerField('年龄', null=True, blank=True, help_text='年龄')
sex = models.SmallIntegerField('性别', default=1, help_text='性别')
phone = models.CharField('手机号码', max_length=20, null=True, blank=True, unique=True, help_text='手机号码')
c_time = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
db_table = 'tb_student'
verbose_name = '学生信息'
verbose_name_plural = verbose_name
ordering = ['-c_time']
def __str__(self):
return self.name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面的代码很简单,Student
类就是一个模型,表示一张数据库表。类中有几个变量,每个变量表示数据库中的一个字段。
# 字段类型
每个字段由一个字段类的实例表示。
例如:字符字段使用CharField
,日期时间使用DateTimeField
。
每个字段实例的名称(例如,name
,age
)就是字段的名称。数据库将会使用它作为列名。
下面是常用字段类型和数据库字段类型的映射关系:
更多字段类型见官方文档 (opens new window)
# 字段参数
每个字段在实例化时可以接收多个参数,用来提供不同的功能。常用的字段参数有:
verbose_name
人类可读的字段详细名称,如果没有给定会使用字段的属性名自动创建。
primary_key
主键设置,如果为
True
,则将该字段设置为模型的主键。如果编写模型是没有设置任何字段为主键,django会自动添加一个id
字段作为主键。unique
唯一索引,如果为
True
,这个字段会在数据库层面创建唯一索引。blank
如果为
True
,该字段允许为空。默认为False
。注意该字段只与验证相关。null
如果为
True
,django将在数据库存储空值为NULL
。默认为False。注意如果希望表单中允许空值,还需要设置blank=True
,因为null
参数只影响数据库的存储。default
默认值。可以是一个值或者一个调用对象。当创建模型实例且没有为该字段提供值时,使用默认值。
help_text
额外的帮助文本,会随表单控件一同显示。它对生成文档也很有用。
db_column
这个参数可以设置字段的数据库列名。如果不设置默认使用字段名作为数据库列名。
validators
该字段运行的验证器列表。
error_messages
这个参数可以覆盖字段引发的默认错误信息。传入一个与你想覆盖的错误信息相匹配的键值的字典。
max_length
CharFiled
类型字段必须传递的参数,表示该字段的最大长度(以字符为单位)。max_length
会在数据库层面强制执行。
# 自动设置主键
默认情况下,django给每个模型一个自动递增的主键,其类型在AppConfig.default_auto_field
中指定,或者在DEFAULT_AUTO_FIELD
配置中全局指定。例如:
id = models.BigAutoField(primary_key=True)
如果你想自己指定主键,在你想要设置为主键的字段上设置参数primary_key=True
。
注意每个模型都需要一个主键字段。
# Meta选项
在模型内定义Meta
类来给模型赋予元数据。
在我们的例子中db_table='tb_project'
表示创建表的时候指定表名为tb_project
,不使用默认的表名。默认会用应用名_模型名小写
作为表名。
verbose_name
和verbose_name_plural
表示模型的可读名称的单数和复数,主要在admin
站点中使用。
ordering=['-c_time']
表示查询数据时默认按照c_time
字段降序排列。
Meta
选项较多,且和很多高级功能有关,在后面的课程中用到再详细讲解。
更多的选项参考官方文档 (opens new window)
# __str__
方法
每个模型都应该定义一个__str__
方法,当打印模型对象时,它的值友好的展示一个对象。
# 数据库迁移
编写模型后,django会将模型的修改应用至数据库进行关联,还需要将模型和数据库进行关联,这个操作称为迁移。
# 安装应用
要进行模型数据库迁移,首先需要将应用安装到项目中。在配置项INSTALLED_APPS
中添加要安装的应用的设置类。例如,安装crm
应用到项目中的配置如下:
study_django/settings.py
INSTALLED_APPS = [
...
'crm.apps.CrmConfig'
]
2
3
4
5
应用的设置类是自动生成的,在应用根目录的apps.py
模块中,安装应用时,使用点式路径,例如crm.apps.CrmConfig
。
# 生成迁移文件
在命令行输入如下命令:
python mamage.py makemigrations crm
你将会看到类似于下面这样的输出:
Migrations for 'crm':
crm\migrations\0001_initial.py
- Create model Student
2
3
通过运行makemigrations
命令,django会检测你对模型文件的修改(当前是创建),并把修改的部分存储为一次迁移。迁移是django对模型定义的变化的记录。上面的命令会在crm/migrations
目录中生成0001_initial.py
,这就是迁移文件。感兴趣可以打开阅读它们,别担心,不需要每次都阅读迁移文件,但是它被设计成人类可阅读的形式,这是便于手动调整django的修改方式。
# 执行迁移
生成迁移文件之后,还需要执行迁移,同步数据库。django通过命令migrate
来执行迁移。
在运行迁移之前,我们可以看看,迁移会执行哪些SQL
语句。
命令sqlmigrate
接收一个迁移的名称,然后返回对应的SQl
:
python manage.py sqlmigrate crm 0001
输出如下:
--
-- Create model Student
--
CREATE TABLE "tb_student" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL, "age" smallint N
ULL, "sex" smallint NOT NULL, "phone" varchar(20) NULL UNIQUE, "c_time" datetime NOT NULL);
2
3
4
5
6
输出格式跟你使用的数据库有关。
现在,运行migrate
命令,在数据库里创建新定义的模型的数据表:
python .\manage.py migrate crm
Operations to perform:
Apply all migrations: crm
Running migrations:
Applying crm.0001_initial... OK
2
3
4
5
django通过在数据库中创建一个特殊的表django_migrations
来跟踪执行过哪些迁移。
migrate
命令会执行所有没有执行过的迁移,将模型的修改同步到数据库结构上。
迁移时非常强大的功能,是的开发过程中持续的改变数据库结构而不需要重新删除和创建表,它专注于使用数据库平滑升级而不丢失数据。
记住,改变模型需要这三步:
- 编辑
models.py
文件,修改模型 - 运行
python manage.py makemigrations
为模型的改变生成迁移文件 - 运行
python manage.py migrate
来应用数据库迁移
# 表关系
显然,关系型数据库的强大之处在于各表之间的关联关系。 Django 提供了定义三种最常见的数据库关联关系的方法:多对一,多对多,一对一。
# 表设计
为我们的crm系统设计学生表(tb_student),学生详情表(tb_student_detail),渠道表(tb_channel),课程表(tb_course),报名表(tb_entry),表关系和字段如下图:
# 多对一
上图中的学生表和渠道表,一个学生会对应一个渠道,一个渠道对应多个学生,学生表中的一条数据和渠道表中的一条数据对应,渠道表中的一条数据与学生表中的多条数据对应,学生表和渠道表形成多对一的关系。
在django中要表达多对一的关系需要使用django.db.models.ForeignKey
字段,创建一个渠道模型,然后在Student
模型中添加一个外键字段如下:
from django.db import models
class Student(models.Model):
...
channel = models.ForeignKey('Channel', null=True, on_delete=models.SET_NULL, help_text='渠道')
...
class Channel(models.Model):
title = models.CharField('名称', max_length=20, help_text='渠道名称')
class Meta:
db_table = 'tb_channel'
verbose_name = '渠道表'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
外键字段定义在多的一方,所以Student
模型中定义了channel
字段,这个字段名一般使用关系模型的小写名。
外键字段的第一个参数是一个位置参数,即需要关联的模型,可以是模型本身,也可以是模型的字符串形式的导入路径(当引用后定义的模型时很有用)。
在数据库层面,django会在字段名后附加_id
来创建数据库列名。所以Student
模型的数据库表将有一个channel_id
列,然后会为这个列创建一个外键约束,被引用表为tb_channel
,被引用字段为tb_channel.id
。
注意:有时候为了效率,在数据库不创建外键约束,而是通过代码逻辑来保证数据的完整性。在django中可以在ForeiginKey
字段中指定db_constraint=False
来控制不创建外键约束。
# 级联操作
当一个由ForeignKey引用
的对象被删除时,django通过on_delete
参数指定的方法实现数据库级联操作。
on_delete
的可能值有:
CASCADE
级联删除
PROTECT
通过引发
ProtectedErro
防止删除被引用字段RESTRICT
通过引发
RestrictedError
方式删除被引用字段SET_NULL
设置外键为空,只有
null
为True
是才能SET_DEFAULT
将外键设置为默认值,必须为外键设置默认值
注意:外键字段必须指定参数on_delete
。
当删除一个渠道时,在业务层面不大可能会删除对应的学生,应该是设置channel_id
字段为null
,所以这个外键字段的级联操作设置为null=True, on_delete=models.SET_NULL
# 多对多
数据库中多对多的关系通过第三张表来表示。
一个学生可以报名多门课程,一个课程可以被多名学生报名。在我们的案例中学生表和课程表通过报名表实现多对多的关系。
在django中要表达多对多的关系要使用django.db.models.ManyToManyField
字段。
对于多对多关系的两个模型,可以在其中任意一个模型中定义多对多字段,但只能选择一个模型设置该字段,不能同时在两个模型中添加该字段。
一般来说应该把多对多字段放到需要在表单中编辑的对象中,或者是在业务中需要查询更多的模型中。
在我们的案例中,编辑学生对象,或者编辑课程对象时都不需要彼此,而在查询时,"报名了某个课程的学生有哪些",这个需求会更多,所以把多对多的字段定义在课程表中。定义Course
模型如下:
class Course(models.Model):
name = models.CharField('名称', max_length=20, help_text='课程名称')
students = models.ManyToManyField('Student', help_text='报名学生')
class Meta:
db_table = 'tb_course'
verbose_name = '课程表'
verbose_name_plural = verbose_name
2
3
4
5
6
7
8
多对多字段名一般设置为关系模型名的复数形式,表示关系模型的对象集合,所以Course
模型中的多对多字段名为students
。多对多字段的第一个参数是一个位置参数,既需要关联的模型,可以是模型本身,也可以是模型的字符串形式的导入路径(当引用后定义的模型时很有用)。
在数据库层面,django会自动创建一个中间表来表示多对多关系。默认情况下这个表名使用创建多对多字段的模型名和字段名生成,上面的例子会生成表名tb_course_students
。然后包含两个字段,分别是两个模型的名字和_id
组成(student_id
,course_id
),并创建外键引用对应表的id
,还会对这两个字段创建联合唯一的约束。
# 自定义中间表
虽然django会自动创建第三张表,但是不能提供额外字段。如果中间表需要包含其他字段,就需要自定义中间表,然后在定义多对多字段时通过through
参数指定第三张表。
所以创建一个Entry
模型如下:
class Entry(models.Model):
student = models.ForeignKey(Student, verbose_name='学生', help_text='学生', on_delete=models.PROTECT)
course = models.ForeignKey(Course, verbose_name='课程', help_text='课程', on_delete=models.PROTECT)
c_time = models.DateTimeField('报名时间', help_text='报名时间', auto_now_add=True)
def __str__(self):
return '{}-{}'.format(self.student.name, self.course.name)
class Meta:
db_table = 'tb_entry'
verbose_name = '报名表'
verbose_name_plural = verbose_name
2
3
4
5
6
7
8
9
10
11
12
在其中定义student
和course
外键字段分别和对应的模型形成多对一的关系。再定义c_time
字段,用来记录报名时间。学生表,课程表通过报名来表达多对多关系。现在创建多对多字段主要是为了查询方便,可以在课程对象和学生对象上彼此关联,所以修改Course
模型中的students
字段如下:
class Course(models.Model):
...
students = models.ManyToManyField('Student', help_text='报名学生', through='Entry')
...
2
3
4
# 一对一
在django
中要表达一对一的关系需要使用django.db.models.OneToOneField
字段,概念上,这类似于ForeignKey
与unique=True
的结合。
在crm
中,学生详情与学生表就是一对一的关系,创建模型如下:
class StudentDetail(models.Model):
STATION_CHOICES = [
('功能测试工程师', '功能测试工程师'),
('自动化测试工程师', '自动化测试工程师'),
('测试开发工程师', '测试开发工程师'),
('测试组长', '测试组长'),
('测试经理', '测试经理')
]
class SalaryChoice(models.TextChoices):
FIRST = '5000以下', '5000以下'
SECOND = '5000-10000', '5000-10000'
THIRD = '10000-15000', '10000-15000'
FOURTH = '15000-20000', '15000-20000'
FIFTH = '20000以上', '20000以上'
student = models.OneToOneField('Student', verbose_name='学生', on_delete=models.CASCADE, help_text='学生')
city = models.CharField('城市', max_length=20, help_text='所在城市', null=True)
company = models.CharField('就职公司', max_length=64, help_text='就职公司', null=True)
station = models.CharField('岗位', choices=STATION_CHOICES, max_length=10, default='功能测试工程师', help_text='岗位')
salary = models.CharField('薪资水平', choices=SalaryChoice.choices, max_length=20, default=SalaryChoice.THIRD, help_text='薪资水平')
class Meta:
db_table = 'tb_student_detail'
verbose_name = '学生详情表'
verbose_name_plural = verbose_name
def __str__(self):
return self.student.name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在数据库层面会创建student_id
列,然后为这个列创建一个外键约束,引用表为tb_student
,字段为tb_student.id
。与多对一不同,这个列还会创建一个唯一约束,形成一对一的关系。其他的级联操作与多对一一样。
本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。
