django模型层
目录
django ORM如何创建表关系
表与表之间的关系
- 判断表关系的方法 --- 换位思考
- 表与表之间的关系
- 一对多 图书和出版社,外键建在查询次数多的一方
- 一对一 作者与作者详情 外键建在查询次数多的一方
- 多对多 图书与作者 需要创建第三张表专门存储关系
ORM创建表关系
创建表关系的步骤
- 先将基表创建出来,然后再添加外键字段
- 添加外键
创建表关系 --- 以图书管理系统为例
- app01-models.py
from django.db import models
# Create your models here.
'''
创建表关系
图书与出版社:一对多的关系
图书与作者:多对多的关系
作者与作者详情:一对一的关系
'''
# 创建图书表
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 总共8位有效数字,小数点占两位
'''
创建图书与出版社的关系 --- 一对多,外键建在查询次数多的哪一方
ForeignKey默认就是与出版社表的主键字段做外键关联
如果字段对应的是ForeignKey,那么orm会自动在字段的后面加上_id,如果在外键字段手动加了_id,那么orm还是会在后面继续加_id
'''
publish = models.ForeignKey(to='Publish')
'''
创建图书与作者的关系 ---- 多对多,外键建在查询频率较高的一方
author是一个虚拟字段,主要是用来告诉orm书籍表与作者表是多对多的关系,让orm自动创建第三张关系表
'''
author = models.ManyToManyField(to='Author')
# 创建出版社表
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
# 创建作者表
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField
'''
作者表与作者详情表是一对一的关系,外键建在查询频率较高的一方
OneToOneField也会自动给字段加上_id后缀
'''
author_details = models.OneToOneField(to='AuthorDetails')
# 创建作者详情表
class AuthorDetails(models.Model):
phone = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
- orm创建表关系总结
- orm中如何定义三种关系
- 一对多 外键字段 = models.FroeignKey(to='关联的表所对应的类名')
- 多对多 虚拟字段 = models.ManyToManyField(to='关联的表所对应的类名')
- 一对一 外键字段 = models.OneToOneField(to='关联的表所对应的类名')
- 外键字段是否会自动添加_id后缀
一对一 一对多都会在外键字段自动加_id后缀
多对多创建的第三张表也会自动加_id字段,第三张表就是两张不同表的主键字段的对应关系
- 其他知识
在django1.X版本中外键默认都是级联更新删除的
多对多的表关系可以有好几种创建方式 这里暂且先介绍一种
ORM操作表数据---单表操作
单表操作
知识补充
django自带的sqlite3数据库对日期格式不是很敏感,处理时容易出错
- 日期字段的参数介绍
# 创建单表
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
# 年月日
# register_time = models.DateField()
# 年月日时分秒
register_time = models.DateTimeField()
'''
auto_now:对表进行操作时就会将register_time的时间更新为当前时间
auto_now_add:创建数据的时间,后面不会再变
'''
测试环境准备
- 简介
验证orm的操作,即对数据的操作可以直接再测试环境中进行,无需使用前端页面与后端函数的对应关系来验证ORM对数据库的操作
- 测试环境的使用
app文件夹中的test.py是django提供的测试文件,也可以自己再app文件夹下创建测试的py文件。
测试环境搭建:
在test.py文件中写以下代码(直接复制manage.py中的前4行,自己写两行即可),
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modles_layer.settings")
import django
django.setup()
测试环境使用:
使用测试环境进行测试的代码必须要在测试环境搭建完成后才能书写,案例如下
from django.test import TestCase
# Create your tests here.
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modles_layer.settings")
import django
django.setup()
# 不要把这句代码写道文件开头,否则会报错,因为只有当测试环境搭建完成才能惊醒代码的测试
from app01 import models
print(models.User.objects.all())
单表操作(一定要将所有代码放在测试环境搭建完成之后)
增加数据
'''
日期字段可以自己指定,但是一定要符合格式,用 - 链接,也支持日期对象
'''
user_dic = {'name':'鬼鬼','age':18,'register_time':'2020-01-01'}
# 增加数据方式一
res = models.User.objects.create(**user_dic)
print(res.name) # 该方法有返回值,就是创建的数据对象本身
# 增加数据方式二
user_dic1 = {'name':'鬼鬼1','age':18,'register_time':datetime.datetime.now()}
user_obj = models.User(**user_dic1)
user_obj.save()
删除数据
'''
pk会自动查找当前表的主键字段指代的就是当前表的主键字段,用来pk之后不需要指代当前表的主键到底叫什么(id pid uid等等)
'''
# 方式一:批量删除
res = models.User.objects.filter(pk=6).delete()
print(res)
# 方式二:拿到单一对象,删除单一数据
user_obj = models.User.objects.filter(pk=8).first()
user_obj.delete()
修改数据
# 修改数据
# 方式一:批量修改
models.User.objects.filter(pk=8).update(name='guigui')
# 方式二
user_obj = models.User.objects.get(pk=1)
'''
get方法返回的直接就是想要修改的数据对象,但是该方法不推荐使用,因为一旦数据不存在该方法就会直接报错,但是filter不会报错
'''
user_obj.name = 'hah'
查看orm内部查看对应的sql语句的方式
- 方式一:
queryset对象点query可以拿到返回值是queryset对象的orm语句所对应的sql语句
res = models.User.objects.values_list('name','age')
print(res) # <QuerySet [('鬼鬼', 18), ('鬼鬼', 18), ('鬼鬼', 18), ('鬼鬼1', 18)]>
print(res.query) # 可以拿到对应的sql语句,该方法只能用于queryset对象才能.query
- 方式二:
所有的sql语句都能查看,需要去配置文件中配置,打印返回值的时候会直接显示对应的sql语句
# 将下述代码配置到配置文件中即可
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
单表操作必须掌握的13条操作
- all() --- 查询所有数据
- filter() ---- 带有过滤条件的查询
- get() ----直接拿到数据对象,按时条件不存在直接报错
- first() ---- queryset中的第一个对象
- last() ----queryset中的最后一个对象
- values()
可以指定获取数据的字段,类似于select name,age from ....
返回结果是queryset对象,列表套字典
res = models.User.objects.values('name','age')
print(res)
// <QuerySet [{'name': '鬼鬼', 'age': 18}, {'name': '鬼鬼', 'age': 18}, {'name': '鬼鬼', 'age': 18}, {'name': '鬼鬼1', 'age': 18}]>
- valuse_list()
可以获取指定字段的数据
返回结果是queryset对象,列表套元组
res = models.User.objects.values_list('name','age')
print(res)
# <QuerySet [('鬼鬼', 18), ('鬼鬼', 18), ('鬼鬼', 18), ('鬼鬼1', 18)]>
- distinct()
# distinct去重,去重一定要是一摸一样的数据,如果带有主键去重,肯定无法进行去重
res = models.User.objects.values('name','age').distinct()
print(res)
- order_by() 排序
res = models.User.objects.order_by('age')
# 默认是升序,降序就是在字段前加负号res = models.User.objects.order_by('-age')
print(res)
- reverse() 反转,反转的前提是数据已经排过序了,必须跟在order_by之后
res = models.User.objects.order_by('name').reverse()
print(res)
- count() 统计当前数据的个数
res = models.User.objects.count()
print(res)
- exclude() 将某条数据排除在外
res = models.User.objects.exclude(name='鬼鬼')
print(res)
- exists() 判断某个数据是否存在,基本用不到,因为数据本身就自带布尔值,返回的是布尔值
res = models.User.objects.filter(pk=2).exists()
print(res)
神奇的双下划线查数据方法(案例说明)
- 查询年龄大于30岁的数据
res = models.User.objects.filter(age__gt=30)
print(res)
- 查询年龄小于30岁的数据
res = models.User.objects.filter(age__lt=30)
print(res)
- 查询年龄大于等于32岁的数据
res = models.User.objects.filter(age__gte=32)
print(res)
- 查询年龄小于等于32岁的数据
res = models.User.objects.filter(age__lte=32)
print(res)
- 查询年龄是18 21岁的数据
res = models.User.objects.filter(age__in=[18,21])
print(res)
- 年龄在18-35岁之间的数据,顾头顾尾
res = models.User.objects.filter(age__range=[18,35])
print(res)
- 名字中含有n的数据,模糊查询
res = models.User.objects.filter(name__contains='n')
print(res)
- 是否区分大小写,默认是区分大小写的
res = models.User.objects.filter(name__contains='n')
print(res)
- 查数据时如何忽略大小写
res = models.User.objects.filter(name__icontains='n')
print(res)
- 判断名字以什么开头以什么结尾
res = models.User.objects.filter(name__startswith='t')
res1 = models.User.objects.filter(name__endswith='n')
- 查询出注册时间是2020-01注册的数据
res = models.User.objects.filter(register_time__month='1')
print(res)
// month还可以换成year day 等,多个条件逗号链接表示and关系
ORM操作表数据 ---- 多表操作
表准备
from django.db import models
# Create your models here.
'''
创建表关系
图书与出版社:一对多的关系
图书与作者:多对多的关系
作者与作者详情:一对一的关系
'''
# 创建图书表
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 总共8位有效数字,小数点占两位
# publish_date = models.DateField(auto_now_add=True)
'''
创建图书与出版社的关系 --- 一对多,外键建在查询次数多的哪一方
ForeignKey默认就是与出版社表的主键字段做外键关联
如果字段对应的是ForeignKey,那么orm会自动在字段的后面加上_id,如果在外键字段手动加了_id,那么orm还是会在后面继续加_id
'''
publish = models.ForeignKey(to='Publish')
'''
创建图书与作者的关系 ---- 多对多,外键建在查询频率较高的一方
author是一个虚拟字段,主要是用来告诉orm书籍表与作者表是多对多的关系,让orm自动创建第三张关系表
'''
author = models.ManyToManyField(to='Author')
# 创建出版社表
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
# email = models.EmailField() # varchar(254),后面校验型组件要用到
# 创建作者表
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField
'''
作者表与作者详情表是一对一的关系,外键建在查询频率较高的一方
OneToOneField也会自动给字段加上_id后缀
'''
author_details = models.OneToOneField(to='AuthorDetails')
# 创建作者详情表
class AuthorDetails(models.Model):
phone = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
一对多(一对一)关系 --- 外键增删查
增加数据
- 方式一:外键字段直接写对应关联的表数据中的id
models.Book.objects.create(name='西游记',price=400,publish_id=1)
models.Book.objects.create(name='明朝那些事',price=100,publish_id=2)
- 方式二:外键字段使用 虚拟字段=对象 的方式,传对象的时候对应的外键字段会自动找到关联的表及该对象所对应的id值
# 先获取外键关联的表中的数据(对象)
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(name='红楼梦',price=200,publish=publish_obj)
删除数据(默认级联删除)
models.Publish.objects.filter(pk=1).delete()
# 删除出版社,那么和该出版社关联的书籍也会被删除
修改数据
- 方式一:直接写外键字段=id
models.Book.objects.filter(pk=1).update(publish_id=2)
- 方式二:虚拟字段=对象
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)
多对多关系 ---- 外键增删改查
如何获取第三张关系表
因为第三张关系表不是自己手动创建的。而是ORM自动帮你创建的,所以无法通过models.表名的方式拿到第三张表关系。拿到第三张表关系的方式请看下面的案例
如何给书籍表添加作者
# 首先拿到需要添加作者的书籍对象
book_obj = models.Book.objects.filter(pk=1).first()
# 由于在定义书籍的表时,在类中定义了一个属性author,通过对象点属性的方式就可以拿到第三张关系表
print(book_obj.author)
# 为书籍添加作者
- 方式一:直接写作者的id
book_obj.author.add(2,3) # 书籍id为1的书籍绑定了id为2 3的作者,可以只添加一个作者括号内只写一个数字
- 方式二:写作者对象,可以自动解析出作者的id,然后关联给书籍
# 先获取作者对象,在将作者对象当作add的参数
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.author.add(author_obj,author_obj1,author_obj2)
删除数据 --- 删除某一个书籍和作者的对应关系
# 首先拿到书籍对象
book_obj = models.Book.objects.filter(pk=1).first()
# 删除书籍与作者的关系
- 方式一:直接写作者的id进行关系的删除
book_obj.author.remove(2) # 将当前书籍对象与id为2的作者接触关联,括号内可以写多个参数
- 方式二:写作者对象
author_obj = models.Author.objects.filter(pk=3).first()
book_obj.author.remove(author_obj)
修改数据 --- 修改书籍与作者的对应关系
# 首先拿到书籍对象
book_obj = models.Book.objects.filter(pk=1).first()
# 修改书籍与作者的对应关系
- 方式一:直接写作者的id进行关系的修改
book_obj.authors.set([1,2]) # 修改书籍与作者的对应关系,括号内必须是一个可迭代对象
- 方式二:写作者对象
author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1]) # 括号内必须给一个可迭代对
清空某一本书与作者的所有绑定关系
book_obj.authors.clear()
# 括号内没有任何参数
多表查询
储备知识 ---- 正反向
正反向的概念 --- 一对一/一对多/多对多通用
正向:外键字段在哪个表中,查关联的表就是正向
反向:被关联的表查外键字段所在的表就是反向
eg:通过书查出版社,就是正向---外键字段在书籍表中
通过出版社查书,就是反向---外键字段在书记表中
正向查询按字段(定义表的时候外键的名字)
反向查询按表名小写(-set)
多表查询
子查询(基于对象的跨表查询)
正向查询
- 注意事项
在书写ORM语句的时候和写sql语句是一样的,不要企图一次性将ORM语句写完,如果比较复杂,就写一点看一点
正向什么时候需要加.all后缀呢?当查询的结果可能有多个的时候就需要加.all,如果是一个就直接拿到数据对象,(当你拿到的结果类似于这种app01.Author.None,说明ORM语句没有错误,只是查到的结果有多个,需要在ORM语句后面加上.all)
- 案例
1.查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
# 书查出版社--正向
res = book_obj.publish
print(res) # 得到的结果是出版社对象 Publish object
print(res.name)
print(res.addr)
2.查询书籍主键为1的作者
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.author
print(res) # <QuerySet [<Author: Author object>, <Author: Author object>]>
3.查询作者jason的电话号码
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.author_details
print(res)
print(res.phone)
反向查询
- 注意事项
当查询结果可以有多个的时候及必须加_set
当查询结果只有一个的时候就不需要加_set
当查询的结果可能有多个的时候就需要加.all
- 案例
1.查询出版社是东方出版社的书
publish_obj = models.Publish.objects.filter(name='东方').first()
res = publish_obj.book_set.all()
print(res)
2.查询作者是jason写的书
author_obj = models.Author.objects.filter(name'jason).first()
res = author_obj.book_set.all()
print(res)
3.查询手机号是110的作者的姓名
author_detail_obj = models.AuthorDetails.objects.filter(phone='110').first()
res = author_detail_obj.author
print(res)
print(res.name)
联表查询(基于双下划线的跨表查询)--- 一行代码搞定数据查询
正向查询
- 案例
1.查询jason的手机号和姓名
res = models.Author.object.filter(name='jason').values('author_details__phone','name')
# 通过外键字段直接mo跳到author_details表中,然后用`__`查找需要的字段值
2.查询书籍主键为1的出版社名称和书的名字
res = models.Book.object.filter(pk=1).values('name','publish__name')
# 首先拿到书籍对象,而书籍就是后面查询的基表,所以拿到书的名字直接写value('name')即可,而出版社的名字是通过外键字段跳到出版社的表中,然后通过`__`找到需要的字段值
3.查询书籍主键为1的作者姓名
res = models.Book.object.filter(pk=1).values('author__name')
反向查询
- 案例
1.查询jason的手机号和姓名,不能使用正向查询的方式,即不能使用models.Author...的方式
res = models.AuthorDetails.object.filter(author__name='jason').values('phone','author__name')
# 首先获取作者名是jason的作者的详细信息models.AuthorDetails.object.filter(author__name='jason')
# 然后,在详情表中获取电话
# 最后通过values跳转到作者表,通过__找到需要的字段值作者名
2.查询书籍主键为1的出版社名称和书的名字
res = models.Publish.object.filter(book__id=1).values('name','book_name')
3.查询书籍主键为1的作者姓名
res = models.Book.object.fliter(pk=1).values('name','author__name')
4.综合大案例 --- 查询书籍主键是1的作者的手机号
涉及到三张表:书籍表 作者表 作者详情表
res = models.Book.object.filter(pk=1).values('author__author_details__phone')
# 首先获取书籍对象,书籍关联了作者表,作者表又关联了作者详情表,
# 首先通过values和书籍表中作者的外键字段跳到作者表,然后再根据外键author_details调转到作者详情表通过__查询到所需的数据
聚合查询
分组查询
F与Q查询
django中开启事务
ORM中常用字段及参数
字段的choices参数
choice参数的使用场景
创建一个用户信息表的时候,如何表示用户的性别呢?可能会回答说使用布尔值,因为只有两状态,表示两种性别。
那么到底是0代表女还是1代表女呢?
如果用户信息表有一个学历字段,从小学大博士,又怎么表示呢?
这个时候就可使用字段的choice属性。
choice属性就是针对可以列举完全的可能性字段,比如用户的学历信息,可以列举小学,初中,高中....
choice基本使用
- 创建表
class User(models.Model):
name = models.CharField(max_length=32)
gender_choice = (
(1,'male'),
(2,'female'),
(3,'others'),
)
'''
gender字段存放的还是数字
如果存的数字在上面元组列举的范围之内就可以非常容易的获取到数字对应的真正内容
'''
gender = models.IntegerField(choices=gender_choice)
score_choice = (
('A','优秀'),
('B','良好'),
('C','及格'),
('D','差评'),
)python
'''
score字段存放的还是字符
如果存的字符在上面元组列举的范围之内就可以非常容易的获取到数字对应的真正内容
'''
score = models.CharField(choices=score_choice,null=True,max_length=32)
- 测试文件中测试
from app01 import models
# 增加数据
models.User.objects.create(name='阿白',gender=2,score='A')
models.User.objects.create(name='阿白',gender=1,score='B')
models.User.objects.create(name='阿白',gender=3,score='C')
# 存的时候存一组数据:该数据的gender与score没有在choice列举的范围内,也是可以存的
models.User.objects.create(name='阿白',gender=0,score='E')
# # 查询数据
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender) # 2
# 只要是有choices参数的字段,如果想要获取对应的信息,固定写法get_字段名_display()
print(user_obj.get_gender_display()) # female
# 如果在choice中没有对应关系,那么字段是什么还是展示什么
user_obj = models.User.objects.filter(pk=4).first()
print(user_obj.get_gender_display()) # 0
MTV & MVC模型
MTV:django号称是MTV模型
M:models
T:templates
V:views
MVC:django本质也是MVC
M:models
V:views
C:controller
# vue框架:MVVM模型
表关系 --- 多对多关系的三种创建方式
全自动
利用ORM自动帮忙创建第三张关系表
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
- 优点
代码无需自己写,非常方便,ORM同时提供操作第三张关系表的方法
- 缺点
第三张关系表的扩展性极差,无法额外增加字段
纯手动
自己通过ForeignKey手动创建第三张关系表
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_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
- 优点
第三张表完全取决于手动的扩展
- 缺点
需要写的代码比较多,而且不能使用ORM提供的简单的方法
半自动 --- 推荐使用
可以使用ORM的正反向查询,但是无法使用多对多关系表对应的add set remove clear方法
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
# 告诉ORM不需要自动帮忙创建第三张关系表
through='Book2Author',
# 告诉ORM第三张关系表对应的表的外键字段
through_fields=('book','author')
)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
- 测试环境中进行测试
models.Book2Author.objects.create(book_id=1,author_id=2)
models.Book2Author.objects.create(book_id=1,author_id=3)
models.Book2Author.objects.create(book_id=2,author_id=1)
models.Book2Author.objects.create(book_id=2,author_id=3)
models.Book2Author.objects.create(book_id=3,author_id=1)
models.Book2Author.objects.create(book_id=4,author_id=3)
models.Book2Author.objects.create(book_id=5,author_id=2)
# 查询书籍id为1的书籍名称及作者
res = models.Book.objects.filter(pk=1).values('name','author__name')
print(res)
# 查询id是1的作者写的书
res = models.Author.objects.filter(pk=1).values('book__name')
print(res)
- through_fields字段先后顺序
判断的本质:通过第三张表查询对应的表,需要用到哪个字段九八哪个字段放在前面
简化判断:多对多关系在哪个表中创建,就把对应的关联字段放在前面