一、ORM 简介
1.1. web开发的分工模式
-
一般中大型公司(或者数据量巨大、读取数据的需求频繁并且追求极致效率的公司)会有专门的DBA管理数据库,编写sql语句,对于应用层开发来说,不用写sql语句,直接调用他写的接口就行。所以在这种公司一般来说,开发人员应该'供'着DBA,因为你想写入或者取出的数据需要依赖于DBA去执行,或者是你写的比较复杂的sql语句需要让DBA帮你看一下,效率行不行、是不是需要优化等等,这就需要看你们的交情或者其心情了。哈哈(开个玩笑)。
-
应用程序开发+sql语句编写。
这种情况多存在于小公司,没有专门设置DBA岗位,要求开发人员什么都会一些,linux、数据库、前端等等,这样成本降低并且减少由于部门之间的沟通带来的损失,提高工作流程效率。
-
应用程序开发+ORM。
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
ORM由来
让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。
几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的。
ORM优势
- 提高开发效率。
- 不同数据库可以平滑切换。
- 让软件开发人员专注于业务逻辑的处理,提高了开发效率。
- ORM 代码转换为 SQL 语句时,需要花费一定的时间,执行效率会有所降低。
- 长期写 ORM 代码,会降低编写 SQL 语句的能力。
ORM 解析过程
- ORM 会将 Python 代码转成为 SQL 语句。
- SQL 语句通过 pymysql 传送到数据库服务端。
- 在数据库中执行 SQL 语句并将结果返回。
二、数据库配置
2.1 Django 如何使用 mysql 数据库
创建 MySQL 数据库( ORM 无法操作到数据库级别,只能操作到数据表)语法:
create database 数据库名称 default charset=utf8; # 防止编码问题,指定为 utf8
例如常见 runoob 数据库,编码指定为 utf8:
create database runoob default charset=utf8;
我们在项目的 settings.py 文件中找到 DATABASES 配置项,将其信息修改为:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库引擎 'NAME': 'runoob', # 数据库名称 'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1 'PORT': 3306, # 端口 'USER': 'root', # 数据库用户名 'PASSWORD': '123456', # 数据库密码 } }
上面包含数据库名称和用户的信息,它们与 MySQL 中对应数据库和用户的设置相同。Django 根据这一设置,与 MySQL 中相应的数据库和用户连接起来。
上面是给项目中的所有的应用都配置成MySQL数据库,当然我们也可以给单个应用配置数据库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'ormtest', # 要连接的数据库,连接前需要创建好 'USER':'root', # 连接数据库的用户名 'PASSWORD':'', # 连接数据库的密码 'HOST':'127.0.0.1', # 连接主机,默认本级 'PORT':3306 # 端口 默认3306 }, 'app01': { #可以为每个app都配置自己的数据,并且数据库还可以指定别的,也就是不一定就是mysql,也可以指定sqlite等其他的数据库 'ENGINE': 'django.db.backends.mysql', 'NAME':'ormtest', # 要连接的数据库,连接前需要创建好 'USER':'root', # 连接数据库的用户名 'PASSWORD':'', # 连接数据库的密码 'HOST':'127.0.0.1', # 连接主机,默认本级 'PORT':3306 # 端口 默认3306 } } app配置单独的数据库
接下来,告诉 Django 使用 pymysql 模块连接 mysql 数据库:
# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置 import pymysql pymysql.install_as_MySQLdb()
2.2 同一个app使用不同的库
settings
views
三、定义模型
3.1 创建 APP
Django 规定,如果要使用模型,必须要创建一个 app。我们使用以下命令创建一个 TestModel 的 app:
django-admin.py startapp TestModel
结构目录如下
HelloWorld |-- HelloWorld |-- manage.py ... |-- TestModel | |-- __init__.py | |-- admin.py | |-- models.py | |-- tests.py | `-- views.py
我们修改 TestModel/models.py 文件,代码如下:
# models.py from django.db import models class Test(models.Model): name = models.CharField(max_length=20)
以上的类名代表了数据库表名,且继承了models.Model,类里面的字段代表数据表中的字段(name),数据类型则由CharField(相当于varchar)、DateField(相当于datetime), max_length 参数限定长度。
接下来在 settings.py 中找到INSTALLED_APPS这一项,如下:
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'TestModel', # 添加此项 )
在对应数据库中生成表结构
$ python3 manage.py makemigrations (appname) # 让 Django 知道我们在我们的模型有一些变更
对应的app下面,migrations出现一个0001_initial.py的文件,这个文件是你执行了上述指令之后产生的脚本文件,这个文件就是一个记录。
接下来我们继续输入指令:
python manage.py migrate
看到几行 "Creating table…" 的字样,你的数据表就创建好了。
Creating tables ... …… Creating table TestModel_test #我们自定义的表 ……
这个指令其实就是执行第一个指令生成的记录也就是那个脚本文件,然后就会在你对应的数据库中生成一个真正的表,生成的表名字前面会自带应用的名字,例如:你的userinfo表在数据表里面叫做:app01_userinfo。
在执行 python manager.py makemigrations时 Django 会在相应的 app 的migrations文件夹下面生成 一个python脚本文件 在执行 python manager.py migrate 时 Django才会生成数据库表,那么Django是如何生成数据库表的呢? Django是根据 migrations下面的脚本文件来生成数据表的 每个migrations文件夹下面有多个脚本,那么django是如何知道该执行那个文件的呢,django有一张django-migrations表,表中记录了已经执行的脚本,那么表中没有的就是还没执行的脚本,则 执行migrate的时候就只执行表中没有记录的那些脚本。 有时在执行 migrate 的时候如果发现没有生成相应的表,可以看看在 django-migrations表中看看 脚本是否已经执行了, 可以删除 django-migrations 表中的记录 和 数据库中相应的 表 , 然后重新 执行
注意:尽管我们没有在 models 给表设置主键,但是 Django 会自动添加一个 id 作为主键。
创建的时候除了自己创建的表,还会创建一些django自带的表。
一些说明:
- 表myapp_person的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数,强烈建议使用小写表名,特别是使用MySQL作为后端数据库时。
- id 字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
- 本示例中的CREATE TABLE SQL使用PostgreSQL语法进行格式化,但值得注意的是,Django会根据配置文件中指定的数据库后端类型来生成相应的SQL语句。
- Django支持MySQL5.5及更高版本。
3.2 自定义表名及字段名称
在我们创建一个模型时,Django的ORM会根据应用名(app name), 模型名(model name)和字段名(field name)自动在数据库中创建数据表。比如我们有一个Blog的应用,里面有Article模型, 其中Article模型有title这个字段,那么Django默认会创建一个名为blog_article的数据表,其中有title这个字段。假如我们希望把表名改为article,标题改为article_title,以便与已经存在的数据表或字段建立映射关系,我们可以按如下代码操作。
class Article(models.Model): """文章模型""" # 通过db_column自定义数据表中字段名 title = models.CharField('标题', max_length=200, db_column='article_title') slug = models.SlugField('slug', max_length=60, blank=True) def __str__(self): return self.title class Meta: db_table = 'article' # 通过db_table自定义数据表名
四、ORM 常用字段和参数
4.1 常用字段
AutoField
int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。
IntegerField
一个整数类型,范围在 -2147483648 to 2147483647。
CharField
字符类型,必须提供max_length参数, max_length表示字符长度。
DateField
日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。
DateTimeField
日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。
4.2 字段合集(争取记忆)
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
4.3 自定义字段(了解为主)
class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED'
自定义char类型字段:
class FixedCharField(models.Field): """ 自定义的char类型的字段类 """ def __init__(self, max_length, *args, **kwargs): super().__init__(max_length=max_length, *args, **kwargs) self.length = max_length def db_type(self, connection): """ 限定生成数据库表的字段类型为char,长度为length指定的值 """ return 'char(%s)' % self.length class Class(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=25) # 使用上面自定义的char类型的字段 cname = FixedCharField(max_length=25)
创建的表结构:
附ORM字段与数据库实际字段的对应关系
对应关系: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)',
4.4 参数字段
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。 (6)db_index 如果db_index=True 则代表着为此字段设置数据库索引。 DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。 (7)auto_now_add 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 (8)auto_now 配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。
关于auto_now,你需要知道的事情:
当需要更新时间的时候,我们尽量通过datetime模块来创建当前时间,并保存或者更新到数据库里面,看下面的分析: 假如我们的表结构是这样的 class User(models.Model): username = models.CharField(max_length=255, unique=True, verbose_name='用户名') is_active = models.BooleanField(default=False, verbose_name='激活状态') 那么我们修改用户名和状态可以使用如下两种方法: 方法一: User.objects.filter(id=1).update(username='nick',is_active=True) 方法二: _t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save() 方法一适合更新一批数据,类似于mysql语句update user set username='nick' where id = 1 方法二适合更新一条数据,也只能更新一条数据,当只有一条数据更新时推荐使用此方法,另外此方法还有一个好处,我们接着往下看 具有auto_now属性字段的更新 我们通常会给表添加三个默认字段 - 自增ID,这个django已经默认加了,就像上边的建表语句,虽然只写了username和is_active两个字段,但表建好后也会有一个默认的自增id字段 - 创建时间,用来标识这条记录的创建时间,具有auto_now_add属性,创建记录时会自动填充当前时间到此字段 - 修改时间,用来标识这条记录最后一次的修改时间,具有auto_now属性,当记录发生变化时填充当前时间到此字段 就像下边这样的表结构 class User(models.Model): create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') username = models.CharField(max_length=255, unique=True, verbose_name='用户名') is_active = models.BooleanField(default=False, verbose_name='激活状态') 当表有字段具有auto_now属性且你希望他能自动更新时,必须使用上边方法二的更新,不然auto_now字段不会更新,也就是: _t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save() json/dict类型数据更新字段 目前主流的web开放方式都讲究前后端分离,分离之后前后端交互的数据格式大都用通用的jason型,那么如何用最少的代码方便的更新json格式数据到数据库呢?同样可以使用如下两种方法: 方法一: data = {'username':'nick','is_active':'0'} User.objects.filter(id=1).update(**data) 同样这种方法不能自动更新具有auto_now属性字段的值 通常我们再变量前加一个星号(*)表示这个变量是元组/列表,加两个星号表示这个参数是字典 方法二: data = {'username':'nick','is_active':'0'} _t = User.objects.get(id=1) _t.__dict__.update(**data) _t.save() 方法二和方法一同样无法自动更新auto_now字段的值 注意这里使用到了一个__dict__方法 方法三: _t = User.objects.get(id=1) _t.role=Role.objects.get(id=3) _t.save() #想让auto_now更新数据时自动更新时间,必须使用save方法来更新数据,所以很不方便,所以这个创建时自动添加时间或者更新时间的auto_now方法我们最好就别用了,比较恶心,并且支持我们自己来给这个字段更新时间: models.py: class Book(models.Model): name = models.CharField(max_length=32) date1 = models.DateTimeField(auto_now=True,null=True) date2 = models.DateTimeField(auto_now_add=True,null=True) views.py: import datetime models.Book.objects.filter(id=1).update( name='chao', date1=datetime.datetime.now(), date2=datetime.datetime.now(), ) 关于auto_now和auto_now_add
五、单表操作
5.1 增:添加表记录
首先我们先要做一个简单的流程,方便我们测试:
#url: urlpatterns = [ url(r'^index/', views.index), ] # views: def index(request): return render(request,'index.html') # html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <h1>欢迎访问index页面</h1> </body> </html>
然后我们通过在index函数中构建一些逻辑,展示我们对单表的增删改查。在研究增删改查之前我们一定要明确ORM的对应关系:
在python中orm的对应关系有三种:
类 ----------> 表
类的对象 ----------> 行(记录)
类的属性 ----------> 表的字段(重点)
我们要想操作一行的数据,就应该将相应的类引入,然后通过实例化对象增加数据。接下来,我们重新创建一个数据库为orm01,创建一个student表结构,从这个表中去研究单表的操作。
这里需要注意:
1. 更改settings里面的数据库配置:orm01.
2. 在models文件中创建student类:
class Student(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16) age = models.IntegerField()
3. 生成记录并且创建表结构
python manage.py makemigrations python manage.py migrate
接下来我们就开始研究如何增加表记录。
方式1:模型类实例化对象
从index函数中操作:
def index(request): # 实例化一个对象这就是一条记录 student_obj = models.Student( name='王阔', age=18 ) # 将此记录增加到数据表中 student_obj.save() return render(request,'index.html')
查看数据表:我们添加成功了。
方式2:通过 ORM 提供的 objects 提供的方法 create 来实现(推荐)
我们通过方式1给student表中添加了一条表记录,但是上面那种方法不灵活,接下来我们介绍一种更加灵活的方法。
models.Student.objects
objects比较特殊,我们称它为类对应的控制器,这个控制器对象可以调用数据表相应的增删改查等方法,非常灵活。
models.Student.objects.create( name='傻强', age=20 )
这样以来我们就添加成功了:
而且我们通过查询create方法的源码可以得知:
Creates a new object with the given kwargs, saving it to the database and returning the created object.
他可以创建一个新的对象保存到相应的数据表中,并且返回这个新创建的对象。那么接下来我们查看一下他的返回值并输入一下:
这样它既向我们的数据表中添加了值,又将创建的对象返回了:
new_obj = models.Student.objects.create( name='志远', age=19 ) print(new_obj) # Student object print(new_obj.name) # 志远
那么,以后我们就将这种(models类的对象)称之为model对象。(很重要一定要记住)
方式3:批量创建
我们以后工作中肯定会让你批量插入很多数据,怎么解决?有同学肯定会说循环create,但是每次create都需要操作数据库,这是一个IO操作,如果这样的话数据量越大,你的效率越低。所以针对这种情况我们可以通过批量创建。
假如我们依次插入20条数据:
obj_list = [models.Student(name=f'乔妹儿{i}', age=20) for i in range(1, 21)] models.Student.objects.bulk_create(obj_list)
方式4:更新或增加
orm还有一种方法是对一条表记录进行更新或者增加的操作:有则更新无则增加,类似于字典的这个操作dic['name'] = 'barry'。
models.Student.objects.update_or_create( name='乔妹儿1', # 筛选条件 defaults={ # 需要更新或者增加的值 'age': 1000, } )
5.2 查询
本来我们应该研究删除表记录,但是我们这里先要研究几个简单的查询,因为这些查询返回两种特殊的对象,对于不同的对象他们的删、改的的方法不同。
1.all()全部取出
通过all从orm取出来的是一个QuerySet类型,这里面有很多个Student类的对象也就是model对象,这个QuerySet类似于列表,但是不同与列表,后面我们会详细对比讨论,在这里我们知道可以循环遍历取值即可。
index函数: all_objs = models.Student.objects.all() print(all_objs) # QuerySet类型 这里面有很多个Student类的对象也就是model对象。 ''' < QuerySet[ < Student: Student object >, < Student: Student object >, < Student: Student object >,...'...(remaining elements truncated)...']> ''' # 通过遍历可以获取每个对象的name属性 for i in all_objs: print(i.name)
model对象与queryset集合是我们经常用的两个对象,一定要分清楚。
上面我们for循环遍历时,每次都需要.name获取name属性,比较麻烦,我们可以不可以直接打印对象获取对应的姓名呢?这就要想到特殊的双下方法__str__.我们可以在Student类中定一个__str__方法,这样以后我们就可以用于调试了。
class Student(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16) age = models.IntegerField() def __str__(self): return self.name
2.filter(条件) 条件查询
通过条件查询获取结果,返回的也是QuerySet类型,如果查询不到内容不会报错,返回一个空的QuerySet集合。
objs = models.Student.objects.filter(age=20) print(objs) ''' <QuerySet [<Student: 傻强>, <Student: 乔妹儿2>, <Student: 乔妹儿3>, <Student: 乔妹儿4>, <Student: 乔妹儿5>, <Student: 乔妹儿6>, <Student: 乔妹儿7>, <Student: 乔妹儿8>, <Student: 乔妹儿9>, <Student: 乔妹儿10>, <Student: 乔妹儿11>, <Student: 乔妹儿12>, <Student: 乔妹儿13>, <Student: 乔妹儿14>, <Student: 乔妹儿15>, <Student: 乔妹儿16>, <Student: 乔妹儿17>, <Student: 乔妹儿18>, <Student: 乔妹儿19>, <Student: 乔妹儿20>]> ''' objs = models.Student.objects.filter(id=2) print(objs) # <QuerySet [<Student: 傻强>]> print(objs[0].id) # 2 可以通过索引取值 objs = models.Student.objects.filter(name='太白') print(objs) # <QuerySet []>
3.get(条件) 条件查询
这个get比较特殊,它返回的是model对象,通过get条件查询,查询的结果有且只有1个。
如果超过一个则报错为:get() returned more than one Student -- it returned 2(有几个显示几个)!
如果没有则报错为:Student matching query does not exist.
# obj = models.Student.objects.get(name='太白') # 报错:Student matching query does not exist. # obj = models.Student.objects.get(age=20) # get() returned more than one Student -- it returned 20! obj = models.Student.objects.get(id=1) print(obj) # model对象
所以一般如果我们使用get条件查询,前提一定要确定你的条件锁定的就是一条行记录。
5.3. 删:删除行记录
删除就要从你的对象性质来讨论了。
1. 调用model对象删除
这样就是删除单条记录
models.Student.objects.get(id=20).delete()
2.调用QuerySet集合删除
这样就是批量删除记录,满足条件的记录都会被删除。
models.Student.objects.filter(age=20).delete()
(等学到外键的时候再说) 在 Django 删除对象时,会模仿 SQL 约束 ON DELETE CASCADE 的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:
b = Blog.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. b.delete()
要注意的是: delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Entry.objects.delete() 方法导致 所有的 记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:
Entry.objects.all().delete()
如果不想级联删除,可以设置为:
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
5.4.改:更新行记录
更新记录的方法只有一个就是update,此方法只能用于QuerySet集合,model对象是不可以使用的。也就是说update可以进行批量更新的操作,并且他会返回一个更新的行记录数量的返回值。
# 我先增加了一条name一样的行记录 ''' # models.Student.objects.create( name='乔妹儿1', age=100 ) ''' count = models.Student.objects.filter(name='乔妹儿1').update(age=20) print(count) # 2 更新了2条
5.5.查:查看(获取)行记录
查询是数据库增删改查的重中之重,方法非常多,大家一定要好好练习。前三个我们都练习过了可以在练习一遍。
1.all()全部取出
通过object控制器调用,返回QuerySet类型。
通过all从orm取出来的是一个QuerySet类型,这里面有很多个Student类的对象也就是model对象,这个QuerySet类似于列表,但是不同与列表,后面我们会详细对比讨论,在这里我们知道可以循环遍历取值即可。
index函数: all_objs = models.Student.objects.all() print(all_objs) # QuerySet类型 这里面有很多个Student类的对象也就是model对象。 ''' < QuerySet[ < Student: Student object >, < Student: Student object >, < Student: Student object >,...'...(remaining elements truncated)...']> ''' # 通过遍历可以获取每个对象的name属性 for i in all_objs: print(i.name)
2.filter(条件) 条件查询
通过object控制器调用,返回QuerySet类型。
通过条件查询获取结果,返回的也是QuerySet类型,如果查询不到内容不会报错,返回一个空的QuerySet集合。
objs = models.Student.objects.filter(age=20) print(objs) ''' <QuerySet [<Student: 傻强>, <Student: 乔妹儿2>, <Student: 乔妹儿3>, <Student: 乔妹儿4>, <Student: 乔妹儿5>, <Student: 乔妹儿6>, <Student: 乔妹儿7>, <Student: 乔妹儿8>, <Student: 乔妹儿9>, <Student: 乔妹儿10>, <Student: 乔妹儿11>, <Student: 乔妹儿12>, <Student: 乔妹儿13>, <Student: 乔妹儿14>, <Student: 乔妹儿15>, <Student: 乔妹儿16>, <Student: 乔妹儿17>, <Student: 乔妹儿18>, <Student: 乔妹儿19>, <Student: 乔妹儿20>]> ''' objs = models.Student.objects.filter(id=2) print(objs) # <QuerySet [<Student: 傻强>]> print(objs[0].id) # 2 可以通过索引取值 objs = models.Student.objects.filter(name='太白') print(objs) # <QuerySet []>
还可以进行多条件查询:
objs = models.Student.objects.filter(name='健身哥', age=18) print(objs) # <QuerySet [<Student: 健身哥>]>
还可以通过打散字典的形式进行筛选
models.Student.objects.filter(**{'id':27,'name':'健身哥','age':18}).update(age=20)
注意:
1. 一定要记住每个方法是通过哪种类型调用,以及返回哪种类型。这样我们方便可以通过链式点的操作进行复杂查询。
2. filter一种是关键字形式,一个是字符串形式,很重要,这两个就可以解决不同的需求了。
3.get(条件) 条件查询
通过object控制器调用,返回model对象
这个get比较特殊,它返回的是model对象,通过get条件查询,查询的结果有且只有1个。
如果超过一个则报错为:get() returned more than one Student -- it returned 2(有几个显示几个)!
如果没有则报错为:Student matching query does not exist.
# obj = models.Student.objects.get(name='太白') # 报错:Student matching query does not exist. # obj = models.Student.objects.get(age=20) # get() returned more than one Student -- it returned 20! obj = models.Student.objects.get(id=1) print(obj) # model对象
4.exclude排除
通过object对象或者QuerySet集合调用,返回QuserySet集合。
下面我们演示了上面说到的链式点操作,可以一直进行点的操作,因为此方法可以QuerySet集合调用,并且返回的还是QuerySet集合。
objs = models.Student.objects.exclude(id=1) print(objs) # 排除id为1的行记录,将剩下所有的返回 objs = models.Student.objects.filter(age=20).exclude(name='齐佳乐') print(objs) # <QuerySet [<Student: 乔妹儿1>, <Student: 乔妹儿1>, <Student: 健身哥>, <Student: 张雨薇>]> objs = models.Student.objects.filter(age=20).exclude(name='齐佳乐').exclude(name='健身哥') print(objs) # <QuerySet [<Student: 乔妹儿1>, <Student: 乔妹儿1>, <Student: 张雨薇>]>
5.order_by排序
通过object对象或者QuerySet集合调用,返回QuserySet集合。
# object对象调用 objs = models.Student.objects.order_by('age') # 通过姓名升序排列 print(objs) # queryset集合调用 objs = models.Student.objects.all().order_by('age') # 通过姓名升序排列 print(objs)
通过年龄升序排列,相同年龄的按照id降序排列:
objs = models.Student.objects.all().order_by('age', '-id') print(objs)
打印结果:
<QuerySet [<Student: id:30 name:梁晨 age:18>, <Student: id:1 name:王阔 age:18>, <Student: id:25 name:董伟华 age:19>, <Student: id:3 name:志远 age:19>, <Student: id:32 name:齐佳乐 age:20>, <Student: id:28 name:张雨薇 age:20>, <Student: id:27 name:健身哥 age:20>, <Student: id:24 name:乔妹儿1 age:20>, <Student: id:4 name:乔妹儿1 age:20>, <Student: id:29 name:王天琪 age:21>, <Student: id:26 name:周泽波 age:21>, <Student: id:31 name:扛把子 age:22>]>
6.reverse反转
通过order_by返回的QuerySet集合调用,返回一个QuerySet集合。
objs = models.Student.objects.reverse() print(objs) # 这样没有作用,all filter等都没有作用 objs = models.Student.objects.order_by('id').reverse() # 只能通过order_by返回的QuerySet集合调用 print(objs)
7.count计数
通过QuerySet集合调用,返回一个元素个数。
num = models.Student.objects.all().count() print(num) # 12 num = models.Student.objects.filter(age=20).count() print(num) # 5
8.first返回第一个model对象
通过QuerySet集合调用,返回第一个model对象
obj = models.Student.objects.filter(age=20).first() print(obj)
9.last返回最后一个model对象
通过QuerySet集合调用,返回最后一个model对象
obj = models.Student.objects.filter(age=20).last() print(obj)
10.exists判断是否存在
通过QuerySet集合调用,返回bool值
flag = models.Student.objects.filter(age=25).exists() print(flag) # False
这里我们要说一句,他的效率是高的,尤其是作为if判断的条件。
# 虽然说你可以通过下面的方式判断,但是如果这个集合一下取出上百万条数据,那么你的效率就很低的,而exists翻译成sql语句是limit=1,也就是说它只看第一个对象存不存在就返回bool值了。 if Queryset集合: pass
我们也可以通过配置settings,每次执行orm语句时都将相应的sql语句展示出来,这样可以查看一下exists().
settings配置:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
再次执行一次:
11.values_list
通过QuerySet集合调用,返回一个QuerySet集合
但是这个QuerySet集合比较特殊,这个里面的元素不是model对象,而是元组的形式。
query_tuple = models.Student.objects.filter(age=20).values_list() print(query_tuple) # <QuerySet [(4, '乔妹儿1', 20), (24, '乔妹儿1', 20), (27, '健身哥', 20), (28, '张雨薇', 20), (32, '齐佳乐', 20)]> query_tuple = models.Student.objects.filter(age=20).values_list().exclude(name='齐佳乐') print(query_tuple) # < QuerySet[(4, '乔妹儿1', 20), (24, '乔妹儿1', 20), (27, '健身哥', 20), (28, '张雨薇', 20)] >
还可以指定想获取的字段:
query_tuple = models.Student.objects.filter(age=20).values_list('name','age') print(query_tuple) # <QuerySet [('乔妹儿1', 20), ('乔妹儿1', 20), ('健身哥', 20), ('张雨薇', 20), ('齐佳乐', 20)]>
12.values
通过QuerySet集合调用,返回一个QuerySet集合
但是这个QuerySet集合比较特殊,这个里面的元素不是model对象,而是字典的形式。
query_dict = models.Student.objects.filter(age=19).values() print(query_dict) # <QuerySet [{'id': 3, 'name': '志远', 'age': 19}, {'id': 25, 'name': '董伟华', 'age': 19}]> query_dict = models.Student.objects.filter(age=19).values('name', 'age') print(query_dict) # <QuerySet [{'name': '志远', 'age': 19}, {'name': '董伟华', 'age': 19}]>
13.distinct去重
通过QuerySet集合调用,返回一个QuerySet集合
这里要注意一点:无论是all还是filter 你对整个对象去重是没有意义的,只要有一个字段不同,都不是重复的。
query_objs = models.Student.objects.filter(age=20).distinct() print(query_objs) # <QuerySet [<Student: id:4 name:乔妹儿1 age:20>, <Student: id:24 name:乔妹儿1 age:20>, <Student: id:27 name:健身哥 age:20>, <Student: id:28 name:张雨薇 age:20>, <Student: id:32 name:齐佳乐 age:20>]>
所以这个去重一般都用于values或者values_list。
query_objs = models.Student.objects.filter(age=20).values('age').distinct() print(query_objs) # <QuerySet [{'age': 20}]>
14.基于双下划线的模糊查询
我们在使用filter方法时,一直在使用 = 条件,但是没有使用过> < >=等条件,这是因为ORM不支持这种写法,不用着急,我们可以根据另一种写法去实现。
query_objs = models.Student.objects.filter(age__gt=19) # 大于 query_objs = models.Student.objects.filter(age__gte=19) # 大于等于 query_objs = models.Student.objects.filter(age__lt=20) # 小于 query_objs = models.Student.objects.filter(age__lte=20) # 小于等于 query_objs = models.Student.objects.filter(age__range=[18, 20]) # 范围 左右都包含 query_objs = models.Student.objects.filter(name__contains='xiao') # 针对字符串类型,内容含有 query_objs = models.Student.objects.filter(name__icontains='xiao') # 针对字符串类型,内容含有 不区分大小写 query_objs = models.Student.objects.filter(name__startswith='x') # 匹配以x开头 query_objs = models.Student.objects.filter(name__istartswith='x') # 匹配以x开头,不区分大小写 query_objs = models.Student.objects.filter(name__endswith='o') # 匹配以x结尾 query_objs = models.Student.objects.filter(name__iendswith='o') # 匹配以x结尾,不区分大小写
15.日期
日期这个字段查询起来比较特殊,我们单独拿出来讲解,现在我们要重新创建一个表。
class Birthday(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16) date = models.DateField() def __str__(self): return self.name
别忘了生成记录以及创建到数据库中。
查看一下表结构:
注意date字段,这个字段要求的是date类型,我们如果存放的是datetime类型也是可以的,只不过只是显示年月日。
接下来我们给表中插入一些数据:
import datetime models.Birthday.objects.create(name='太白1', date=datetime.datetime.now()) models.Birthday.objects.create(name='太白2', date='2000-04-25') models.Birthday.objects.create(name='太白3', date='2000-06-25') models.Birthday.objects.create(name='太白4', date='2000-08-26') models.Birthday.objects.create(name='太白5', date='2000-12-28') models.Birthday.objects.create(name='太白6', date='2001-03-26') models.Birthday.objects.create(name='太白7', date='2001-08-30') models.Birthday.objects.create(name='太白8', date='2003-01-13') models.Birthday.objects.create(name='太白9', date='2005-10-01')
对应的数据库的数据为:
查询2000年出生的人,这样写就会报错了:
query_objs = models.Birthday.objects.filter(date='2000') print(query_objs) # "'2000' value has an invalid date format. It must be in YYYY-MM-DD format."]
查询2000年出生的人:
query_objs = models.Birthday.objects.filter(date__year='2000') print(query_objs) # <QuerySet [<Birthday: 太白2>, <Birthday: 太白3>, <Birthday: 太白4>, <Birthday: 太白5>]>
查询2000年4月出生的人:
query_objs = models.Birthday.objects.filter(date__year='2000',date__month='04') print(query_objs) # <QuerySet [<Birthday: 太白2>]>
查询2000年大于4月小于12月出生的人:
query_objs = models.Birthday.objects.filter(date__year='2000', date__month__gt='04',date__month__lt='12') print(query_objs) # <QuerySet [<Birthday: 太白3>, <Birthday: 太白4>]>