书籍名称:[Python3 自动化软件发布系统-Django2 实战]
Model ORM
在 Django 的框架设计中采用了 MTV 模型,即 Model,Template,Viewer。Model 相对于传统的三层或者 MVC 框架来说就相当于数据处理层,它主要负责与数据的交互。
Django 默认采用的是 ORM 框架中的 Codefirst 模型(根据代码中的类自动生成数据库中的表),也就是说开发人员只需要专注于代码的编写,而不需要过多的关注数据库层面的东西。
- 什么是 ORM,ORM 是 Object Relational Mapping(关系对象映射),其操作本质上会根据对接的数据库引擎,翻译成对应的 SQL 语句;与 ORM 作对比的,常常是原生 SQL。
- Django 项目中的所有模型都是 django.db.models.Model 类的子类。每个类都将被转换为数据表。每个字段由 django.db.models.Field 子类的实例表示,它们将被转换为数据库的列;而且每个 orm 实例都表示为数据库表里的一行数据。
- 每个 Django 模型都带有一个特殊的属性,称之为模型管理器(Model Manager)。可以通过属性 objects 来访问这个管理器,它主要用于数据库操作。而每次操作模型管理器,都会返回一个 QuerySet 类型的结果。
Model 示例
这里以一个小型的论坛为原型,来讲述一下 Django Models 的内容。整个项目的构思是维护几个论坛版块,每个版块就像一个分类一样。在指定的版块中,用户可以通过创建新主题开始讨论,其他用户参与讨论或回复。
- Board: 版块;
- Topic: 主题;
- Post: 帖子(主题的回复或评论)
- Board 模型,将从两个字段开始:name 和 description。name 字段必须是唯一的,应避免有重复的名称,description 用于说明这个版块是做什么的;
- Topic 模型包括四个字段,subject 表示主题内容;last_update 用来定义话题的排序;starter 用来识别谁发起的话题;board 用于指定它属于哪个版块;
- Post 模型有一个 message 字段,用于存储回复的内容;created_at 在排序的时候用;update_at告诉用户是否更新了内容,同时还需要有对应的 User 模型的引用;以及 Post 是由谁创建的和由谁更新的;
- User 模型,包括 username,password,email,is_superuser 标志,需要注意的是,我们不需要创建 User 模型,因为 Django 已经在 contrib 包中内置了 User 模型,可以直接拿来用。
编写模型代码
在 bbs 目录下的 models.py 文件里,输入以下内容,实现这四个 Model 的创建:
1 | from django.db import models |
代码解释:
在 Topic 数据表中,定义了一个指向 Board 的外键,
- related_name 属性用于指定反向查询的名称,即通过它,可以查询出属于每个 Board 的 Topic;
- on_delete 的属性定义了级联删除,即当删除一个 Board 时,其所属的所有 Topic 也会一并删除;
- 在 Post 模型中,updated_by 字段设置 related_name = ‘+’,这提示 Django 不需要这种反向关系,所以它会被忽略;
执行 makemigrations
在定义好 models.py 文件之后,在项目根目录下执行如下命令,生成迁移文件
1 | % python manage.py makemigrations |
执行 migrate
执行完 makemigrations 命令后,已在 bbs 目录的 migrations 目录下,生成了一个名为 0001_initial.py 文件。该文件记录的就是 models.py 文件里的变更记录。接下来,再运行如下命令,把这些动作应用到数据库;
1 | % python manage.py migrate |
从上面的输出可以看出,由于我们从来没有运行过这个命令,所以 Django 会先将本身提供的数据库设置同步到数据库,同时也将 bbs.0001_initial 同步到了数据库中。
ORM 常用 Field 及属性
Django ORM 支持的数据类型及常用设置:
常用的字段 Field
- CharField:字符串类型,映射到数据库中会转换成 varchar 类型,使用时必须传入 max_length 属性定义该字符串的最大长度;如果超过 254 个字符,建议使用 TextField;
- EmailField:在数据库底层也是一个 varchar 类型,默认最大长度是 254 个字符。也可以使用 max_length 定义该字符串的最大长度;这个字段在数据库层面不会限制一定要传递符合 email 条件的字符串,只是以后在使用 ModelForm 表单验证时会起作用;
- UrlField:类似于 CharField,在数据库底层也是一个 varchar 类型,只不过只能用来存储 URL 格式的字符串,并且默认的 max_length 是 200,同 EmailField;
- FloatField:浮点数类型,映射到数据库中会变成 double 类型;
- IntegerField:整数类型,映射到数据库会变成 11 位的 int 类型;
- BooleanField:布尔类型,映射到数据库中会变成长度只有 1 位的 tinyint 类型,这个 Field 不接受 null 参数。想要使用 null 的布尔类型的字段,就使用 NullBooleanField;
- AutoField:自增长类型,映射到数据库中是 11 位的整数,使用此字段时,必须传递 primary_key=True,否则在生成迁移脚本时就会报错;一个模型不能有两个自增长字段。
- DateTimeField:日期时间类型,在 Python 中对应的是 datetime.datetime 类型,映射到数据库中也是 datetime 类型。使用这个 Field 可以传递以下几个参数:
- auto_now=True: 在每次这个数据保存的时候,都是用当前时间,比如作为一个记录修改日期的字段;
- auto_now_add=True: 在每条数据第一次被添加进去的时候,都是用当前的时间,比如作为一个记录第一次入库的字段;
- DateField:日期类型,用法同 DateTimeField,在 Python 中对应的是 datetime.date 类型,在映射到数据库中是 date 类型;
- TimeField:时间类型,用法同 DateTimeField,在 Python 中对应的是 datetime.time 类型,在映射到数据库中是 time 类型;
- FileField:用来存储文件;
- ImageField:用来存储图片文件;
- TextField:大量的文本类型;
字段常用的参数
- null:标识是否可以为空,默认为 False。在使用字符串相关的 Field 时,官方建议尽量不要使用这个参数。如果想要在表单验证的时候允许这个字符串为空,那么建议使用 blank=True;如果Field 是 BooleanField,由于 BooleanField不接受 null 参数,因此如果想设置这个字段可以为空的 bool 类型,那么应该使用 NullBooleanField。
- blank:标识这个字段在表单验证的时候是否可以为空,默认是 False。这个和 null 是有区别的,null 是一个纯数据库级别的;而 blank 是表单验证级别的;
- db_column:这个字段在数据库中的名字,如果没有设置这个参数,那么将会使用模型中属性的名字;
- db_index:标识这个字段是否为索引字段;
- default:默认值,可以是一个值,或者一个函数,但是不支持 lambda 表达式,并且不支持列表/字典/集合 等可变的数据结构。在用函数作为值传递给 default 时,只能传递函数名,不需要括号;
- primary_key:是否为主键,与 AutoField/BigAutoField 连用,默认是 False;
- unique:在表中这个字段的值是否唯一,若是,则在数据库中就是唯一约束,一般用于设置手机号/邮箱等;
- choices:在一个范围内选择出一项,这个属性可以在 Django Admin 中显示下拉框,在一定程序上避免连表查询;
模型中的 Meta 配置
对于一些模型级别的配置,可以在模型中定义一个类,叫做 Meta,然后在这个类中添加一些类属性来控制模型的作用。
比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称,那么可以在 Meta 类中添加一个 db_table 的属性。如下所示:
1 | class Book(models.Model): |
以下将对 Meta 类中的一些常用配置进行解释:
- db_table:模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候会使用模型所在 App 的名称加上模型名的小写来作为默认的表名;
- ordering:设置提取数据的排序方式,因为可以按照多个字段以优先关系进行排序,所以需要传递一个字段的列表。在提取数据时,可以根据列表中字段从前到后(优先级从高到低)的方式排序,默认排序为正序,如果需要哪个字段按倒序排列,则可以在这个字段前面加上 “-“;
Django Shell 操作 ORM
进入项目的根目录,执行以下命令进入支持 Django 的 Shell:
1
python manage.py shell
进入 Shell 后,先运行如下示例,以便了解 ORM 的基本操作
1
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
29
30
31
32In [1]: from bbs.models import Board
In [2]: board = Board(name='Java', description='build once, run anywhere.')
In [3]: board.save()
In [4]: board.id
Out[4]: 1
In [5]: board.name
Out[5]: 'Java'
In [6]: board.description
Out[6]: 'build once, run anywhere.'
In [7]: board.description = 'build windows, run on linux.'
In [8]: board.save()
In [9]: board = Board.objects.create(name='Python', description='life is short, use python.')
In [10]: board.id
Out[10]: 2
In [11]: Board.objects.all()
Out[11]: <QuerySet [<Board: Java>, <Board: Python>]>
In [12]: Board.objects.get(name='Python')
Out[12]: <Board: Python>
In [13]: Board.objects.filter(id__lt=3)
Out[13]: <QuerySet [<Board: Java>, <Board: Python>]>代码解释
- 第1行,首先从 bbs 目录的 models 文件中导入 Board 数据库表;
- 第2行,实例化一个 Board 对象 board;
- 第3行,将这个对象保存到数据库;
- 第4行到第6行,通过这个对象获取所有字段的值;
- 第7行到第8行,修改并保存这个对象的新值;
- 第9行,调用 objects 管理器的 create() 方法创建一个对象,这种方法不需要调用 save() 方法就可以直接保存到数据库中;
- 第11行,使用 objects 管理器的 all() 方法,可以返回包含所有记录的 QuerySet;
- 第12行,使用 objects 管理器的 get() 方法,返回符合 name=’Python’ 条件的 QuerySet;
- 第13行,使用 objects 管理器的 filter() 方法,返回的是满足过滤条件的 QuerySet(id__lt=3,表示的过滤条件是 id 的值小于 3);
上面是一个简单的例子,接下来看看如何实现与外键有关的数据库操作。因为在新增 topic 及 post 时,都涉及到用户,所以需要先运行如下命令,新增一个管理员用户:
1
python manage.py createsuperuser
假设我们新增的用户名为 ‘wanwu’,接着,就可以进入支持 Django 的 shell 进行如下操作了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21In [1]: from bbs.models import Board, Topic, Post
In [2]: from django.contrib.auth.models import User
In [3]: user = User.objects.get(username='wanwu')
In [4]: board = Board.objects.get(name='Python')
In [5]: topic = Topic(subject='first Python subject', board=board, starter=user)
In [6]: topic.save()
In [7]: post = Post(message='Python is very good.', topic=topic, created_by=user)
In [8]: post.save()
In [9]: board.topics.all()
Out[9]: <QuerySet [<Topic: first Python subject>]>
In [10]: topic.posts.all()
Out[10]: <QuerySet [<Post: Python is very good.>]>代码解释:
- 第1~2行,导入必要的 Models 中的数据表;
- 第3行,获取一个用户名为 ‘wanwu’ 的 user 实例;
- 第4行,获取一个名称为 ‘Python’ 的 board 实例;
- 第5行,按照 topic 数据表的要求,填充相关字段。注意,board 和 starter 是用对应的实例对象去填充的;
- 第6行,保存 topic 实例;
- 第7行,按照 post 数据表的要求,填充相关字段。注意,topic 和 created_by 是用对应的实例对象去填充;
- 第8行,保存 post 实例;
- 第9行,利用 topic 中 board 外键的 related_name 关系,可以获取一个 board 下面的所有 topic。这里利用的正是 models.py 中的 Topic 数据表:board=models.ForeignKey(Board, related_name=’topics’, …) 这行定义;
- 第10行,利用 post 中 topic 外键的 related_name 关系,可以获取一个 topic 下面的所有 post。这里利用的正是 models.py 中的 Post 数据表:topic=models.ForeignKey(Topic, related_name=’posts’, …) 这行定义.