模型层-基本操作
orm#
在我们常说的MVC模型或MTV模型中, ORM操作就占了很大的比重, 其中的模型(M)就代表的对数据库的操作. 没有ORM, 我们只能通过pymysql等模块操作数据库, 然后再传入原生的sql语句字符串来实现对数据库的增删改查, 而这就导致一个问题, 我们作为程序员, 不是专业的DBA, 写出来的sql语句可能效率很低, 并且不易维护, 并且还极易出错, 可能会把许多时间花在调试sql语句上, 这样开发效率就非常低下.
此外, 数据模型与数据库不能实现解耦, 一旦我们需要更换数据库, 就需要对代码进行大量的修改. 基于这种种原因, orm
(对象关系映射)就被发明出来, 它能让程序员以熟悉的操作对象的方式来操作数据库, 能够轻松的实现创造表, 增删改查记录, 而无需编写一条原生sql语句, 并且它能让我们的数据模型不依赖与具体的数据库, 只需要在配置文件中简单的改动一些配置, 就能轻松的更换数据库.
总结上面的话, ORM就是能将程序员编写的代码翻译成sql语句, 再通过pymysql等模块来直接操作数据库,相当于在它的基础上又封装了一层.
创建模型表和数据库表#
在Django利用orm操作可以帮我们自动在数据库中创建表.
- 首先在app目录下的models模块下创建一个User模型类
from django.db import models
class User(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
- 配置数据库
这里我们选择mysql作为最后的存储引擎, 这时候就可以在settings文件做一个简单的配置了.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test',
'USER': 'root',
'PASSWORD': '123',
'HOST': 'localhost',
'PORT': 3306
}
}
更改数据库之后还不要忘记在项目或app目录下的__init__
文件中添加下面两句话
import pymysql
pymysql.install_as_MySQLdb()
- 迁移
现在只是简单的在逻辑层面上和数据库建立了关系, 还需要在数据库中真正的创建表.
这需要在shell环境下执行下面两句命令行:
python3 manage.py makemigrations
python3 manage.py migrate
创建上面的表相当于在数据库执行下面的sql语句
CREATE TABLE `app01_user` (
`id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
`name` VARCHAR ( 20 ) NOT NULL,
`age` INTEGER NOT NULL
);
- 完成
到此模型表就与数据的库表建立一对一的关系了.
需要注意的是
- 当我们在表中没有显示的声明哪个字段是主键的时候, ORM会自动为我们创建一个名为id的主键字段
- 表名
app01_user
是由Django默认生成的, 规则是app名称_模型类名
- 我们每一次更新了模型表中的字段的信息了, 需要重新执行上面两条迁移命令, 来与数据实现同步.
常用字段和参数#
字段合集#
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)
- 二进制类型
字段参数#
通用字段
(1)null
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
(2)blank
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
(3)default
字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。
(4)primary_key
如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。
(5)unique
如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
(6)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
时间字段参数:
auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段。
上面两个参数互斥,不能共存
关系字段-ForeignKey
to
设置要关联的表
to_field
设置要关联的表的字段
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE
删除关联数据,与之关联也删除
db_constraint
是否在数据库中创建外键约束,默认为True。
关系字段-OneToOne
to
设置要关联的表。
to_field
设置要关联的字段。
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。(参考上面的例子)
与外键的行为类似, 一对一字段一般应用在扩展字段上.
单表操作#
添加表记录#
添加单条数据
假设当前以User表为例
class User(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
添加单条数据有两种方式:
方式一
# create方法添加数据会直接返回创建出来的对象
user = models.User.objects.create(name='user1')
print(user)
# out: user1
方式2
# save方法是先创建一个User对象, 再通过调用对象的save方法, 才能执行保存到数据库的操作.
user = models.User(name='user2')
user.save()
添加多条数据
添加多条数据, 我们第一反应就是通过循环调用create
方法或save
方法来执行插入操作, 但是这样的效率非常低. 需要通过频繁的请求数据库才能完成操作
为了避免这种效率低下的方式, 就需要用到bulk_create
方式来进行批量操作
users = [models.User(name=f'user{i}') for i in range(10)]
models.User.objects.bulk_create(users)
# (0.003) INSERT INTO `user` (`name`) VALUES ('user0'), ('user1') ...; args=('user0', 'user1', 'user2', ...)
# 循环创建多个对象
for i in range(10):
models.User.objects.create(name=f'user{i}')
# (0.003) INSERT INTO `app01_user` (`name`) VALUES ('user0'); args=['user0']
# (0.000) INSERT INTO `app01_user` (`name`) VALUES ('user1'); args=['user1']
# (0.001) INSERT INTO `app01_user` (`name`) VALUES ('user2'); args=['user2']
...
上面的方式可以通过在settings文件中配置打印sql语句就可以看出来, 批量操作只请求了数据库一次, 而create方法则每创建一个对象就需要请求一次数据库.
删除表记录#
方式一: 直接多个删除, 在QuerySet
对象直接调用delete方法. 会把符合条件的全部都删除.
models.User.objects.filter(pk__gt=6).delete()
方式二: 获取到单个对象, 直接调用delete方法来进行删除
user = models.User.objects.filter(pk=2).first() # type: models.User
user.delete()
注意点:
- 不能直接在
User.objects
对象上执行delete操作 - 不能在
values
或values_list
上执行delete操作 - Django删除对象默认的是级联删除, 删除带有外键 约束的对象的时候, 会连带被约束的对象一起删除.
修改表记录#
修改表记录同样有两种方式.
方式一: 直接在QuerySet对象上执行修改操作
models.User.objects.filter(pk=4).update(name='user666')
# UPDATE `app01_user` SET `name` = 'user666' WHERE `app01_user`.`id` = 4;
user = models.User.objects.filter(pk=4).first() # type: models.User
方式二: 在具体的对象上执行save方法, 来进行修改操作.
user.name = 'user777'
user.save()
# UPDATE `app01_user` SET `name` = 'user777', `age` = 18 WHERE `app01_user`.`id` = 4
区别:
- 查看执行的sql语句, update只会对对应的字段进行修改, save方法会对所有字段进行修改, update效率更高
- update是可以进行批量修改的.
小疑问: Django执行save方法如何正确的区分insert或update方法?
参考官方文档了解了原因.
内部是这么做逻辑判断的.
- 如果对象的主键存在的话(主键不为空或是空字符串), 就执行update操作
- 如果对象的主键存在的话或者当执行了update操作之后, 但是没有任何影响, 这时候会执行insert操作
针对上面第二条, 说明我们最好不要主动添加主键, 因为会多走一次数据库查询. 就像下面这样
# UPDATE `app01_user` SET `name` = 'user10', `age` = 18 WHERE `app01_user`.`id` = 10;
# INSERT INTO `app01_user` (`id`, `name`, `age`) VALUES (10, 'user10', 18);
实际多执行了一条sql语句.
查询表记录#
查询是ORM的重中之重, 熟练掌握查询的操作是十分有必要的, 初始, 如果我们需要理解ORM和数据操作之间的关系, 可以通过两种方式查看.
- 当执行查询操作获得了querySet对象的时候, 可以调用query属性查看当前操作执行sql语句
- 直接在settings文件中配置logging参数, 直接打印出每条sqla语句的转换结果.
logging配置文件
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
查询API#
假设当前的模型类的结构如下所示
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.FloatField()
pub_time = models.DateField()
def __str__(self):
return self.name
表里数据:
1 三国演义 22.2 2019-08-14
2 红楼梦 33.3 2018-02-22
3 三国演义2 20 2019-10-25
4 水浒传 10 2017-01-02
5 射雕英雄传 24 2018-02-12
6 西游记 54.9 2019-07-10
<1> all(): 查询所有结果
models.Book.objects.all()
<QuerySet [<Book: 三国演义>, <Book: 红楼梦>, <Book: 三国演义2>, <Book: 水浒传>, <Book: 射雕英雄传>, <Book: 西游记>]>
<2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象, 筛选条件之间是and的关系.
models.Book.objects.filter(price__gt=21.0, name__contains='三国演义')
<QuerySet [<Book: 三国演义>]>
<3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误, 这个方法不是很好用
print(models.Book.objects.get(name='三国演义'))
print(models.Book.objects.get(name__contains='三国演义'))
# 第二个报错 get() returned more than one Book -- it returned 2!
<4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象
print(models.Book.objects.exclude(name__contains='三国演义'))
<QuerySet [<Book: 红楼梦>, <Book: 水浒传>, <Book: 射雕英雄传>, <Book: 西游记>]>
<5> order_by(*field): 对查询结果排序, 默认是升序, 在字段前面加-
号表示降序
print(models.Book.objects.order_by('price'))
print(models.Book.objects.order_by('-price'))
<QuerySet [<Book: 水浒传>, <Book: 三国演义2>, <Book: 三国演义>, <Book: 射雕英雄传>, <Book: 红楼梦>, <Book: 西游记>]>
<QuerySet [<Book: 西游记>, <Book: 红楼梦>, <Book: 射雕英雄传>, <Book: 三国演义>, <Book: 三国演义2>, <Book: 水浒传>]>
<6> reverse(): 对查询结果反向排序
print(models.Book.objects.order_by('price').reverse())
<QuerySet [<Book: 西游记>, <Book: 红楼梦>, <Book: 射雕英雄传>, <Book: 三国演义>, <Book: 三国演义2>, <Book: 水浒传>]>
# 结果与上面的反序相同
<8> count(): 返回数据库中匹配查询(QuerySet)的对象数量
print(models.Book.objects.all().count())
# 6
<9> first(): 返回第一条记录, 这个经常使用
print(models.Book.objects.first())
# 三国演义
<10> last(): 返回最后一条记录
print(models.Book.objects.last())
西游记
<11> exists(): 如果QuerySet包含数据,就返回True,否则返回False
print(models.Book.objects.filter(name='1234').exists())
False
<12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列.
print(models.Book.objects.filter(price__gt=24).values('name', 'pub_time'))
<QuerySet [{'name': '红楼梦', 'pub_time': datetime.date(2018, 2, 22)}, {'name': '西游记', 'pub_time': datetime.date(2019, 7, 10)}]>
<13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
print(models.Book.objects.filter(price__gt=24).values_list('name', 'pub_time'))
<QuerySet [('红楼梦', datetime.date(2018, 2, 22)), ('西游记', datetime.date(2019, 7, 10))]>
<14> distinct(): 从返回结果中剔除重复纪录, 对查询结果的每一个字段都做比较.
注意点#
- 返回结果是QuerySet对象的话, 支持链式操作, 可以一直做过滤操作
- QuerySet对象实行的是惰性查询, 对QuerySet对象进行切片, 过滤, 创建, 传递值都不会真正查询数据库
- 对于查询结果是否存在, 使用exists方法比bool值判断更高效
对于会真正执行数据操作的主要有以下操作
- 迭代
- 带有步长的切片
- len()
- list
- bool
双下划线查询#
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
models.Tb1.objects.filter(name__contains="ven") # 获取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.filter(id__range=[1, 3]) # id范围是1到3的,等价于SQL的bettwen and
类似的还有:startswith,istartswith, endswith, iendswith
date字段还可以:
models.Class.objects.filter(first_day__year=2017)
date字段可以通过在其后加__year,__month,__day等来获取date的特点部分数据
# date
#
# Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
# Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
# year
#
# Entry.objects.filter(pub_date__year=2005)
# Entry.objects.filter(pub_date__year__gte=2005)
# month
#
# Entry.objects.filter(pub_date__month=12)
# Entry.objects.filter(pub_date__month__gte=6)
# day
#
# Entry.objects.filter(pub_date__day=3)
# Entry.objects.filter(pub_date__day__gte=3)
# week_day
#
# Entry.objects.filter(pub_date__week_day=2)
# Entry.objects.filter(pub_date__week_day__gte=2)
需要注意的是在表示一年的时间的时候,我们通常用52周来表示,因为天数是不确定的,老外就是按周来计算薪资的哦~