Django之ORM操作
一、前期准备
用django自带的数据库进行测试
1.连接sqlite3数据库
2.先下载驱动,测试链接
3.开始使用,建表建字段
用自己的MySQL数据库进行测试
注意,django会自动帮我们建表,但是库需要我们手动创建
1.先去settings中的database中修改django默认的数据库,改为mysql,注意,库名这些东西都大写
2.在__init__.py中告诉django使用我们的mysql数据库
import pymysql pymysql.install_as_MySQLdb()
3.连接数据库
4.下载驱动,测试连接,开始使用
在应用下面的models中新建表和字段
建立一个用户表
class User(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() register_time=models.DateField() def __str__(self): return '这是user对象:%s'%self.name
测试数据库条件准备
不需要重复的走路由层和视图函数层,如何实现?
去manage.py中拷贝以下代码块
import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE","Ormtest.settings")
然后将这些东西放在测试文件中,这里我们就叫test
下面我们的所有操作都是在test.py中
import os if __name__ == "__main__": import django django.setup() from app01 import models
二、常用字段和参数
1.AutoField
int自增列,必须填入参数primary_key=True,当model中如果没有自增列,则会自动创建一个列名为id的列。
2.IntegerField
一个整数类型,范围在 -2147483648 to 2147483647。(村手机号,我们一般选择使用字符串进行存储,不用这个字段,而且用这个字段位数也不够)
注意:整数后面的()并不是存储的位数,而是显示长度
3.CharField
字符类型,必须提供参数max_length,max_length表示字符长度
需要知道的是django里面的CharField对应的是MySQL里面的varchar类型,没有设置char类型的字段(可以自定义)
4.DateField
日期格式年-月-日,日期格式为YYYY-MM-DD,相当于python中的datetime.date()实例,参数有
auto_now=True 每次更新数据记录的时候会更新该字段
auto_now_add=True 创建数据记录的时候会把当前时间添加到数据库
5.DateTimeField
日期时间格式年月日时分秒,格式为YYYY-MM-DD HH:mm:ss,相当于python中datetime.datetime()实例,参数有
auto_now=True 每次更新数据记录的时候会更新该字段
auto_now_add=True 创建数据记录的时候会把当前时间添加到数据库
6.字段合集
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) - 二进制类型 字段合集
7.django ORM字段与MySQL的对应
对应关系: '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)', ORM字段与MySQL字段对应关系
8.字段参数
null
用来表示某个字段可以为空
unique
表示字段唯一,如果在一个字段中设置unique=True,则表示该字段在这个表中必须是唯一的
db_index
为字段设置索引,db_index=True
default
为这个字段设置默认值
auto_now=True 和auto_now_add=True详见日期字段👆👆👆
三、单表查询,双下划线查询
为了更好的了解djangoORM对数据库的操作,我们选择使用了Mysql数据库测试,因为自带的sqlite3数据库好多东西不支持,比如说日期格式会自动存储为时间戳,然后查看年份是不支持的,以及模糊匹配中没有对比,等等,所以我们选择了MySQL,可以更好的了解ORM的强大之处。测试准备代码详见前期准备,图片如下
做完这些,就可以直接在测试文件中运行test.py了,如果你想查看内部走的sql语句,可以将下面的代码直接拷贝到django项目中的settings.py文件中,在最后黏贴如下代码:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
如果你只想查看某一个sql语句,你可以通过queryset对象点query的方法在终端直接查看
在我们学习django ORM数据库操作之前,我们需要知道的是,只要是queryset对象,可以支持无限制的点方法,类似于jquery的链式操作
在models中创建类
from django.db import models # Create your models here. # 单表查询表 class User(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() register_time = models.DateField() def __str__(self): return '对象的名字:%s'%self.name # 多表查询表 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) publish_date = models.DateField(auto_now_add=True) # 外键关系 通常建在查询频率较高的一方 publish = models.ForeignKey(to='Publish') #多对多关系建立在哪里都可以 authors = models.ManyToManyField(to='Author') # 虚拟字段, 信号字段 def __str__(self): # 当外部要打印当前对象的时候调用 return '书籍对象的名字:%s'%self.title class Publish(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32) email = models.EmailField() # 对应就是varchar类型 def __str__(self): return '出版社对象的名字:%s'%self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() #一对一关系建在哪里都可以,推荐建在查询频率高的一方 authordetail = models.OneToOneField(to='AuthorDetail') def __str__(self): return '作者对象的名字:%s'%self.name class AuthorDetail(models.Model): phone = models.CharField(max_length=32) addr = models.CharField(max_length=32)
1 单表查询
1.1 新增数据
# #基于create创建 user_obj=models.User.objects.create(name='jason',age=18,register_time='2019-6-13') print(user_obj) #能打印的原因是create有一个返回值,并且这个返回值就是当前创建的对象本身 # #基于对象的绑定方法创建 user_obj=models.User(name='egon',age=18,register_time='2019-7-4') user_obj.save() print(user_obj) #需要注意的是save这个方法是将整个表都取出来全部重新写了一遍 # #那么注册时间支不支持传对象呢? from datetime import datetime ctime=datetime.now() print(ctime) #2019-06-13 19:20:15.685773 user_obj=models.User.objects.create(name='owen',age=20,register_time=ctime) print(user_obj) #这是user对象:owen 说明注册时间即支持字符串也支持时间对象
1.2 修改数据
#基于对象 user_obj=models.User.objects.filter(name='jason').first() #拿到的是数据对象本身 user_obj.age=23 user_obj.save() #基于queryset对象 user_obj=models.User.objects.filter(name='egon').update(age=20) print(user_obj) #1 return rows 返回的是行数?
1.3 删除数据
#基于queryset对象 user_obj=models.User.objects.filter(name='owen').delete() print(user_obj) #(1, {'app01.User': 1}) #基于对象本身 user_obj=models.User.objects.filter(name='jason').first() user_obj.delete() #不提示,但是是可以删除的
1.4 查询数据
user_list = models.User.objects.all() #查全部数据 print(user_list) #全部的queryset对象 user_list2=models.User.objects.filter(name='egon') print(user_list2) #<QuerySet [<User: 这是user对象:egon>]> 筛选过后的queryset对象 print(user_list2.first()) #点first拿到的是对象本身 user_list3=models.User.objects.get(name='jason') print(user_list3) #get拿到的直接是对象本身 user=models.User.objects.filter(name='ccc') print(user) #<QuerySet []> print(user.first()) #None user2=models.User.objects.get(name='ccc') print(user2) #直接报错 所以建议后面使用的时候用filter
其他查询必知必会13条(重要)
<1> all():查询所有结果,拿到的结果是queryset对象,支持索引取值,拿到的是数据对象本身
<2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 这里的filter可以传多个参数,参数之间是and的关系,拿到的是queryset对象,通过first可以拿到数据对象本身
<3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误,拿到的是数据对象
<4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 ,就是拿到除了筛选条件外的所有数据,里面是and的关系
<5> order_by(*field): 对查询结果排序('-id')/('price') ,默认是按照升序进行排序,如果是降序,那么筛选条件加-号
res = models.User.objects.order_by('age') # 默认是升序 # res = models.User.objects.order_by('-age') # 可以在排序的字段前面加一个减号就是降序
<6> reverse(): 对查询结果反向排序 >>>前面要先有排序才能反向,基于排序才可以反向排序(比如先order_by())
res = models.User.objects.order_by('age') print(res) res1 = models.User.objects.order_by('-age') print(res1) #按照条件进行排序,默认升序,加-号是降序 res2 = models.User.objects.order_by('age').reverse() print(res2)
<7> count(): 返回数据库中匹配查询(QuerySet)的对象数量。
res = models.User.objects.all().count() print(res) # 查询所有的queryset对象 res1 = models.User.objects.filter(age=20).count() print(res1) #查询年纪为20岁的queryset对象
<8> first(): 返回第一条记录
res=models.User.objects.all().first() print(res) # 所有的queryset对象取第一个 res1=models.User.objects.filter(age=18).first() print(res1) #年纪为18的queryset对象取第一个
<9> last(): 返回最后一条记录
res = models.User.objects.all().last() print(res) #所有的queryset对象取最后一个 res1=models.User.objects.filter(age=18).last() print(res1) #年纪为18的queryset对象取最后一个
<10> exists(): 如果QuerySet包含数据,就返回True,否则返回False
res = models.User.objects.filter(name='ccc').exists() print(res) #False 判断queryset是否为空 返回的是布尔值 res1 = models.User.objects.filter(name='jason').exists() print(res1) #True 判断queryset是否为空 返回的是布尔值
<11> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列
<12> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
<13> distinct(): 从返回结果中剔除重复纪录如果你查询跨越多个表,可能在计算QuerySet时得到重复的结果。此时可以使用distinct(),注意只有在PostgreSQL中支持按字段去重。
13个必会操作总结
返回queryset对象的方法有
all(),filter(),exclude(),order_by(),reverse(),distinct(),values(),values_list()
queryset对象支持索引取值,但是只能是正向,负数索引不支持,取值first,last都支持,拿到的是数据对象本身
数字
count()
拿到的是查询结果包含的queryset对象的数量
布尔值
exists()
用来判断查询到的queryset对象是否为空
数据对象本身
get(),first(),last()
2 双下划线查询
我们上面的都是基于给出的条件直接去数据库中找到的对应的数据,那么如何查询一个字段里面的所有数据呢,这就用到了双下划线查询,主要用于单个字段的数据查询,下面是一些题目练习,方便理解学习
#查询年龄大于19岁的用户 res = models.User.objects.filter(age__gt=19) print(res) # get than #查询年龄小于20岁的用户 res1=models.User.objects.filter(age__lt=20) # print(res1) #less than # 查询年龄大于等于19的用户 res = models.User.objects.filter(age__gte=19) print(res) #get than equal # #查询年龄小于等于20岁的用户 res2 = models.User.objects.filter(age__lte=20) print(res2) #less than equal #查询年龄是20或22或19的用户 res = models.User.objects.filter(age__in=[20,22,19]) print(res) # #查询年龄在19-30范围内的用户 res1 = models.User.objects.filter(age__range=[19,30]) print(res1) # # #查询年份是2019年的用户 res = models.User.objects.filter(register_time__year=2019) print(res) #查询名字是以j开头的用户 res = models.User.objects.filter(name__startswith='j') print(res) #查询名字是以n结尾的用户 res1 = models.User.objects.filter(name__endswith='n') print(res1) #查询名字中包含字母N的用户 res = models.User.objects.filter(name__contains='N') print(res) #是大写N才可以 res1 = models.User.objects.filter(name__icontains='N') print(res1) #忽略大小写
2.1 大于... greater than
字段__gt=筛选条件
2.2 小于... less than
字段__lt=筛选条件
2.3 大于等于... greater than equal
字段__gte=筛选条件
2.4小于等于... less than equal
字段__lte=筛选条件
2.5 在...范围内
字段__range=筛选条件
2.6 在...条件内
字段__in=筛选条件
2.7 查询年份
字段__year=筛选年份
2.8 以...开头
字段__startswith=筛选条件
2.9 以...结尾
字段__endswith=筛选条件
2.10 模糊匹配
字段__contains=筛选条件 不直接当作大小写,看重大小写
字段__icontains=筛选条件 直接当作条件忽略大小写
多表查询
一对多:Foreignkey
新增数据
#直接写id 有外键关系 book_obj = models.Book.objects.create(title='飘',price=66.66,publish_id=1,publish_time='2017-6-16') print(book_obj) #传数据对象 from datetime import datetime ctime=datetime.now() publish_obj=models.Publish.objects.filter(pk=2).first() book_obj = models.Book.objects.create(title='三国演义',price=199.99,publish_time=ctime,publish=publish_obj) print(book_obj)
修改数据
#queryset修改 先找到数据对象,然后再去修改外键 models.Book.objects.filter(pk=1).update(publish_id=1) publish_obj=models.Publish.objects.filter(pk=2).first() models.Book.objects.filter(pk=1).update(publish=publish_obj) #对象修改 就是找到对象,让对象去操作 book_obj = models.Book.objects.filter(pk=6).first() book_obj.publish_id=1 book_obj.save() book_obj = models.Book.objects.filter(pk=10).first() book_obj.publish=publish_obj #点的是ORM中的字段名 book_obj.save()
删除数据
#queryset对象删除 models.Book.objects.filter(pk=17).delete() book_obj=models.Book.objects.filter(pk=6).first() book_obj.delete()
以上的这几种方法都支持对象和queryset对象的操作,可以根据要求不同,选择合适的方法进行操作
多对多:ManyToMany
给多对多添加关系
add支持传多个数据和数据对象
#多对多关系之间的添加 add book_obj=models.Book.objects.filter(pk=15).first() book_obj.authors.add(3) book_obj.authors.add(1,2) author_obj=models.Author.objects.filter(pk=1).first() author_obj2=models.Author.objects.filter(pk=3).first() book_obj.authors.add(author_obj,author_obj2)
注意,add后面如果要直接传queryset对象是不支持的,必须是一个个的对象,所以我们想到了打散传值,因为queryset对象也是一个大字典呐,支持打散传值
book_obj=models.Book.objects.filter(pk=15).first() author_obj = models.Author.objects.all() book_obj.authors.add(*author_obj)
修改多对多的关系(重置)
set支持传多个数据和对象,但是注意的是里面的必须是一个可迭代对象,所以如果要传数据,记得用元组包起来
set里面放的是一个可迭代对象,那么你发现了什么👇👇👇
book_obj=models.Book.objects.filter(pk=10).first() # book_obj.authors.set((1,)) # book_obj.authors.set((1,3)) #基于对象 author_obj=models.Author.objects.all() book_obj.authors.set(author_obj)
queryset对象也是一个可迭代对象,现在我们所学习的可迭代对象又多了一个
删除多对多之间的关系
注意,remove里面可以传对象和数字,但是里面不可以接收queryset对象,必须是一个个的对象,那就打散传值喽!
book_obj = models.Book.objects.filter(pk=10).first() # book_obj.authors.remove(1,3) author_obj = models.Author.objects.all() book_obj.authors.remove(*author_obj)
清空多对多的绑定关系
clear,clear不用传参的,直接就可以将当前查询出来的对象里面的绑定关系清空
book_onj = models.Book.objects.filter(pk=1).first()
book_onj.authors.clear()
正反向查询
首先,理解什么是正反向查询,就是两张表之间有关系,我从当前这张表通过一种手段直接到了另外一张表的一种方式,这种手段就是看当前手里面的这张表有没有与另外一张表的对应关系,也就是字段,如果有,我就直接通过这个字段到达另外一张表,这就是正向查询,如果没有,我通过表名小写也可以到达,这就是反向。
理解了正反向以后,我们就来学习查询两张表的方法,就是基于对象和基于双下划线的查询。
基于对象查询
正向
反向
记住正反向查询的关键
正向查询-------当前表的字段
反向查询-------有关系表的表名小写加_set,如果还是没有出来,看是否对应了多个值,再加.all()
基于双下划线查询
需求是不让你手动写好多行,我需要你一行给我搞定,你如何做到一行查询到所有的结果---基于双下划线
正向
反向
多个筛选条件的反向查询
多个筛选条件的正向查询
聚合查询
关键字是aggregate
导入模块以及使用
from django.db.models import Max,Min,Sum,Avg,Count models.Publish.objects.aggregate(avg_price=Avg('book__price'))
正向
反向
分组查询
关键字是annotate
练习2
练习3
多对多表的三种创建方式
我们上面所用的都是django自带的帮我们创建的第三张表,但是一看数据库发现我们只有两个字段,就是这两张表相关的字段,后期如果我们想要扩展这些字段,就不能直接这样子去用了,这就用到了多对多表的其他创建方式
第一种 django帮我们创建第三张表
class Book(models.Model): name=models.CharField(max_length=32) author = models.ManyToManyField(to='Author') #这个字段就是自动帮我们创建的第三张表,在数据库中,这里看不到 class Author(models.Model): name=models.CharField(max_length=32)
这种没什么好讲的了,所有数据库查询都支持,但是缺点是只有当前的字段,无法新增字段
第二种 我们自己纯手动创建第三张表
class Book(models.Model): name=models.CharField(max_length=32) class Author(models.Model): name=models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') info = models.CharField(max_length=32) #额外添加其他字段
这种看似实现了第三张表的创建,以及字段的新增,但是有一个致命的缺陷,那就是无法使用django的ORM帮我们去查询数据
#查询数据对应的作者 res = models.Book.objects.filter(pk=1).values('author__name') print(res) 基于双下划线的反向查询已经不支持了 book_obj = models.Book.objects.filter(pk=1).first() res = book_obj.author_set print(res) 基于对象的反向查询也不支持了 好处:表的字段可以自定义了 坏处:不能支持表查询了
不管是用对象还是queryset对象都没办法找到数据,那不就是做了无用功了嘛!,哎,你别急,看我变变变👇👇👇
第三种 半自动创建第三张表
class Book(models.Model): name=models.CharField(max_length=32) #虚拟字段,所以不会再库中出现 authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author')) through='Book2Author' #告诉django我已经创建了关系,与哪张表建立了关系,django就不会帮你建表了 through_fields=('book','author') #与建立关系的那张表的哪些字段建立了关系,因为创建的第三张表字段会很多, #只有几个会有关系,所以要告诉django哪些字段建立了关系 class Author(models.Model): name=models.CharField(max_length=32)
#多对多字段写在哪里都可以,我们写在了Book表里面 #book = models.ManyToManyField(to='Book',through='Book2Author',through_fields=('author','book')) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') info = models.CharField(max_length=32)
再来看看django支持数据查询吗?
正向查询 res = models.Book.objects.filter(pk=1).values('authors__name') print(res) book_obj = models.Book.objects.filter(pk=1).first() res = book_obj.authors.all() print(res) 反向 res = models.Author.objects.filter(pk=1).values('book__name') res = models.Book.objects.filter(pk=1).values('book2author__info') print(res)
果然支持,至此,我们的多对多表的三种创建方式正式结束!
ps:使用第三种方法创建表关系以后,就不能使用add,set,remove,clear等方法管理多对多的关系了,要管理多对多的关系的话必须通过第三张表的model了