目录
- 一、模型的定义
- 二、数据迁移
- 三、数据表关系
- 四、数据表操作
- 4.1 Shell工具
- 4.2 数据新增
- 4.3 数据修改
- 4.4 数据删除
- 4.5 数据查询
Django 对各种数据库提供了很好的支持,包括 PostgreSQL、MySQL、SQLite 和 Oracle,而且为这些数据库提供了统一的 API 方法,这些 API 统称为 ORM 框架。通过使用 Django 内置的 ORM 框架可以实现数据库连接和读写操作。本文以 MySQL 数据库为例,分别讲述 Django 的模型定义与数据迁移、数据表关系、数据表操作和多数据库的连接与使用。
关于数据库的基础知识可以参考专栏:https://blog.csdn.net/xw1680/category_12277246.html?spm=1001.2014.3001.5482
一、模型的定义
ORM 框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。从效果上说,它创建了一个可在编程语言中使用的 "虚拟对象数据库"
, 通过对虚拟对象数据库的操作从而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在 Django 中,虚拟对象数据库也称为模型,通过模型实现对目标数据库的读写操作,实现方法如下:
- 配置目标数据库,在 settings.py 中设置配置属性,配置步骤可参考 Django 2024全栈开发指南(二):Django项目配置详解 一文的
四、数据库配置
小节。 - 构建虚拟对象数据库,在 App 的 models.py 文件中以类的形式定义模型。
- 通过模型在目标数据库中创建相应的数据表。
- 在其他模块(如视图函数)里使用模型来实现目标数据库的读写操作。
在项目的配置文件 settings.py 里设置数据库配置信息。以 Django5Study 项目为例,其配置信息如下:
DATABASES = {# 默认数据库"default": {"ENGINE": "django.db.backends.mysql","OPTIONS": {'read_default_file': str(BASE_DIR / 'my.cnf')},},
}
我们通过终端登录 mysql,查看 django5study 数据库,数据库中只有内置的迁移文件生成的数据表,如下图所示:
我们想要的数据表可以通过模型创建,因为 Django 对模型和目标数据库之间有自身的映射规则,如果自己在数据库中创建数据表,就可能不符合 Django 的建表规则,从而导致模型和目标数据库无法建立有效的通信联系。大概了解项目的环境后,在 Django5Study 项目中新建子应用 chapter03_Model,命令如下:
(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py startapp chapter03_Model
接下来我们在子应用 chapter03_Model 目录下的 models.py 文件中定义图书模型,字段名称包括书名、发布日期、阅读量、评论量、与是否下架,示例代码如下:
from django.db import models# Create your models here.
# 准备书籍列表信息的模型类
# chapter03_Model的models.py
class BookInfo(models.Model):# 创建字段,字段类型...verbose_name主要是在admin站点中使用book_name = models.CharField(max_length=20, verbose_name='名称')pub_date = models.DateField(verbose_name='发布日期', null=True)read_count = models.IntegerField(default=0, verbose_name='阅读量')comment_count = models.IntegerField(default=0, verbose_name='评论量')is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')class Meta:db_table = 'book_info' # 指明数据库表名verbose_name = '图书' # 在admin站点中显示的名称def __str__(self):"""定义每个数据对象的显示信息"""return self.book_name
模型 BookInfo 定义了5个字段,分别代表字符类型、日期类型、整型、整型和布尔类型。但在实际开发中,我们需要定义不同的字段类型来满足各种开发需求,因此Django划分了多种字段类型,在源码目录 django\db\models\fields
的 __init__.py
和 files.py
文件里找到各种模型字段,如下图所示:
说明如下:
# AutoField: 自增长类型,数据表的字段类型为整数,长度为11位
# BigAutoField: 自增长类型,数据表的字段类型为bigint,长度为20位
# CharField: 字符类型
# BooleanField: 布尔类型
# CommaSeparatedIntegerField: 用逗号分隔的整数类型
# DateField: 日期(Date)类型
# DateTimeField: 日期时间(Datetime)类型
# Decimal: 十进制小数类型
# EmailField: 字符类型,存储邮箱格式的字符串
# FloatField: 浮点数类型,数据表的字段类型变成Double类型
# IntegerField: 整数类型,数据表的字段类型为11位的整数
# BigIntegerField: 长整数类型
# IPAddressField: 字符类型,存储Ipv4地址的字符串
# GenericIPAddressField: 字符类型,存储Ipv4和Ipv6地址的字符串
# NullBooleanField: 允许为空的布尔类型
# PositiveIntegerFiel: 正整数的整数类型
# PositiveSmallIntegerField: 小正整数类型,取值范围为0~32767
# SlugField: 字符类型,包含字母、数字、下画线和连字符的字符串
# SmallIntegerField: 小整数类型,取值范围为-32,768~+32,767
# TextField: 长文本类型
# TimeField: 时间类型,显示时分秒HH:MM[:ss[.uuuuuu]]
# URLField: 字符类型,存储路由格式的字符串
# BinaryField: 二进制数据类型
# FileField: 字符类型,存储文件路径的字符串
# ImageField: 字符类型,存储图片路径的字符串
# FilePathField: 字符类型,从特定的文件目录选择某个文件
每个模型字段都允许设置参数,这些参数来自父类 Field,我们在源码里查看 Field 的定义过程 (django\db\models\fields\__init__.py
) ,如下图所示:
对模型字段的参数进行分析:
# verbose_name: 默认为None,在Admin站点管理设置字段的显示名称
# primary_key: 默认为False,若为True,则将字段设置成主键
# max_length: 默认为None,设置字段的最大长度
# unique: 默认为False,若为True,则设置字段的唯一属性
# blank: 默认为False,若为True,则字段允许为空值,数据库将存储空字符串
# null: 默认为False,若为True,则字段允许为空值,数据库表现为NULL
# db_index: 默认为False,若为True,则以此字段来创建数据库索引
# default: 默认为NOT_PROVIDED对象,设置字段的默认值
# editable: 默认为True,允许字段可编辑,用于设置Admin的新增数据的字段
# serialize: 默认为True,允许字段序列化,可将数据转化为JSON格式
# unique_for_date: 默认为None,设置日期字段的唯一性
# unique_for_month: 默认为None,设置日期字段月份的唯一性
# unique_for_year: 默认为None,设置日期字段年份的唯一性
# choices: 默认为空列表,设置字段的可选值
# help_text: 默认为空字符串,用于设置表单的提示信息
# db_column: 默认为None,设置数据表的列名称,若不设置,则将字段名作为数据表的列名
# db_tablespace: 默认为None,如果字段已创建索引,那么数据库的表空间名称将作为该字段的索引名注意: 部分数据库不支持表空间
# auto_created: 默认为False,若为True,则自动创建字段,用于一对一的关系模型
# validators: 默认为空列表,设置字段内容的验证函数
# error_messages: 默认为None,设置错误提示
上述参数适用于所有模型字段,但不同类型的字段会有些特殊参数,每个字段的特殊参数可以在字段的初始化方法 __init__
里找到,比如字段 DateField 和 TimeField 的特殊参数 auto_now_add 和 auto_now,字段 FileField 和 ImageField 的特殊参数 upload_to。
在定义模型时,一般情况下都会重写函数 __str__
, 这是设置模型的返回值,默认情况下,返回值为模型名+主键。函数 __str__
可用于外键查询,比如模型A设有外键字段F,外键字段F关联模型B,当查询模型A时,外键字段F会将模型B的函数 __str__
返回值作为字段内容。需要注意的是,函数 __str__
只允许返回字符类型的字段,如果字段是整型或日期类型的,就必须使用 Python 的 str() 函数将其转化成字符类型。模型除了定义模型字段和重写函数 __str__
之外,还有 Meta 选项,这三者是定义模型的基本要素。Meta 选项里设有19个属性,每个属性的说明如下:
# abstract: 若设为True,则该模型为抽象模型,不会在数据库里创建数据表
# app_label: 属性值为字符串,将模型设置为指定的项目应用,比如将index的models.py定义的模型A指定到其他App里
# db_table: 属性值为字符串,设置模型所对应的数据表名称
# db_teblespace: 属性值为字符串,设置模型所使用数据库的表空间
# get_latest_by: 属性值为字符串或列表,设置模型数据的排序方式
# managed: 默认值为True,支持Django命令执行数据迁移;若为False,则不支持数据迁移功能# order_with_respect_to: 属性值为字符串,用于多对多的模型关系,指向某个关联模型的名称,
# 并且模型名称必须为英文小写比如模型A和模型B,模型A的一条数据对应模型B的多条数据,两个模型关联后,
# 当查询模型A的某条数据时,可使用get_b_order()和set_b_order()来获取模型B的关联数据,
# 这两个方法名称的b为模型名称小写,此外get_next_in_order()和get_previous_in_order()可以
# 获取当前数据的下一条和上一条的数据对象# ordering: 属性值为列表,将模型数据以某个字段进行排序
# permissions: 属性值为元组,设置模型的访问权限,默认设置添加、删除和修改的权限
# proxy: 若设为True,则为模型创建代理模型,即克隆一个与模型A相同的模型B
# required_db_features: 属性值为列表,声明模型依赖的数据库功能比如['gis_enabled'],表示模型依赖GIS功能
# required_db_vendor: 属性值为列表,声明模型支持的数据库,默认支持SQLite、PostgreSQL,MySQL和Oracle
# select_on_save: 数据新增修改算法,通常无须设置此属性,默认值为False
# indexes: 属性值为列表,定义数据表的索引列表
# unique_together: 属性值为元组,多个字段的联合唯一,等于数据库的联合约束
# verbose_name: 属性值为字符串,设置模型直观可读的名称并以复数形式表示
# verbose_name_plural: 与verbose_name相同,以单数形式表示
# label: 只读属性,属性值为app_label.object_name,如index的模型PersonInfo,值为index.PersonInfo
# label_lower: 与label相同,但其值为字母小写,如index.personinfo
综上所述,模型字段、函数 __str__
和 Meta 选项是模型定义的基本要素,模型字段的类型、函数 __str__
和 Meta 选项的属性设置需由开发需求而定。在定义模型时,还可以在模型里定义相关函数,如 get_absolute_url(),当视图类没有设置属性 success_url 时,视图类的重定向路由地址将由模型定义的 get_absolute_url() 提供。除此之外,Django 支持开发者自定义模型字段,从源码文件得知,所有模型字段继承 Field 类,只要将自定义模型字段继承 Field 类并重写父类某些属性或方法即可完成自定义过程,具体的自定义过程不再详细讲述,可以参考内置模型字段的定义过程。
二、数据迁移
数据迁移是将项目里定义的模型生成相应的数据表,本小节将会深入讲述数据迁移的操作,包括数据表的创建和更新。以上一小节的 chapter03_Model 子应用为例,项目所配置的数据库中只有内置的迁移文件生成的数据表,我们想要通过模型创建数据表 BookInfo,可使用 Django 的操作指令完成创建过程。
中间有个小插曲,执行迁移命令一直不成功,提示:No changes detected,我还以为数据库连接等出现了问题,结果发现是在上一小节中创建的 chapter03_Model 子应用没有在 settings.py 文件中注册,要仔细呀,注册如下:
# Application definition
# 子应用列表
INSTALLED_APPS = ["django.contrib.admin","django.contrib.auth","django.contrib.contenttypes","django.contrib.sessions","django.contrib.messages","django.contrib.staticfiles",# TODO 1.添加(注册)子应用 chapter01_HelloDjango5'chapter01_HelloDjango5',# TODO 2024-11-14.添加(注册)子应用 chapter02_DjangoSettings'chapter02_DjangoSettings',# TODO 2024-11-14 注册关于模型学习的子应用'chapter03_Model',
]
注册完成之后在终端下输入 Django 的操作指令:
(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py makemigrations
Migrations for 'chapter03_Model':chapter03_Model\migrations\0001_initial.py+ Create model BookInfo(django5_study) D:\Code\dream\PythonStudy\Django5Study>
当 makemigrations 指令执行成功后,在项目子应用 chapter03_Model 的 migrations 文件夹里创建 0001_initial.py 文件,如果项目里有多个子应用,并且每个子应用 的 models.py 文件里定义了模型对象,当首次执行 makemigrations 指令时,Django 就在每个子应用的 migrations 文件夹里创建 0001_initial.py 文件。打开查看 0001_initial.py 文件,文件内容如下图所示:
0001_initial.py 文件将 models.py 定义的模型生成数据表的脚本代码,该文件的脚本代码可被 migrate 指令执行,migrate 指令会根据脚本代码的内容在数据库里创建相应的数据表,在终端下输入 migrate 指令即可完成数据表的创建,代码如下:
(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py migrate
Operations to perform:Apply all migrations: admin, auth, chapter03_Model, contenttypes, sessions
Running migrations:Applying chapter03_Model.0001_initial... OK(django5_study) D:\Code\dream\PythonStudy\Django5Study>
指令运行完成后,打开数据库就能看到新建的数据表,其中数据表 book_info 由项目应用 chapter03_Model 定义的模型 BookInfo 创建,而其他数据表是 Django 内置的功能所使用的数据表,分别是会话 Session、用户认证管理和 Admin 后台系统等。
在开发过程中,开发者因为开发需求而经常调整数据表的结构,比如新增功能、优化现有功能等。假如在上述例子里新增模型 PeopleInfo 及其数据表,为了保证不影响现有的数据表,如何通过新增的模型创建相应的数据表?针对上述问题,我们只需再次执行 makemigrations 和 migrate 指令即可,比如在 chapter03_Model 的 models.py 里定义模型 PeopleInfo,代码如下:
# 准备人物列表信息的模型类
class PeopleInfo(models.Model):GENDER_CHOICES = ((0, 'male'),(1, 'female'))name = models.CharField(max_length=20, verbose_name='名称')gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')description = models.CharField(max_length=200, null=True, verbose_name='描述信息')book = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')class Meta:db_table = 'people_info'verbose_name = '人物信息'def __str__(self):return self.name
在终端下输入并运行 makemigrations 指令,Django 会在 chapter03_Model 的 migrations 文件夹里创建 0002_peopleinfo.py 文件;然后输入并运行 migrate 指令即可完成数据表 people_info 的创建。
(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py makemigrations
Migrations for 'chapter03_Model':chapter03_Model\migrations\0002_peopleinfo.py+ Create model PeopleInfo(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py migrate
Operations to perform:Apply all migrations: admin, auth, chapter03_Model, contenttypes, sessions
Running migrations:Applying chapter03_Model.0002_peopleinfo... OK
makemigrations 和 migrate 指令还支持模型的修改,从而修改相应的数据表结构,比如在模型 PeopleInfo 里新增字段 age,代码如下:
# 准备人物列表信息的模型类
class PeopleInfo(models.Model):GENDER_CHOICES = ((0, 'male'),(1, 'female'))name = models.CharField(max_length=20, verbose_name='名称')# 增加字段年龄age = models.PositiveSmallIntegerField(verbose_name='年龄', default=0)gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')description = models.CharField(max_length=200, null=True, verbose_name='描述信息')book = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')class Meta:db_table = 'people_info'verbose_name = '人物信息'def __str__(self):return self.name
新增模型字段必须将属性 null 和 blank 设为 True 或者为模型字段设置默认值(设置属性 default),否则执行 makemigrations 指令会提示字段修复信息。当 makemigrations 指令执行完成后,在 index 的 migrations 文件夹创建相应的 .py 文件,只要再次执行 migrate 指令即可完成数据表结构的修改。
每次执行 migrate 指令时,Django 都能精准运行 migrations 文件夹尚未被执行的 .py 文件,它不会对同一个 .py 文件重复执行,因为每次执行时,Django 会将该文件的执行记录保存在数据表 django_migrations 中,数据表的数据信息如下图所示:
如果要重复执行 migrations 文件夹的某个 .py 文件,就只需在数据表里删除相应的文件执行记录。一般情况下不建议采用这种操作,因为这样很容易出现异常,比如数据表已存在的情况下,再次执行相应的 .py 文件会提示 table "xxx" already exists
异常。migrate 指令还可以单独执行某个 .py 文件,首次在项目中使用 migrate 指令时,Django 会默认创建内置功能的数据表,如果只想执行 chapter03_Model 的 migrations 文件夹的某个 .py 文件,那么可以在 migrate 指令里指定文件名,代码如下:
python manage.py migrate chapter03_Model 0001_initial
在 migrate 指令末端设置项目应用名称 chapter03_Model 和 migrations 文件夹的 0001_initial 文件名,三者(migrate 指令、项目应用名称 chapter03_Model 和 0001_initial 文件名)之间使用空格隔开即可,指令执行完成后,数据库只有数据表 django_migrations 和 people_info。我们知道,migrate 指令根据 migrations 文件夹的 .py 文件创建数据表,但在数据库里,数据表的创建和修改离不开 SQL 语句的支持,因此 Django 提供了 sqlmigrate 指令,该指令能将 .py 文件转化成相应的 SQL 语句。以 chapter03_Model 的 0001_initial.py 文件为例,在终端输入 sqlmigrate 指令,指令末端必须设置项目应用名称和 migrations 文件夹的某个 .py 文件名,三者之间使用空格隔开即可,指令输出结果如下图所示:
除此之外,Django 还提供了很多数据迁移指令,如 squashmigrations、inspectdb、showmigrations、sqlflush、sqlsequencereset 和 remove_stale_contenttypes,这些指令在 Django 2024全栈开发指南(一):框架简介、环境搭建与项目结构 一文中的 2.5.1 Django的操作指令
小节里已说明过了,此处不再重复讲述。当我们在操作数据迁移时,Django 会对整个项目的代码进行检测,它首先执行 check 指令,只要项目里某个功能文件存在异常,Django 就会终止数据迁移操作。也就是说,在执行数据迁移之前,可以使用 check 指令检测整个项目,项目检测成功后再执行数据迁移操作,如下图所示:
三、数据表关系
一个模型对应数据库的一张数据表,但是每张数据表之间是可以存在外键关联的,表与表之间有3种关联:一对一、一对多和多对多。一对一关系存在于两张数据表中,第一张表的某一行数据只与第二张表的某一行数据相关,同时第二张表的某一行数据也只与第一张表的某一行数据相关,这种表关系被称为一对一关系,以下面两张表为例进行说明:
上面两张表中的字段 ID 分别是一一对应的,并且不会在同一表中有重复 ID,使用这种外键关联通常是一张数据表设有太多字段,将常用的字段抽取出来并组成一张新的数据表。在模型中可以通过 OneToOneField 来构建数据表的一对一关系,代码如下:
class Performer(models.Model):id = models.IntegerField(primary_key=True)name = models.CharField(max_length=20)nationality = models.CharField(max_length=20)masterpiece = models.CharField(max_length=50)class PerformerInfo(models.Model):id = models.IntegerField(primary_key=True)performer = models.OneToOneField(Performer, on_delete=models.CASCADE)birth = models.CharField(max_length=20)elapse = models.CharField(max_length=20)
对上述模型执行数据迁移,在数据库中分别创建数据表 chapter03_model_performer 和 chapter03_model_performerinfo,打开 Navicat Premium 查看两张数据表的表关系,如下图所示:
一对多关系存在于两张或两张以上的数据表中,第一张表的某一行数据可以与第二张表的一到多行数据进行关联,但是第二张表的每一行数据只能与第一张表的某一行进行关联,以下面两张表为例进行说明。
第一张表的字段 ID 是唯一的,但是第二张表字段 ID 允许重复,字段 ID 相同的数据对应第一张表某一行数据,这种表关系在日常开发中最为常见。在模型中可以通过 ForeignKey 来构建数据表的一对多关系,代码如下:
class Performer(models.Model):id = models.IntegerField(primary_key=True)name = models.CharField(max_length=20)nationality = models.CharField(max_length=20)class Program(models.Model):id = models.IntegerField(primary_key=True)performer = models.ForeignKey(Performer, on_delete=models.CASCADE)name = models.CharField(max_length=20)
对上述模型执行数据迁移,在数据库中分别创建数据表 Performer 和 Program,然后打开 Navicat Premium 查看两张数据表的表关系,如下图所示:
多对多关系存在于两张或两张以上的数据表中,第一张表的某一行数据可以与第二张表的一到多行数据进行关联,同时第二张表中的某一行数据也可以与第一张表的一到多行数据进行关联,以下面的表为例进行说明:
从3张数据表中可以发现,一个演员可以参加多个节目,而一个节目也可以由多个演员来共同演出。每张表的字段 ID 都是唯一的。从最后一张表中可以发现,节目 ID 和演员 ID 出现了重复的数据,分别对应表2和表1的字段 ID,多对多关系需要使用新的数据表来管理两张表的数据关系。在模型中可以通过 ManyToManyField 来构建数据表的多对多关系,代码如下:
class Performer(models.Model):id = models.IntegerField(primary_key=True)name = models.CharField(max_length=20)nationality = models.CharField(max_length=20)class Program(models.Model):id = models.IntegerField(primary_key=True)name = models.CharField(max_length=20)performer = models.ManyToManyField(Performer)
数据表之间创建多对多关系时,只需在项目里定义两个模型对象即可,在执行数据迁移时,Django 自动生成3张数据表来建立多对多关系,如下图所示:
综上所述,模型之间的关联是由 OneToOneField、ForeignKey 和 ManyToManyField 外键字段实现的,每个字段设有特殊的参数,参数说明如下:
- to:必选参数,关联的模型名称。
- on_delete:必选参数,设置数据的删除模式,删除模型包括:CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET和DO_NOTHING。
- CASCADE 级联,删除主表数据时连通一起删除外键表中数据
- PROTECT 保护,通过抛出 ProtectedError 异常,来阻止删除主表中被外键应用的数据
- SET_NULL 设置为 NULL,仅在该字段 null=True 允许为 null 时可用
- SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用
- SET() 设置为特定值或者调用特定方法
- DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出 IntegrityError 异常
- limit_choices_to:设置外键的下拉框选项,用于模型表单和 Admin 后台系统。
- related_name:用于模型之间的关联查询,如反向查询。
- related_query_name:设置模型的查询名称,用于 filter 或 get 查询,若设置参数 related_name,则以该参数为默认值,若没有设置,则以模型名称的小写为默认值。
- to_field:设置外键与其他模型字段的关联性,默认关联主键,若要关联其他字段,则该字段必须具有唯一性。
- db_constraint:在数据库里是否创建外键约束,默认值为 True。
- swappable:设置关联模型的替换功能,默认值为 True,比如模型A关联模型B,想让模型C继承并替换模型B,使得模型A与模型C之间关联。
- symmetrical:仅限于 ManyToManyField,设置多对多字段之间的对称模式。
- through:仅限于 ManyToManyField,设置自定义模型C,用于管理和创建模型A和B的多对多关系。
- through_fields:仅限于 ManyToManyField,设置模型C的字段,确认模型C的哪些字段用于管理模型A和B的多对多关系。
- db_table:仅限于 ManyToManyField,为管理和存储多对多关系的数据表设置表名称。
四、数据表操作
本小节讲述如何使用 ORM 框架实现数据新增、修改、删除、查询、执行 SQL 语句和实现数据库事务等操作,具体说明如下:
- 数据新增:由模型实例化对象调用内置方法实现数据新增,比如单数据新增调用 create,查询与新增调用 get_or_create,修改与新增调用 update_or_create,批量新增调用 bulk_create。
- 数据修改必须执行一次数据查询,再对查询结果进行修改操作,常用方法有:模型实例化、update 方法和批量更新 bulk_update。
- 数据删除必须执行一次数据查询,再对查询结果进行删除操作,若删除的数据设有外键字段,则删除结果由外键的删除模式决定。
- 数据查询分为单表查询和多表查询,Django 提供多种不同查询的 API 方法,以满足开发需求。
- 执行 SQL 语句有3种方法实现:extra、raw 和 execute,其中 extra 和 raw 只能实现数据查询,具有一定的局限性;而 execute 无须经过 ORM 框架处理,能够执行所有 SQL 语句,但很容易受到 SQL 注入攻击。
- 数据库事务是指作为单个逻辑执行的一系列操作,这些操作具有原子性,即这些操作要么完全执行,要么完全不执行,常用于银行转账和火车票抢购等。
4.1 Shell工具
Django 的 manage 工具提供了 shell 命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试 python 语句,通过如下命令进入 shell:
(django5_study) D:\Code\dream\PythonStudy\Django5Study>python manage.py shell
Python 3.12.7 | packaged by Anaconda, Inc. | (main, Oct 4 2024, 13:17:27) [MSC v.1929 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.29.0 -- An enhanced Interactive Python. Type '?' for help.In [1]: # 导入两个模型类,以便后续使用In [2]: from chapter03_Model.models import BookInfo, PeopleInfo
这样可以更好地演示数据库的增、删、改操作,该模式非常方便开发人员开发和调试程序。
4.2 数据新增
Django 对数据库的数据进行增、删、改操作是借助内置 ORM 框架所提供的 API 方法实现的,简单来说,ORM 框架的数据操作 API 是在 QuerySet 类里面定义的,然后由开发者自定义的模型对象调用 QuerySet 类,从而实现数据操作。
先在 Navicat 中添加测试数据:
insert into book_info(book_name, pub_date, read_count,comment_count, is_delete) values
('射雕英雄传', '1980-5-1', 12, 34, 0),
('天龙八部', '1986-7-24', 36, 40, 0),
('笑傲江湖', '1995-12-24', 20, 80, 0),
('雪山飞狐', '1987-11-11', 58, 24, 0);select * from book_info;insert into people_info(name, gender, book_id, description, is_delete,age) values('郭靖', 1, 1, '降龙十八掌', 0,25),('黄蓉', 0, 1, '打狗棍法', 0,23),('黄药师', 1, 1, '弹指神通', 0,45),('欧阳锋', 1, 1, '蛤蟆功', 0,47),('梅超风', 0, 1, '九阴白骨爪', 0,33),('乔峰', 1, 2, '降龙十八掌', 0,37),('段誉', 1, 2, '六脉神剑', 0,28),('虚竹', 1, 2, '天山六阳掌', 0,32),('王语嫣', 0, 2, '神仙姐姐', 0,25),('令狐冲', 1, 3, '独孤九剑', 0,19),('任盈盈', 0, 3, '弹琴', 0,18),('岳不群', 1, 3, '华山剑法', 0,30),('东方不败', 0, 3, '葵花宝典', 0,33),('胡斐', 1, 4, '胡家刀法', 0,28),('苗若兰', 0, 4, '黄衣', 0,26),('程灵素', 0, 4, '医术', 0,20),('袁紫衣', 0, 4, '六合拳', 0,20);
在 Shell 模式下,若想对数据表 people_info 插入数据,则可输入以下代码实现:
In [11]: p1 = PeopleInfo()
In [12]: p1.name='洪七公'
In [13]: p1.gender=1
In [14]: p1.book_id=1
In [15]: p1.description='降龙十八掌'
In [16]: p1.is_delete=False
In [17]: p1.age=48
In [18]: p1.save()
上述代码是对模型 PeopleInfo 进行实例化,再对实例化对象的属性进行赋值,从而实现数据表 people_info 的数据插入,代码说明如下:
- 从项目子应用 chapter03_Model 的 models.py 文件中导入模型 PeopleInfo
- 对模型 PeopleInfo 声明并实例化,生成对象 p1
- 对对象 p1 的属性进行逐一赋值,对象 p1 的属性来自于模型 PeopleInfo 所定义的字段。完成赋值后,再由对象 p1 调用 save 方法进行数据保存
需要注意的是,模型 PeopleInfo 的外键命名为 book,但在数据表 people_info 中变为 book_id,因此对象 p1 设置外键字段 book 的时候,外键字段应以数据表的字段名为准。上述代码运行结束后,在数据表 people_info 里查看数据的插入情况,如下图所示:
除了上述方法外,数据插入还有以下3种常见方法,代码如下:
In [19]: from chapter03_Model.models import BookInfo, PeopleInfo
In [20]: # 方法一: 使用create方法实现数据插入
In [21]: p = BookInfo.objects.create(book_name='红楼梦',pub_date='1978-03-05',
read_count=50,comment_count=70,is_delete=0)
In [22]: # 数据新增后,获取新增数据的主键id
In [23]: p.id
Out[23]: 5
In [24]: # 方法二: 同样使用create方法,但数据以字典格式表示
In [25]: d = dict(book_name='西游记',
pub_date='1988-05-03',read_count=150,comment_count=90,is_delete=0)
In [26]: p = BookInfo.objects.create(**d)
In [27]: # 数据新增后,获取新增数据的主键id
In [28]: p.id
Out[28]: 6
In [29]: # 方法三: 在实例化时直接设置属性值
In [30]: p = BookInfo(book_name='水浒传',pub_date='1988-10-23',read_count=45,comment_count=38,is_delete=0)
In [31]: p.save()
In [32]: # 数据新增后,获取新增数据的主键id
In [33]: p.id
Out[33]: 7
执行数据插入时,为了保证数据的有效性,我们需要对数据进行去重判断,确保数据不会重复插入。以往的方案都是对数据表进行查询操作,如果查询的数据不存在,就执行数据插入操作。为了简化这一过程,Django 提供了 get_or_create 方法,使用如下:
In [34]: d = dict(book_name='西游记',pub_date='1988-05-03',read_count=150,comment_count=90,is_delete=0)In [35]: p = BookInfo.objects.get_or_create(**d)In [39]: p
Out[39]: (<BookInfo: 西游记>, False)In [40]: d = dict(book_name='西游记1',pub_date='1988-05-03',read_count=150,comment_count=90,is_delete=0)In [41]: p = BookInfo.objects.get_or_create(**d)In [42]: p
Out[42]: (<BookInfo: 西游记1>, True)
get_or_create 根据每个模型字段的值与数据表的数据进行判断,判断方式如下:
- 只要有一个模型字段的值与数据表的数据不相同(除主键之外),就会执行数据插入操作。
- 如果每个模型字段的值与数据表的某行数据完全相同,就不执行数据插入,而是返回这行数据的数据对象。若执行结果显示为 False,则数据表已存在数据,不再执行数据插入,若执行结果显示为 True,则代表数据插入。
除了 get_or_create 之外,Django 还定义了 update_or_create 方法,这是判断当前数据在数据表里是否存在,若存在,则进行更新操作,否则在数据表里新增数据,使用说明如下:
In [43]: # 第一次是新增数据In [44]: d = dict(book_name='三国演义',pub_date='1985-11-15',read_count=800,comment_count=520,is_delete=0)In [45]: p = BookInfo.objects.update_or_create(**d)In [46]: p
Out[46]: (<BookInfo: 三国演义>, True)In [47]: # 第二次是修改数据In [48]: p = BookInfo.objects.update_or_create(**d, defaults={'book_name': '三国演义2'})In [50]: p[0].book_name
Out[50]: '三国演义2'
update_or_create 是根据字典 d 的内容查找数据表的数据,如果能找到相匹配的数据,就执行数据修改,修改内容以字典格式传递给参数 defaults 即可;如果在数据表找不到匹配的数据,就将字典 d 的数据插入数据表里。如果要对某个模型执行数据批量插入操作,那么可以使用 bulk_create 方法实现,只需将数据对象以列表或元组的形式传入 bulk_create 方法即可:
In [55]: b1 = BookInfo(book_name='遮天',pub_date='2008-10-23',read_count=40,comment_count=30,is_delete=0)In [56]: b2 = BookInfo(book_name='遮天2',pub_date='2012-01-10',read_count=68,comment_count=88,is_delete=0)In [57]: book_list = [b1, b2]In [58]: BookInfo.objects.bulk_create(book_list)
Out[58]: [<BookInfo: 遮天>, <BookInfo: 遮天2>]
在使用 bulk_create 之前,数据类型为模型 BookInfo 的实例化对象,并且在实例化过程中设置每个字段的值,最后将所有实例化对象放置在列表或元组里,以参数的形式传递给 bulk_create,从而实现数据的批量插入操作。
4.3 数据修改
数据修改的步骤与数据插入的步骤大致相同,唯一的区别在于数据对象来自数据表,因此需要执行一次数据查询,查询结果以对象的形式表示,并将对象的属性进行赋值处理,代码如下:
In [59]: b2 = BookInfo.objects.get(id=11)In [60]: b2
Out[60]: <BookInfo: 遮天2>In [61]: b2.read_count
Out[61]: 68In [62]: b2.read_count=100In [63]: b2.save()
上述代码获取数据表 people_info 里主键 id 等于 11 的数据对象 b2,然后修改数据对象 b2 的 read_count 属性,从而完成数据修改操作。打开数据表 people_info 查看数据修改情况,如下图所示:
除此之外,还可以使用 update 方法实现数据修改,使用方法如下:
In [64]: # 批量更新一条或多条数据,查询方法使用filter
In [65]: # filter以列表格式返回,查询结果可能是一条或多条数据
In [66]: BookInfo.objects.filter(id=11).update(book_name='遮天2-1')
Out[66]: 1In [67]: # 更新数据以字典格式表示
In [68]: d = dict(book_name='遮天2-2')
In [69]: BookInfo.objects.filter(book_name='遮天2-1').update(**d)
Out[69]: 1In [70]: # 不使用查询方法,默认对全表的数据进行更新
In [71]: BookInfo.objects.update(read_count=666)
Out[71]: 11In [72]: # 使用内置F方法实现数据的自增或自减
In [73]: # F方法还可以在annotate或filter方法里使用In [75]: from django.db.models import FIn [76]: b1 = BookInfo.objects.filter(id=11)In [77]: # 将read_count字段原有的数据自增加一In [78]: b1.update(read_count=F('read_count')+1)
Out[78]: 1In [80]: print(b1)
<QuerySet [<BookInfo: 遮天2-2>]>
在 Django 2.2 或以上版本新增了数据批量更新方法 bulk_update,它的使用与批量新增方法 bulk_create 相似,使用说明如下:
# 新增两行数据
b1 = BookInfo.objects.create(book_name='xx1', pub_date='1978-03-05',read_count=5, comment_count=7, is_delete=0)
b2 = BookInfo.objects.create(book_name='xx2', pub_date='1977-04-05',read_count=3, comment_count=6, is_delete=0)# 修改字段read_count和book_name的数据
b1.read_count=6
b2.book_name='xx3'In [87]: # 批量修改字段read_count和book_name的数据
In [88]: BookInfo.objects.bulk_update([b1,b2], fields=['read_count','book_name'])
Out[88]: 2
4.4 数据删除
数据删除有3种方式:删除数据表的全部数据、删除一行数据和删除多行数据,实现方式如下:
In [91]: # 删除一条id为13的数据In [92]: BookInfo.objects.get(id=13).delete()
Out[92]: (1, {'chapter03_Model.BookInfo': 1})In [93]: # 删除多条数据In [94]: BookInfo.objects.filter(read_count=70).delete()
Out[94]: (0, {})In [95]: BookInfo.objects.filter(comment_count=70).delete()
Out[95]: (2, {'chapter03_Model.BookInfo': 2})In [96]: # 删除数据表中的全部数据In [97]: BookInfo.objects.all().delete()
Out[97]: (21, {'chapter03_Model.PeopleInfo': 12, 'chapter03_Model.BookInfo': 9})
删除数据的过程中,如果删除的数据设有外键字段,就会同时删除外键关联的数据,因为我们建立模型的时候使用的是:
比如删除数据表 book_info 里主键等于 2 的数据(简称为数据A),在数据表 people_info 里,有些数据(简称为数据B)关联了数据A,那么在删除数据A时,也会同时删除数据B。
4.5 数据查询
首先将测试数据恢复至原样,如下图所示:
以数据表 book_info 和 people_info 为例,在 Django5Study 项目的 Shell 模式下使用 ORM 框架提供的 API 方法实现数据查询,代码如下:
In [1]: from chapter03_Model.models import *
# 全表查询
# SQL: Select * from book_info,数据以列表返回
In [2]: b1 = BookInfo.objects.all()
# 查询第一条数据,序列从0开始
In [3]: b1[0]
Out[3]: <BookInfo: 射雕英雄传>
# 查询前3条数据
# SQL: Select * from book_info LIMIT 3
# SQL语句的LIMIT方法,在Django中使用列表截取即可
In [4]: b_list = BookInfo.objects.all()[:3]
In [5]: print(b_list)
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>]># 查询某个字段
# SQL: Select book_name from book_info
# values方法,数据以列表返回,列表元素以字典表示
In [6]: b = BookInfo.objects.values('book_name')
In [7]: print(b)
In [8]: b[1].get('book_name')
Out[8]: '天龙八部'
# values_list方法,数据以列表返回,列表元素以元组表示
In [9]: BookInfo.objects.values_list('read_count')[:2]
Out[9]: <QuerySet [(12,), (36,)]>
# 使用get方法查询数据
# SQL: select * from book_info where id=2
In [10]: b1 = BookInfo.objects.get(id=1)
In [11]: b1.book_name
Out[11]: '射雕英雄传'
# 使用filter方法查询数据,注意区分get和filter的差异
In [12]: b2 = BookInfo.objects.filter(id=2)
In [13]: print(b2)
<QuerySet [<BookInfo: 天龙八部>]>
In [14]: print(b1)
射雕英雄传
In [15]: b2[0].book_name
Out[15]: '天龙八部'
# SQL的and查询主要在filter里面添加多个查询条件
In [17]: b = BookInfo.objects.filter(book_name='射雕英雄传', read_count=12)
In [18]: b
Out[18]: <QuerySet [<BookInfo: 射雕英雄传>]>
# filter的查询条件可设为字典格式
In [19]: d = dict(book_name='天龙八部', read_count=36)
In [20]: b = BookInfo.objects.filter(**d)
In [21]: b
Out[21]: <QuerySet [<BookInfo: 天龙八部>]># SQL的or查询,需要引入Q,编写格式:Q(field=value)|Q(field=value)
# 多个Q之间使用"|"隔开即可
# SQL: select * from book_info where id=1 or book_name='雪山飞狐'
>>> from django.db.models import Q
In [23]: b1 = BookInfo.objects.filter(Q(id=1)|Q(book_name='雪山飞狐'))
In [24]: b1
Out[24]: <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 雪山飞狐>]># SQL的不等于查询,在Q查询前面使用"~"即可
# SQL语句: select * from book_info where not (id=3)
In [25]: b2 = BookInfo.objects.filter(~Q(id=3))
In [26]: b2
Out[26]: <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
# 还可以使用exclude实现不等于查询
In [27]: b3 = BookInfo.objects.exclude(id=3)
In [28]: b3
Out[28]: <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
# 使用count方法统计查询数据的数据量
In [31]: BookInfo.objects.exclude(id=3).count()
Out[31]: 3
# 去重查询,distinct方法无须设置参数,去重方式根据values设置的字段执行
# SQL: select distinct gender from people_info;
In [32]: PeopleInfo.objects.values('gender').distinct()
Out[32]: <QuerySet [{'gender': 1}, {'gender': 0}]>
# 根据字段id降序排列,降序只要在order_by里面的字段前面加"-"即可
# order_by可设置多字段排列,如BookInfo.objects.order_by('-id', 'job')
In [43]: BookInfo.objects.order_by('-id').all()[:2]
Out[43]: <QuerySet [<BookInfo: 雪山飞狐>, <BookInfo: 笑傲江湖>]>
# 聚合查询,实现对数据值求和、求平均值等。由annotate和aggregate方法实现
# annotate类似于SQL里面的GROUP BY方法
#如果不设置values,默认对主键进行GROUP BY分组
# SQL: select gender,count(gender) as 'gender__count' from people_info group by gender
In [44]: from django.db.models import Sum, Count
In [50]: b = PeopleInfo.objects.values('gender').annotate(Count('gender'))
In [51]: print(b.query) # 返回查询的sql语句# aggregate是计算某个字段的值并只返回计算结果
# SQL: select count(id) as 'id_count' from PeopleInfo
In [52]: b = PeopleInfo.objects.aggregate(count_id=Count('id'))
In [53]: b
Out[53]: {'count_id': 17}# union、intersection和difference语法
# 每次查询结果的字段必须相同
# 第一次查询结果v1
In [55]: p1 = PeopleInfo.objects.filter(gender=1)
# 第二次查询结果v2
In [57]: p2 = PeopleInfo.objects.filter(gender=0)
# 使用SQL的UNION来组合两个或多个查询结果的并集
# 获取两次查询结果的并集
In [58]: p1.union(p2)
# 使用SQL的INTERSECT来获取两个或多个查询结果的交集
# 获取两次查询结果的交集
In [60]: p1 = PeopleInfo.objects.filter(gender=1)
In [61]: p2 = PeopleInfo.objects.filter(age__lt=26)
In [62]: p1.intersection(p2)
Out[62]: <QuerySet [<PeopleInfo: 郭靖>, <PeopleInfo: 令狐冲>]>
# 使用SQL的EXCEPT来获取两个或多个查询结果的差
# 以p1为目标数据,去除p1和p2的共同数据
In [63]: p1.difference(p2)
上述例子讲述了开发中常用的数据查询方法,但有时需要设置不同的查询条件来满足多方面的查询要求。上述的查询条件 filter 和 get 是使用等值的方法来匹配结果。若想使用大于、不等于或模糊查询的匹配方法,则可在查询条件 filter 和 get 里使用下表所示的匹配符实现:
综上所述,在查询数据时可以使用查询条件 get 或 filter 实现,但是两者的执行过程存在一定的差异,说明如下:
- 查询条件 get:查询字段必须是主键或者唯一约束的字段,并且查询的数据必须存在,如果查询的字段有重复值或者查询的数据不存在,程序就会抛出异常信息。
- 查询条件 filter:查询字段没有限制,只要该字段是数据表的某一字段即可。查询结果以列表形式返回,如果查询结果为空(查询的数据在数据表中找不到),就返回空列表。