Django学习笔记(三)ORM相关操作
1. ORM
1.1 概念
对象关系映射,Object Relational Mapping
1.2 映射关系
ORM | 数据库 |
---|---|
类 | 数据表 |
对象 | 数据行 |
属性 | 字段 |
在代码层面来看,每一个类都将被映射到数据库中对应的一张表中。每一个实例都将对应一行数据,而对应的属性就是数据表中的字段。
1.3 准备工作
必须先在settings.py文件中配置好数据库。
本次使用mysql数据库,如果无法使用MySQLdb模块,可以安装pymysql,在__init__.py文件中:
import pymysql
pymysql.install_as_MySQLdb()
1.4 使用流程
- 创建好对应的model
- 使用脚本自动生成对应的数据表
- 通过特定语法增删改查
2. model的创建
如同类的声明,在app下的models.py中声明
class Publisher(models.Model):
name = models.CharField(max_length=32) #会对应生成varchar(32)字段
3. ORM常用字段类型
3.1 AutoField
int型自增字段,必须加上primary_key=True 即设置主键
id = models.AutoField(primary_key=True)
3.2 BigAutoField
bigint型自增字段,必须加上primary_key=True属性
3.3 IntegerField
从 -2^31 (-2,147,483,648) 到 2^31 - 1 (2,147,483,647) 的整型
3.4 CharField
字符类型,必须填入max_length参数,max_length即长度
name = models.CharField(max_length=32)
3.5 DateField
日期
3.6 DateTimeField
日期与时间
日期字段特有属性:
- auto_now_add 新增数据时,自动保存当前时间
- auto_now 每次新增和修改时,自动保存当前时间
- default 默认值,不填的时候填充默认值
上述三个属性互斥
3.7 BooleanField
布尔值
3.8 NullBooleanField
可空布尔值
3.9 TextField
长文本
3.10 FloatField
浮点型
3.11 DecimalField
参数:
- max_digits: 总长度
- decimal_places:小数位长度
rate = models.DecimalField(max_digits=10, decimal_places=4)
4. 外键关系
class Book(models.Model):
name = models.CharField(max_length=32)
publisher_id = models.ForeignKey('Publisher',on_delete=models.CASCADE)
# 会自动给外键加'_id'后缀
# on_delete(2.0版本改为必填)的可选项:
# models.CASCADE 级联删除,主表删除后子表对应数据也删除
# models.PROTECT 保护模式,如果主表对应子表中还有数据,则不允许删除
# models.SET(v) 主表删除时,外键设置为某值
# models.SET_DEFAULT(v) 主表删除时,外键设置为默认值,需要在创建时加入default属性
# models.SET_NULL() 主表删除时,外键设置为NULL
# modesl.DO_NOTHING() 什么都不做
通过ForeignKey直接引入对应的类名,即可绑定数据表之间的外键关系
5. 自动生成数据表
将目录切换到项目目录下,通过终端执行如下语句:
python manage.py makemigrations # 生成变更文件
python manage.py migrate # 将变更同步至数据库中
执行后将会根据model中声明的结构,自动创建对应的数据表。(上述命令只会查找在settings中注册的应用)
6. 实际操作
models.py
class Publisher(models.Model):
name = models.CharField(max_length=32) #varchar(32)
class Book(models.Model):
name = models.CharField(max_length=32) #varchar(32)
pub = models.ForeignKey('Publisher', on_delete=models.CASCADE)
查:
from app01 import models
# 获取所有的数据 QuerySet 对象列表
models.Publisher.objects.all()
# 获取存在且唯一(不唯一则报错)的数据 对象
models.Publisher.objects.get()
# 获取多条数据 对象列表 不会报错
models.Publisher.objects.filter()
ret = models.Book.objects.all() #对象列表
for book in ret:
print(book)
print(book.id/book.pk)
print(book.name)
print(book.pub) #书籍相关的出版社的对象
print(book.pub_id) #书籍相关的出版社的ID
新增:
from app01 import models
models.Publisher.objects.create(name='xxx')
models.Book.objects.create(name='xxx',pub=出版社的对象)
models.Book.objects.create(name='xxx',pub_id=出版社的对象的ID)
删除
models.Publisher.objects.get(pk=1).delete()
models.Publisher.objects.filter(pk=1).delete() # 批量删除
编辑
方式一:
book_obj.name = 'xxx'
book_obj.pub = 出版社对象
book_obj.pub_id = 出版社id
book_obj.save() # 保存到数据库
方式二:
models.Book.objects.filter(pk=1).update(name='xx', pub_id=出版社的id) # 相当于批量更新,且仅更新修改的字段,上面的方法会整行更新
使用第一种,相当于更新book表的全部字段
使用方式二,只会更新赋值的字段,且不用save,并且可以批量更新。
Objects.get()无法调用update方法
7. 自定义字段的方式
类型定义:
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length,*args, **kwargs)
def db_type(self, connection):
# 限定数据库内生成的字段类型是char 长度为设置的max_length
return 'char(%s)' % self.max_length
声明:
class Person(models.Model):
phone = MyCharField(max_length=11)
8. models字段常用的参数
# 该字段在数据库中可以为空
null=True
# Django admin 提交时可空 因此上述两条应该同时使用
blank=True
# 数据库中字段列名指定(与python中使用时的名称不一定一致,仅设置数据库列名)
db_column='xx'
# 默认值
default = xx
# 索引
db_index=True
# 唯一约束
unique=True
# admin管理平台显示的字段名称(可为中文)
verbose_name='xx'
# 给用户显示可选的参数
choices
# models.BooleanField(choices=((True,'男'),(False,'女')))
# 加上choices后变为下拉列表,加之前是单选框
9. models表的常用参数
# 在对应Model中添加,注意缩进!!!
class Meta:
# 数据库中生成的表名 默认:app名称_类名
db_table = "person"
# admin管理平台展示的表名
verbose_name = '个人信息'
# 原先界面显示的verbose_name 加 s
verbose_name_plural = '所有用户信息'
# 联合索引
index_together= [
# 两个存在的字段
("name","age")
]
# 联合唯一索引 加入以后对应字段组合唯一
unique_together = (("name","age"),)
10. 查询实践
# -*- coding = utf-8 -*-
# @Time :2021/1/3 下午8:08
# @Author :Asher
# @File :1.必知必会13条.py
# @SoftWare :PyCharm
import os
import django
# 如此声明可以直接在一个py文件中进行orm相关调试
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
django.setup()
from app01 import models
# all 查询所有数据 QuerySet 对象列表 【对象,一行数据】
ret = models.Person.objects.all()
# get 获取一个有且唯一的数据 没有或者返回多个的时候会报错
ret = models.Person.objects.get(pk=2)
# print 的时候调用的是 __str__ 方法,但是如果是输出列表,展示了列表中的元素,调用的是__repr__ 方法
# 可以自己重写__repr__ 也可以直接写 __repr__=__str__ 不能带空格 这样可直接输出__str__内容
# filter 获取满足条件的数据 QuerySet 对象列表 【对象,一行数据】 返回满足条件的0到任意条数据
ret = models.Person.objects.get(pk=2)
# order_by('字段名') 排序(默认升序)
# order_by('-字段名') 降序排序
# order_by('字段名1','字段名2') 支持多字段排序
ret = models.Person.objects.filter(age=2).order_by('age')
# reverse 对已经排序的对象列表做反转,如果没有做过排序,则无效
ret = models.Person.objects.filter(age=2).order_by('age').reverse()
# values() QuerySet [{}] 对象列表中每一个对象都是字典
# 不指定字段,不传参获取数据所有的字段名和值
# 如果传参,传入对应的字段名,则字典内容只会查出对应的字段
ret = models.Person.objects.all().values('pid')
# values_list() QuerySet [()] 对象列表中每个对象都是元组 没有字段名
# 不指定字段,不传参获取数据所有的值
# 如果传参,传入对应的字段名,则字典内容只会查出对应的字段
ret = models.Person.objects.all().values_list()
# distinct() 去重 可以配合values()获取指定字段进行去重
ret = models.Person.objects.all().distinct()
# count() 计数 len() 同样可以计数,但count方法效率更高
ret = models.Person.objects.all().count()
ret = len(models.Person.objects.all())
# first() 获取第一个元素
ret = models.Person.objects.all().first()
# last() 获取第一个元素
ret = models.Person.objects.all().last()
# exists() 判断是否有结果
ret = models.Person.objects.filter(pk=2).exists()
# exclude() 获取不满足条件的数据
ret = models.Person.objects.exclude(pk=2)
print(ret)
"""
根据返回值不同类型划分:
返回对象列表
all
filter
exclude
order_by
reverse
values [{},{}]
values_list [(),()]
distinct
返回对象
get
first
last
返回数字
count
返回布尔值
exists
"""
11. 数据比较方法
# 大于 great than
models.Person.object.filter(id__gt=4)
# 小于 less than
models.Person.object.filter(id__lt=4)
# 大于等于 great than equal
models.Person.object.filter(id__gte=4)
# 小于等于
models.Person.object.filter(id__lte=4)
# 范围,左右都包含
models.Person.object.filter(id__range=[1,4])
# 成员判断
models.Person.object.filter(id__in=[1,4])
# like
models.Person.object.filter(name__contains='alex')
# like 忽略大小写
models.Person.object.filter(name__icontains='alex')
# 以。。开头
models.Person.object.filter(name__startswith='alex')
# 以。。开头忽略大小写
models.Person.object.filter(name__istartswith='alex')
# 以。。结尾
models.Person.object.filter(name__endswith='alex')
# 以。。结尾忽略大小写
models.Person.object.filter(name__iendswith='alex')
# 年份筛选
models.Person.object.filter(birth__year='2020')
# 不支持月份筛选,因此可使用contains做过滤
models.Person.object.filter(birth__contains='2020-02')
# 校验字段呢是否为null
models.Person.object.filter(name__isnull=True)
12. 有关外键的查询
#model
class Publisher(models.Model):
name=models.CharField(max_length=32)
class Book(models.Model):
name=models.CharField(max_length=32) pub=models.ForeginKey('Publisher',on_delete=models.CASCADE,related_name='books') # pub_id
# 基于对象的查询
book_obj=Book.objects.get(pk=1)
# 正向查询
book_obj.pub # 所关联的对象
book_obj.pub_id # 所关联对象的id
#反向查询
pub_obj=Publisher.objects.get(pk=1)
# 不指定related_name时,使用类名小写_set
pub_obj.book_set # 关系管理对象
pub_obj.book_set.all() # 关联的所有对象
# 指定related_name='books'后(在models文件中指定)
pub_obj.books.all()
# 基于字段的查询
ret=Book.objects.filter(name='xxx')
ret=Book.objects.filter(pub__name='出版社')
ret=Publisher.objects.filter(pk=1)
# 不指定related_name
ret=Publisher.objects.filter(book__name='书名')
# 指定related_name='books'后
ret=Publisher.objects.filter(books__name='书名')
# 指定related_query_name='xxx'后 优先级最高
ret=Publisher.objects.filter(xxx__name='书名')
13. 多对多关系的操作
#model
class Book(models.Model):
name=models.CharField(max_length=32) pub=models.ForeginKey('Publisher',on_delete=models.CASCADE,related_name='books') # pub_id
class Author(models.Model):
name=models.CharField(max_length=32)
books=models.ManyToManyField('Book')
author_obj = models.Author.filter(pk=1)
author_obj.books # 关系管理对象
# all 查询所有关联对象
author_obj.books.all()
# set 设置关系 [1,2] [对象,对象]
author_obj.books.set([1,2])# 传入[]=删除所有关系
# add 新增关系 id 或 对象
author_obj.books.add(1,2)
# remove 删除关系 id 或 对象
author_obj.books.remove(1,2)
# clear 清空关系
author_obj.clear()
# create 新增一个所关联的对象,并和当前对象绑定关系
author_obj.books.create(name='xxx',pub_id=1)
# 一对多关系中反向查询获取到的关系管理对象
# 1.参数中只能使用对象,不能传入id
# 2.当ForginKey中有参数null=True时,才能使用remove和clear操作。
# 因为删除关系相当于把外键中绑定的id置为Null,字段如果不允许为null,就不允许使用此方法
14. 聚合和分组
# aggregate(聚合) 终止子句 因为查询结果已经变为字典,无法再使用.xxx(filter之类的)做相关查询
# 使用aggregate时可以不使用all()方法,默认当前表所有数据,不过可以使用filter先做一次过滤
ret = models.Book.objects.all().aggregate(Max('price')) # {'price__max': Decimal('999.00')}
ret = models.Book.objects.all().aggregate(Min('price')) # {'price__min': Decimal('9.90')}
ret = models.Book.objects.all().aggregate(Max('price'),Min('price')) # {'price__max': Decimal('999.00'), 'price__min': Decimal('9.90')}
ret = models.Book.objects.all().aggregate(max=Max('price'),min=Min('price')) # {'max': Decimal('999.00'), 'min': Decimal('9.90')}
print(ret)
## 分组 group by
## annotate 注释 过程中使用了分组,将聚合后的信息以注释的形式添加进了数据结果组里
## 统计每本书的作者个数
ret = models.Book.objects.annotate(Count('authors')).values()
## 统计每个出版社最便宜的书的价格
## 方法一:
ret = models.Publisher.objects.annotate(Min('book__price')).values()
for i in ret:
print(i)
## 方法二:
ret = models.Book.objects.annotate(Min('price')).values() # 错误写法 按照书进行分组了
## 按照pub、pub__name、name 三个条件做了分组
ret = models.Book.objects.values('pub','pub__name','name').annotate(Min('price'))
for i in ret:
print(i)
## 统计不止一个作者的书
ret = models.Book.objects.annotate(count=Count('authors')).filter(count__gte=2)
for i in ret:
print(i)
## 根据一本书的作者数量做排序
ret = models.Book.objects.annotate(count=Count('authors')).values().order_by('-count')
print(ret)
## 求各个作者作书的总价格
## 方法一
ret = models.Author.objects.annotate(sum=Sum('books__price')).values()
## 方法二
ret = models.Book.objects.values('authors__id','authors__name').annotate(sum=Sum('price'))
print(ret)
15. F查询和Q查询
# -*- coding = utf-8 -*-
# @Time :2021/1/17 下午3:42
# @Author :Asher
# @File :6.F查询和Q查询.py
# @SoftWare :PyCharm
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
django.setup()
from app01 import models
from django.db.models import F,Q
# ret = models.Book.objects.filter(kucun__lt=50)
# 相当于 where sale > kucun
ret = models.Book.objects.filter(sale__gt=F('kucun'))
ret = models.Book.objects.filter(id__lte=3).update(sale=F('sale') * 2 - 3)
## 求id<3或id>5的数据
ret = models.Book.objects.filter(id__lt=3,id__gt=5)# 错误写法,相当于id<3且id>5
ret = models.Book.objects.exclude(id__gte=3,id__lte=5)# 正确写法,取id大于等于3且小于等于5的结果取反
# 使用Q查询 使用,连接时,依旧表示且的关系,想表示或的关系需要使用|连接
ret = models.Book.objects.filter(Q(id__lt=3)|Q(id__gt=5))
# Q查询可嵌套
ret = models.Book.objects.filter(Q(Q(id__lt=3)|Q(id__gt=5)),name__startswith='新')
# 两个Q查询中间可以用&符号连接
ret = models.Book.objects.filter(Q(Q(id__lt=3)|Q(id__gt=5))&Q(name__startswith='新'))
# | 表示或 &表示与 ~表示非
print(ret)