Django之ORM
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。在业务逻辑层和数据库层之间充当了桥梁的作用.
Django项目使用MySQL数据库
-
在Django项目的settings.py文件中,配置数据库连接信息:
DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "你的数据库名称", # 需要自己手动创建数据库 "USER": "数据库用户名", "PASSWORD": "数据库密码", "HOST": "数据库IP", "POST": 3306, 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}, }
}
```
-
在与Django项目同名的目录下的
__init__.py
文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库:import pymysql pymysql.install_as_MySQLdb()
注:数据库迁移的时候出现一个警告
WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default' HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
在配置中多加一个OPTIONS参数:Django官网解释
'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
ORM模型
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表。
基本情况:
- 每个模型都是一个Python类,它是django.db.models.Model的子类。
- 模型的每个属性都代表一个数据库字段。
- 综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档链接。
简单上手
下面这个例子定义了一个 Person 模型,包含 first_name 和 last_name。
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name 和 last_name 是模型的字段。每个字段被指定为一个类属性,每个属性映射到一个数据库列。
上面的 Person 模型将会像这样创建一个数据库表:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
说明:
- 表明myapp_person是自动生成的,
app名_类名
,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数. - id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
- Django支持MySQL5.5及更高版本。
ORM的字段
-
AutoField 主键
自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。 当model中如果没有自增列,则自动会创建一个列名为id的列 一个model不能有两个AutoField字段。 # 自定义自增列 nid = models.AutoField(primary_key=True)
-
IntegerField
整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField正整数 BigIntegerField长整型(有符号的)
-
BooleanField
布尔值类型 gender = models.BooleanField('性别', choices=((0, '女'), (1, '男')))
-
CharField
字符类型 必须提供max_length参数, max_length表示字符长度
-
TextField
文本类型
-
DateTimeField DateField 日期时间
auto_now_add=True # 新增数据的时候会自动保存当前的时间 auto_now=True # 新增、修改数据的时候会自动保存当前的时间 default
-
DecimalField 十进制的小数
max_digits 小数总长度 5 decimal_places 小数位长度 2
-
EmailField
字符串类型,Django Admin以及ModelForm中提供验证机制
-
IPAddressField
字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
-
其他类型
SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 NullBooleanField(Field): - 可以为空的布尔值 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) - 浮点型 BinaryField(Field) - 二进制类型
自定义字段类型
class MyCharField(models.Field):#继承models.Field,不会生成表
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
使用自定义char类型字段:
class Class(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=25)
# 使用自定义的char类型的字段
cname = MyCharField(max_length=25)
ORM的字段参数
null 数据库中字段是否可以为空
db_column 数据库中字段的列名
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键
db_index 数据库中字段是否可以建立索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息
choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gender = models.IntegerField(choices=[(0, '女'),(1, '男'),],default=1)
error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique,unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}
validators 自定义错误验证(列表类型),从而定制想要的验证规则
Model Meta 表参数
class UserInfo(models.Model):
def __str__(self):#定义打印该对象显示内容
return self.name
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"
# admin中显示的表名称
verbose_name = '个人信息'
# verbose_name加s
verbose_name_plural = '所有用户信息'
# 联合索引
index_together = [
("pub_date", "deadline"), # 应为两个存在的字段
]
# 联合唯一索引
unique_together = (("driver", "restaurant"),) # 应为两个存在的字段
ORM查询
-
all() 获取所有的数据 ——QuerySet对象列表
ret = models.Person.objects.all()
-
get() 获取满足条件的一个数据 ——对象
获取不到或者多个都报错
ret = models.Person.objects.get(pk=1)
-
filter() 获取满足条件的所有数据 —— QuerySet 对象列表
ret = models.Person.objects.filter(pk=1)
-
exclude() 获取不满足条件的所有数据 —— QuerySet 对象列表
ret = models.Person.objects.exclude(pk=1)
-
values() 拿到对象所有的字段和字段的值 QuerySet [ {} ,{} ]
拿到对象指定的字段和字段的值 QuerySet [ {} ,{} ] ret = models.Person.objects.values('pid','name') <QuerySet [{'pid': 1, 'name': 'bbb'}]>
-
values_list() 拿到对象所有的字段的值 QuerySet [ () ,() ]
values('字段') 拿到对象指定的字段的值 QuerySet [ {} ,{} ] ret = models.Person.objects.values_list('name','pid')
-
order_by 排序 前面加负号-表示降序 ——》 QuerySet [ () ,() ]
ret = models.Person.objects.all().order_by('age','-pid')
-
reverse 反向排序 只能对已经排序的QuerySet进行反转
ret = models.Person.objects.all().order_by('age','-pid').reverse()
-
distinct 去重 完全相同的内容才能去重
对于对象的去重没有意义,因为id永远不一样。不可能存在两个相同的对象。
ret = models.Person.objects.values('age').distinct()
-
count() 计数
ret = models.Person.objects.all().count()
-
first 取第一元素 没有元素 None
ret = models.Person.objects.filter(pk=1).values().first()
-
last 取最后一元素 没有元素 None
-
exists 查询的数据是否存在
ret = models.Person.objects.filter(pk=1000).exists()
单表双下划线
ret = models.Person.objects.filter(pk__gt=1) # gt greater than 大于
ret = models.Person.objects.filter(pk__lt=3) # lt less than 小于
ret = models.Person.objects.filter(pk__gte=1) # gte greater than equal 大于等于
ret = models.Person.objects.filter(pk__lte=3) # lte less than equal 小于等于
ret = models.Person.objects.filter(pk__range=[2,3]) # range 范围
ret = models.Person.objects.filter(pk__in=[1,3,10,100]) # in 成员判断
ret = models.Person.objects.filter(name__contains='A')
ret = models.Person.objects.filter(name__icontains='A') # 忽略大小写
ret = models.Person.objects.filter(name__startswith='a') # 以什么开头
ret = models.Person.objects.filter(name__istartswith='A')
ret = models.Person.objects.filter(name__endswith='a') # 以什么结尾
ret = models.Person.objects.filter(name__iendswith='I')
date字段可以:
ret = models.Person.objects.filter(birth__year='2019')
ret = models.Person.objects.filter(birth__contains='2018-06-24')
ret = models.Person.objects.filter(phone__isnull=False)
多对一操作
ForeignKey外键
class Publisher(models.Model):
name = models.CharField(max_length=32, verbose_name="名称")
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=32)
pub = models.ForeignKey(Publisher,
related_name='books',related_query_name='xxx',on_delete=models.CASCADE)
def __str__(self):
return self.title
基于对象查询
正向
从多的一方查询,查询到单个对象
语法:对象.关联字段.字段
book_obj.pub ——》 所关联的对象
book_obj.pub_id ——》 所关联的对象id
book_obj.pub.name
反向
从少的一方查询,查询到多个结果
语法:obj.表名_set
-
没有指定related_name
pub_obj.book_set ——》 关系管理对象 (类名小写_set)
pub_obj.book_set.all() ——》 所关联的所有对象
-
指定related_name='books'
pub_obj.books ——》 关系管理对象
pub_obj.books.all() ——》 所关联的所有对象
外键关系管理对象方法
print(pub_obj.books.all())
#创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
obj = pub_obj.books.create(title='用python养猪')
#更新model对象的关联对象。
pub_obj.books.set(models.Book.objects.filter(pk__in=[4,5])) # 不能用id 只能用对象
# 把指定的model对象添加到关联对象集中。
pub_obj.books.add(*models.Book.objects.filter(pk__in=[1,2]))# 不能用id 只能用对象
pub = models.ForeignKey(Publisher, related_name='books',null=True, on_delete=models.CASCADE)
# 只有外键可为空null=True时有remove clear
pub_obj.books.remove(*models.Book.objects.filter(pk__in=[1,2]))
pub_obj.books.clear()
基于字段查询
正向
语法:关联字段__字段
models.Book.objects.filter(pub__name='xxxxx') 外键+双下划线+跨表字段
反向
语法:表名__字段
-
没有指定related_name
models.Publisher.objects.filter(book__title='xxxxx')
-
指定related_name=‘books’
models.Publisher.objects.filter(books__title='xxxxx')
-
指定related_query_name='book‘,专门用于字段查询,优先级高于related_name
models.Publisher.objects.filter(book__title='xxxxx')
多对多操作
ManyToManyField
实例解析:
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=6, decimal_places=2) # 9999.99
sale = models.IntegerField()
kucun = models.IntegerField()
pub = models.ForeignKey(Publisher, null=True,on_delete=models.CASCADE)
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=32, )
books = models.ManyToManyField(Book) #django自建,共三种创建方式.也可加related_name
def __str__(self):
return self.name
基于对象:
-
正向:
author_obj = models.Author.objects.get(pk=1) author_obj.books #关系管理对象 author_obj.books.all()
-
反向:
book_obj = models.Book.objects.get(pk=1) # 不指定related_name print(book_obj.author_set) # ——》 关系管理对象 print(book_obj.author_set.all()) # related_name='authors' print(book_obj.authors)# ——》 关系管理对象 print(book_obj.authors.all())
基于字段:
-
正向:
ret = models.Author.objects.filter(books__title='Django')
-
反向:
# 不指定related_name ret = models.Book.objects.filter(author__name='yhp') #类名字__字段 # related_name='authors' ret = models.Book.objects.filter(authors__name='yhp') #用其替换类名 # related_query_name='xxx' ret = models.Book.objects.filter(xxx__name='yhp')
关系管理对象方法
author_obj = models.Author.objects.get(pk=1)
# all() 所关联的所有的对象
# set 重置多对多的关系 [id,id] [ 对象,对象 ]清空关系,再重新设置
author_obj.books.set([1,2])
author_obj.books.set(models.Book.objects.filter(pk__in=[1,2,3]))
# add 添加多对多的关系 (id,id) (对象,对象),再原有关系基础上添加,直接添加,不是列表形式
author_obj.books.add(4,5)
author_obj.books.add(*models.Book.objects.filter(pk__in=[4,5]))#打散添加
# remove 删除多对多的关系 (id,id) (对象,对象)
author_obj.books.remove(4,5)
author_obj.books.remove(*models.Book.objects.filter(pk__in=[4,5]))
# clear() 清除所有的多对多关系
author_obj.books.clear()
# create() 创建对象并设置关系
obj = author_obj.books.create(title='书名',pub_id=1)
book_obj = models.Book.objects.get(pk=1)
obj = book_obj.author_set.create(name='烧饼')
关系管理对象详解
"关联管理对象"是在一对多或者多对多的关联上下文中使用的管理器。
它存在于下面两种情况:
- 外键关系的反向查询
- 多对多关联关系
简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。
create()
创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
>>> import datetime
>>> models.Author.objects.first().book_set.create(title="番茄物语", publish_date=datetime.date.today())
add()
把指定的model对象添加到关联对象集中。
添加对象
>>> author_objs = models.Author.objects.filter(id__lt=3)
>>> models.Book.objects.first().authors.add(*author_objs)
添加id
>>> models.Book.objects.first().authors.add(*[1, 2])
set()
更新model对象的关联对象。
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.set([2, 3])
remove()
从关联对象集中移除执行的model对象
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.remove(3)
clear()
从关联对象集中移除一切对象。
>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.clear()
注意:
对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。
举个例子:
ForeignKey字段没设置null=True时,
class Book(models.Model):
title = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Publisher)
没有clear()和remove()方法:
>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'
当ForeignKey字段设置null=True时,
class Book(models.Model):
name = models.CharField(max_length=32)
publisher = models.ForeignKey(to=Class, null=True)
此时就有clear()和remove()方法:
>>> models.Publisher.objects.first().book_set.clear()
注意:
- 对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
分组&聚合
from app01 import models
from django.db.models import Max, Min, Avg, Sum, Count
# 聚合
ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price'))
print(ret)
运行结果:{'avg':160.0,'price__max':Decimal('190.0')}
数据结构为字典,终止语句.关键字传参可改变字典的key,默认为:字段__聚合函数名
说明:
models.Book.objects.filter(pk__gt=3) #表示对哪些数据进行聚合
aggregate(Max('price'),avg=Avg('price'))#对哪些字段进行聚合,
# 分组
# 统计每一本书的作者个数
ret = models.Book.objects.annotate(count=Count('author')) # annotate 注释
# 统计出每个出版社的最便宜的书的价格
# 方式一
ret = models.Publisher.objects.annotate(Min('book__price')).values()
# 方式二
ret = models.Book.objects.values('pub_id').annotate(min=Min('price'))
# annotate前为分组依据,后面为聚合字段
aggregate()
是QuerySet
的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
F和Q
from django.db.models import F
# F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
ret=models.Book.objects.filter(sale__gt=F('kucun'))
# 只更新sale字段,update效率高于save
models.Book.objects.all().update(sale=100)
# 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
models.Book.objects.all().update(sale=F('sale')*2+10)
Q(条件)
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
查询函数可以混合使用Q 对象
和关键字参数。所有提供给查询函数的参数(关键字参数或Q
对象)都将"AND”在一起。但是,如果出现Q
对象,它必须位于所有关键字参数的前面。
| 或
- & 与
- 非
from django.db.models import Q
ret = models.Book.objects.filter(Q(Q(pk__gt=3) | Q(pk__lt=2)) & Q(price__gt=50))
事务
from django.db import transaction
try:
with transaction.atomic():
# 进行一系列的ORM操作
models.Publisher.objects.create(name='xxxxx')
models.Publisher.objects.create(name='xxx22')
except Exception as e :
print(e)
# with语句内进行原子型操作