Django (学习第二部 ORM 模型层)
阅读原文时间:2023年07月10日阅读:7
  1. Django的 ORM 简介
  2. ORM操作 (增删改查)
  3. ORM操作数据库的增删改查
  4. ORM创建表关系
  5. ORM中常用字段及参数
  6. 数据库的查询优化
  7. ORM中如何开启事务

ORM --- 查询 (重点)

Django的 ORM 简介

ORM:  对象映射关系

ORM的作用:  利用Python 面向对象的代码简单快捷的操作数据库

ORM不足之处: 封装层度太高,有时候sql语句的效率偏低,需要自己写sql语句

映射关系:

对象

记录

对象属性

记录某个字段对应的值

1. 应用下面的 django 中 models.py 文件进行操作

class User(models.Model):
#id int primary_key auto_increment
id = models.AutoField(prinary_key =True)
# usename varchar(32) 必须指定max_length ,不指定会报错########
username = models.CharField(max_length=32)
# passworn int
password = nodels.IntrgerField()

2. 数据库迁移命令

在terminal 中输入

Python3 manage.py makemigration   (将记录写入到 migrations 文件夹下)

python3 mangae.py migrate   (将命令真正的同步到数据库中)

注意点:

只要修改了models.py中与数据库相关的代码,必须重新执行上述的俩条数据库迁移命令

由于一张中必须有一个主键字段 (一般情况为id字段),ORM在我们没有定义id字段时会自动创建id字段

ORM操作字段 (增删改查)

字段增加

  1 表中没有数据时,直接在class中添加对象

  2 表中有数据时

    1 可以在终端内给默认值

    2 该字段可以为空  (添加null=True)

    info = models.CharField(max_length=32,verbose_name='简历',null=True)

    3 直接给字段设定默认值  (添加default = 'study)

    hobby = models.CharField(max_length=32,verbose_name='兴趣', default='study')

字段的修改

  修改原class中的代码,执行数据库迁移命令即可

字段的删除

  直接注释掉原class中的代码,执行数据库迁移命令

字段的查看

  直接打开models文件进行查看即可

注意点:

  在操作models.py的时候一定要细心  千万不要注释一些字段     执行迁移命令时,仔细检查自己的代码

ORM操作数据库的增删改查

字段的操作

增:

  1. 可以在终端内直接给出默认值
  2. 该字段可以为空,在属性中添加( null= True  )
  3. 可以直接给字段设定默认值 ( default='默认值'')

改:

  直接对原代码经过修改.

删:

  直接将原代码进行注释即可.

对字段造作完后需要执行.那俩条数据迁移命令

python3 manage.py makemigrtions

python3 mangee.py migrate

数据的增删改查

对数据进行操作时先将 models引入到views 文件中( from app01 import models )

查:

方法一

filter()  括号内可以携带多个参数,参数与参数之间默认是and连接的关系,若不携带任何参数默认获取所有

返回值看作列表套数据对象的格式,支持索引取值(不支持负数索引),可以进行切片操作.(不推荐使用索引的方式取值)

*推荐使用 .first() 方法取值

res = models.orm表名.objects.filter(username=username)  #   等共同与mysql中的 select * form 表名 where username=值;

方法二

all()  

res = models.orm表名.objects.all()  

增:

create()  括号内可以携带多个值

返回值就是当前队形被创建的对象本身

方法一

res = models.orm表名.objects.create( username=username)

方法二

res = models.orm表名(username = username )

res.save()  #保存数据

方法一  (全部更新)

update()  括号内填写要修改的参数,可以携带多个值

res = models.orm表名.objcets.filter(id=N).update(修改的参数)

方法二  (局部更新 当表的字段比较多的时候,效率慢)

obj.name = newname

obj.password=newpassword

obj.save()

删:

delete()

res = models.orm表名.objcets.filter(主键).delete()

ORM创建表关系

一对一:

外键字段放在哪一方都可以(建议放在查询次数较多的一方)

res = models.OneToOneField(to="关联表名")

一对多:

外键字段放在多的哪一方

res = models.ForeignKey(to="关联表名")

多对多:

外键字段放在哪一方都可以 (推荐放在查询频率较高的一方)

authors = models.ManyToManyField(to="关联表名")

  authors是一个虚拟字段,主要告诉orm 这是一个多对多的关系,让orm自己创建第三张表

ORM中常用字段及参数

AutoField
主键字段 primary_key=True

CharField varchar
verbose_name 字段的注释
max_length 长度

IntegerField int
BinIntegerField bigint

DecimalField
max_digite=8
decimal_places=2

EmailField varchar(234)

DateField date
DateTimeFiled datetime
  auto_now: 每次修改数据的时候都会自动更新当前的时间
  auto_noe_add:只在创建数据的时候记录创建时间后续不会自动修改

Bool二胺Field(Field) 布尔值类型
该字段传布尔值(False/True) 数据库中存的是0和1

TextField(Field) 文本类型
该字段可以用来存大段的内容(文章 博客)没有字数的限制

FileField(Field) 字符类型
upload_to = '/data'
给字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中/data/a.txt

更多字段请查看:https://www.cnbiogs.com/Dominic-Ji//p/9203990.html

Django除了提供字段之外,还可以自定义字段

class MyCharField(models.Field):
  def __init__(self.max_length,*args,**kwargs):
    self.max_length = max_length
    #调用父类的init方法
    super().__init__(max_length=max_length,*args,**kwargs)  #一定要是关键字的形式传入

  def db_type(self,connection):
    #返回真正的数据类型及各种约束条件
    :param connection:
    :return:
    return 'char(%s)' %self.max_length

#自定义字段使用
myfield = MyCharField(max_length=16,null=Ture)

#外键字段及参数
unique = True
  ForeiqnKey(unique=True) === OneToOneField()
  #你在用前面的字段创建一对一 orm会有一个提示信息 orm推荐使用后者但是前者也能用

db_index
如果db_index=True 则代表着为此字段设置索引

to_Field
设置要关联的表的字段 默认不写关联的就是另外一张的主键字段

on_delete
当删除字段关联表中的数据时,当前表与其关联的行的行为
''' Django2.x及以上的版本 需要自己指定外键字段的级联更新级联删除'''

数据库的查询优化

onlly与defer

select_rrelated与prefetch_related

orm语句的特点:  惰性查询  如果只是写了orm 语句 后面没有用到该语句所查询出来的参数那么orm会自动识别 直接不执行

# only与defer

#res = models.Book.objects.all()
#print(res) # 要用数据了才走数据库

#想要获得书籍中的所有数的名字  
#res = models.Book.objects.values('title')  
#for d in res:  
#    print(d.get('title'))  
#获得的是一个数据对象  然后点title就能够拿到书名 并且没有其他字段  
#res = models.Book.objects.only('title')  
#res = models.Book.objects.all()  
#  
#for i in res:  
    #print(i.title) #点击only括号内的字段 不会走数据库  
    #print(i.price)#点击only括号内没有字段 会重新周数据库查询而all不需要走

res = models.Book.Objects.defer('title') #对象除了没有title属性之外其他的都有

for i in res:  
    print(i.price)

'''  
    deer 与only 刚好相反  
        defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据而如果查询的是非括号内的字段 则不需要走数据库了  
'''

select_related与 prefetch_related

select_related与 prefetch_related 跟跨表查询有关

#res = models.Book.objects.all()  
#for i in res:  
# print(i.publish.name)#每循环一次就要走一次数据库查询

# res = models .Book.objects.select\_related('authors') #INNER JOIN  

'''
select_related 内部直接先将book与pub历史连起来 然后一次性将大表里面的所有数据数据全部封装给查询出来的对象
这个时候对象无法是点击book表的数据还是publish的数据都无需再走数据库查询了

select\_related括号内只能放外键字段 一对多 一对一  
    多对多也不行  

'''

for i i你 res:

print(i.publish.name) #每循环一次就要走一次数据库查询

res = models.Bookobjects.prefatch\_related('publish') #子查询

'''
prafetch_related该方法内部其实是子查询
将子查询出来的所有结果也给封装到对象中
'''

for i in res:
print(i.publish,name)

ORM中如何开启事务

事务
ACID
原子性: 不可分割的最小的单位
一次性: 跟原子性是相辅相成的
隔离性: 事务之间互不干扰
持久性: 事务一旦确认就永久有效

事务的回滚:
collback
事务的确认
commit

#事务
from django.代表 import transaction
tyy:
with transaction,atomic():
#sql1
#sql2
#在with代码块中书写的所有orm操作都是属于同一个事务
except Except as e:
pritn(e)
print('执行其他操作')

ORM --- 查询  (重点)

测试脚本

import os

if __name__=="__main__':
os.environ.setdefault('DJANGO_SETTINGS-MODULE','day64.settings')
import djagno
django.setup()

from app01 import models  
model.User.objects.all()

单表查询(增删改查)

增:

#方法一:

models.表名.objects.create(字段='添加的值'…)
#方法二:

表名_obj = models.表名(字段='添加的值'…)
表名_obj.save()

删:

#方法一:

res = models.User.objects.filter(pk=n).delete()

方法二:

表名_obj=models.表名.onjects(pk=n).first()
表名_obj.delete()

注意:pk会自动寻找当前表的主键字段,指代的就是当前标的主键字段用pk之后 就不需要指代当前表的主键字段到底是什么(uid pid sid )

改:

方法一:

model.表名.objects.filter(pk=你).update(字段='要更新的值')

常见的查询方法单表(重点):

all() '''获取表中所有数据,结果为queryset类型'''
ret = models.Book.objects.all()

filter() '''获取部分数据,结果为queryset类型'''
ret = models.Book.objects.filter(title='西游记')
ret = models.Book.objects.filter(id=1,title='三国演义') # 多条件查询,条件之间是且的关系(and)

get() '''获取单条数据,结果是model对象'''
ret = models.Book.objects.get(title='西游记')
'''关于get需要注意的点'''

1. get() 获取有且只能有一条

2.找不到会报错:Book matching query does not exist.

3.结果超过1条也报错: get() returned more than one Book -- it returned 3!

exclude(**kwargs): '''排除的意思,它包含了与所给筛选条件不匹配的对象,返回值是queryset类型'''
models.Book.objects.exclude(id=6),# 返回id不等于6的所有的对象,
models.Book.objects.all().exclude(id=6) # 在queryset基础上也调用exclude

                
order_by(*field): '''queryset类型的数据来调用,对查询结果排序,默认是按照id来升序排列的,返回值还是queryset类型'''
models.Book.objects.all().order_by('price','id')
'''注意点'''

1.直接写price,默认是按照price升序排列,

2.按照字段降序排列,就写个负号就行了:order_by('-price'),

3.order_by('price','id')是多条件排序,按照price进行升序,price相同的数据,按照id进行升序

reverse() '''queryset类型的数据来调用,对查询结果反向排序,返回值还是queryset类型,在排序基础上才能使用'''
models.Book.objects.all().order_by('id').reverse() # 注意:要在order_by 基础上使用

count():'''queryset类型的数据来调用,返回数据库中匹配查询(QuerySet)的对象数量'''
models.Book.objects.all().count()

first(): '''queryset类型的数据来调用,返回第一条记录'''
Book.objects.all()[0] = Book.objects.all().first() # 得到的都是model对象,不是queryset

last(): '''queryset类型的数据来调用,返回最后一条记录'''

注意:在ORM中不能使用复数索引

ret = models.Book.objects.all()[-1] # 报错,不支持负数索引:Negative indexing is not supported.

exists(): '''queryset类型的数据来调用,如果QuerySet包含数据,就返回True,否则返回False'''

values(*field): '''用的比较多,queryset类型的数据来调用,返回一个QuerySet'''
ret = models.Book.objects.values('title','price')
# 获取所有记录的某些字段数据,结果为querset类型,里面的元素是字典类型数据,属性名称为键,对应记录的字段数据为值
'''还可以通过queryset类型数据来调用'''
ret = models.Book.objects.all().values('title','price')

values_list(*field): '''它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列'''
ret = models.Book.objects.all().values_list('title','price')
ret = models.Book.objects.values_list('title','price')

distinct(): '''values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复记录'''
ret = models.Book.objects.all().values_list('price').distinct()

神奇的双下滑线查询

# 1.按日期查询
ret = models.Book.objects.filter(pub_date__year='2000', pub_date__month='8',pub_date__day='12')

2.以..开头/结尾

ret = models.Book.objects.filter(title__startswith='少妇') # 以..开头(区分大小写)
ret = models.Book.objects.filter(title__istartswith='py') # 以..开头(不区分大小写)
ret = models.Book.objects.filter(title__endswith='2') # 以..结尾(区分大小写)

3.包含

ret = models.Book.objects.filter(title_icontains='python') # title值中包含python的(区分大小写)
ret = models.Book.objects.filter(title__icontains='python') # title值中包含python的(不区分大小写)

4.数字等于…/数字在某个范围内

ret = models.Book.objects.filter(price__in=[3,4]) # 等于3或者等于4 -- 3 or4
ret = models.Book.objects.filter(price__range=[1,3]) # 在1-3之间 between 1 and 3

5.年份写纯数字和字符串数字都可以

ret = models.Book.objects.filter(pub_date__year=2018)
ret = models.Book.objects.filter(pub_date__year='2018')

6.大于/大于等于/小于/小于等于

ret = models.Book.objects.filter(price__gt=3) # 价格大于3的
ret = models.Book.objects.filter(price__gte=3) # 价格大于等于3的
ret = models.Book.objects.filter(price__lt=3) # 价格小于3的
ret = models.Book.objects.filter(price__lte=3) # 价格小于等于3的

多表操作   (外键字段的增删改查)

增:

一对一

# 一对一
# 1.用create增
models.AuthorDetail.objects.create(
birthday='2018-01-01',
telephone='13800000000',
addr='北京'
)
ad_obj = models.AuthorDetail.objects.get(id=1) # 创建一对一的关联model对象
models.Author.objects.create(
name='张三',
age=38,
# ad=ad_obj, # 方法一:写关联对象
ad_id=2, # 方法二:直接写关联id值
)
# 2.用类对象去增
ad_obj = models.AuthorDetail.objects.get(id=4)
obj= models.Author(
name='杨浩',
age=47,
ad=ad_obj, # 方法一
# ad_id=3, # 方法二
)
obj.save()

一对多

'''出版社和书是一对多的关系;一个出版社可以出版多本书'''

一对多,在多的一方写关联语句

models.Book.objects.create(  
    title='西游记',  
    publishDate='2018-08-08',  
    price=22,  
    # publishs=models.Publish.objects.get(id=1), # 方法一  
    publishs\_id=1, # 方法二  
)

一对多

# 一对多
'''出版社和书是一对多的关系;一个出版社可以出版多本书'''

一对多,在多的一方写关联语句

models.Book.objects.create(  
    title='西游记',  
    publishDate='2018-08-08',  
    price=22,  
    # publishs=models.Publish.objects.get(id=1), # 方法一  
    publishs\_id=1, # 方法二  
)

多对多

# 多对多
'''书和作者是多对多的关系;一本书可以有多个作者,一个作者也可以写很多本书'''
obj = models.Book.objects.filter(id=1).first()
# 方法一:通过get(id=?)获取对象
a1 = models.Author.objects.get(id=1)
a2 = models.Author.objects.get(id=2)
obj.authors.add(a1,a2)
# 方法二:add中直接写id
obj.authors.add(1,2)
# 方法三:id写入列表中,用*号打散
obj.authors.add(*[1,2])

删:

# 一对一和一对多删除
'''一对一和一对多 ,基本和单表一样(级联删除)'''
models.Author.objects.get(id=1).delete()

models.AuthorDetail.objects.get(id=2).delete()  
models.Book.objects.get(id=1).delete()

多对多删除

ret = models.Book.objects.get(id=2)  
ret.authors.clear()  # 清空该书籍对应的第三张表中的记录(全部删除)  
ret.authors.remove(3,4)  #指定删除该书和哪些作者的关系 (指定删除)

改:

# 一对一修改和一对多修改
models.Author.objects.filter(name='张三').update(
name='李四',
age=50,
# ad=models.AuthorDetail.objects.get(id=1), 两种方式都可以
ad_id=1,
)

多对多修改

obj = models.Book.objects.get(id=2)
obj.authors.set(['3',])
'''多对多修改使用set方法 注意:set的值是字符串,set的原理是clear+add'''

跨表查询(重点)

#查看执行原生sql

方式1

from django.db import connection
print(connection.queries)

方式2

'''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',
},
}
}
'''方式1:配置之后,可以使用下面的代码执行sql语句'''
cursor = connection.cursor()
cursor.execute('select * from app01_book;')
print(cursor.fetchall())

'''方式2:也可以使用pymysql执行sql语句'''

'''方式3:可以使用objects下的raw方法执行sql语句'''
ret = models.Author.objects.raw('select * from app01_author;') # 注意:只限于本表操作
print(ret)
for i in ret:
print(i.name)

正向查询和反向查询

正向查询:A表关联了B表,关联属性在A表,那么通过A表数据查询B表中数据时,叫做正向查询
正向查询语法:正向查询使用对象.关联属性名称
反向查询:反之,叫做反向查询
反向查询语法:反向查询使用对象.关联模型类名小写

  子查询(基于对象的跨表查询)

一对一

'''一对一:正向查询'''

查看张三作者的家庭住址

1.sql语句查询

select app01_authordetail.addr from app01_author inner join app01_authordetail on app01_author.ad_id app01_atuhordetail.id where app01_author.name='张三'

2.基于对象的跨表查询

obj = models.Author.objects.get(name='张三')
print(obj.ad.addr)

'''一对一:反向查询'''

查询北京的作者名称

obj = models.AuthorDetail.objects.get(addr='北京')
print(obj.author.name) # 反向查询:对象.表名小写.属性

一对多

'''一对多:正向查询'''

查询西游记这本书的出版社

obj = models.Book.objects.get(title='西游记')
print(obj.publishs.name)

'''一对多:反向查询'''
'''一对多查询,给一查多,结果有多个:要使用对象.表名_set.all()'''

查询马哥出版社出版的书籍有哪些

ret = models.Publish.objects.get(name='马哥出版社')
books = ret.book_set.all() # , ]>
print(books.values('title')) # all()查询的是queryset对象,想得到里面的具体内容,要用values(字段)

多对多

'''一对一:正向查询'''

查看张三作者的家庭住址

1.sql语句查询

select app01_authordetail.addr from app01_author inner join app01_authordetail on app01_author.ad_id app01_atuhordetail.id where app01_author.name='张三'

2.基于对象的跨表查询

obj = models.Author.objects.get(name='张三')
print(obj.ad.addr)

'''一对一:反向查询'''

查询北京的作者名称

obj = models.AuthorDetail.objects.get(addr='北京')
print(obj.author.name) # 反向查询:对象.表名小写.属性

'''一对多:正向查询'''

查询西游记这本书的出版社

obj = models.Book.objects.get(title='西游记')
print(obj.publishs.name)

'''一对多:反向查询'''
'''一对多查询,给一查多,结果有多个:要使用对象.表名_set.all()'''

查询马哥出版社出版的书籍有哪些

ret = models.Publish.objects.get(name='马哥出版社')
books = ret.book_set.all() # , ]>
print(books.values('title')) # all()查询的是queryset对象,想得到里面的具体内容,要用values(字段)

'''多对多:正向查询'''

查询西游记是谁写的

1.sql查询

select app01_author.name from app01_book inner join app01_book_authors on app01_book.id =
app01_book_authors.book_id inner join app01_author on app01_author.id = app01_book_authors.author_id

2.正向查询

obj = models.Book.objects.get(title='西游记')
print(obj.authors.all().values('name'))

'''多对多:反向查询'''

查询张三写了哪些书

obj = models.Author.objects.get(name='张三')
print(obj.book_set.all().values('title'))

  联表查询(基于双下划线的跨表查询)

# 一对一

查看张三作者的家庭住址

'''正向写法'''
ret = models.Author.objects.filter(name='张三').values('ad__addr')
'''反向写法'''
ret = models.AuthorDetail.objects.filter(author__name='张三').values('addr')
print(ret) #

一对多

查询西游记这本书的出版社

'''正向写法'''
ret = models.Book.objects.filter(title='西游记').values('publishs__name')
print(ret) #
'''反向写法'''
ret = models.Publish.objects.filter(book__title='西游记').values('name')
print(ret) #

多对多

查询水浒传是谁写的

'''正向写法'''
ret = models.Book.objects.filter(title='水浒传').values('authors__name')
print(ret)
'''反向写法'''
ret = models.Author.objects.filter(book__title='水浒传').values('name')
print(ret) #

聚合查询

from django.db.models import Avg,Max,Min,Count,Sum

聚合查询

'''聚合查询使用aggregate()方法'''
ret = models.Book.objects.all().aggregate(Avg('price'))
ret = models.Book.objects.all().aggregate(a=Avg('price'),m=Max('price'))
print(ret,type(ret)) #{'price__avg': 15.0}
'''注意结果为字典类型.'''

分组查询

# 统计一下每个出版社出版书的平均价格

1.sql查询

select publishs_id,avg(price) from app01_book group by publishs_id;
select avg(app01_book.price) from app01_book inner join app01_publish on
app01_book.publishs_id = app01_publish.id group by app01_publish.name;

2.分组查询

ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price')) # 方式一

ret = models.Publish.objects.annotate(a=Avg('book__price')) # 方式二

, ]>

print(ret.values('a','name'))

F与Q查询

F查询

查询一下点赞数大于评论数的书籍

'''1.传统方法'''
ret = models.Book.objects.all()
book_list = []
for i in ret:
if i.dianzan > i.comment:
book_list.append(i)

'''2.F查询'''
from django.db.models import F
''''针对本表不同字段数据进行对比时或者本表字典做一些统一修改时使用F查询'''

点赞数大于评论数的

ret = models.Book.objects.filter(dianzan__gt=F('comment'))

'''F查询也支持统一修改'''

所有书籍上调10块

models.Book.objects.all().update(price=F('price')+10) # 支持四则运算

Q查询

from django.db.models import Q
'''
| -- or
& -- and
~ -- not
'''

'''and与和or或'''
ret = models.Book.objects.filter(Q(comment__gt=30)|Q(dianzan__gt=50))

ret = models.Book.objects.filter(Q(comment__gt=30)&Q(dianzan__gt=50))

等同于# ret = models.Book.objects.filter(comment__gt=30,dianzan__gt=50)

ret = models.Book.objects.filter(Q(comment__gt=30)|Q(dianzan__gt=50),publishDate__year='2018')

注意没有Q包裹的条件,写在Q包裹的条件后面.并且Q查询和publishyear之间是and的关系

'''Q查询多层嵌套'''
ret = models.Book.objects.filter(Q(Q(comment__gt=30)|Q(dianzan__gt=50))&Q(xx=11),publishDate__year='2018')

'''条件取反:波浪线写在Q前面'''

取评论数小于等于30 的,或者点赞数大于50的

ret = models.Book.objects.filter(~Q(comment__gt=30)|Q(dianzan__gt=50))

返回顶部