ORM
文章目录
基本环境
-
执行环境
- 新建虚拟环境,此处存疑
- Terminal执行
-
安装
pip install mysqlclient
– 没有mysqlclient抛异常
pip install django
-
项目准备
django-admin startproject salary .
- 管理、创建一个项目salary
- .,表示在当前目录创建
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrl81kMy-1575548413381)(D:\1Face_To_Face\16week_sixteen\xmind\salary.png)]
-
创建应用
python manage.py startapp employee
- manage.py文件是管理文件
- 执行管理文件startapp employee目录
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zm43twwP-1575548413387)(D:\1Face_To_Face\16week_sixteen\xmind\employee.png)]
项目创建
- 创建Employee类 – employee.models.py文件中
from django.db import models # models,模块专用
# Create your models here.
# Employee对应table
# Employee类属性对应Field
# Employee实例对应record,一个Employee实例,对应一行记录,且实例属性名为类属性名
class Employee(models.Model): # 需要继承自models.Model类,元类
class Meta: # 使用Meta类修改表名。不指定默认使用<appname>_<mode_name>
db_table = 'employees' # 未指定表名为employee_employee
emp_no = models.IntegerField(primary_key=True) # 设置主键,唯一且不为null
birth_date = models.DateField(null=False) # 日期字段
first_name = models.CharField(null=False,max_length=14) # CharField对应可变字符串,Django中没有不可变字段
last_name = models.CharField(null=False,max_length=16)
gender = models.SmallIntegerField(null=False) # Djano中没有枚举,使用数值
hire_date = models.DateField(null=False)
def __repr__(self): # 测试,用来观察
return "<Employee: {} {} {} >".format(self.emp_no,self.first_name,self.last_name)
__str__ = __repr__
-
配置 – salary.setting.py
-
打开salary目录下的setting.py主配置文件
-
修改数据库配置
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', # 修改成django.db.backends.mysql 'NAME': 'mydatabase', # 修改成指定数据库名 'USER': 'mydatabaseuser', # 登录用户信息 'PASSWORD': 'mypassword', # 用户登录密码 'HOST': '127.0.0.1', # 数据库IPv4地址 'PORT': '5432', # 端口,注意是字符串 } }
- 修改时区
TIME_ZONE = 'Asia/Shanghai
- 注册应用, 将employee.models.Employee类追加注册到INSTALLED_APPS的列表中
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'employee', ]
- 在employee目录下,创建test.py文件,进行配置
- 测试代码就在此文件中
import os import django # 参考salary目录下的wsgi.py文件 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings') django.setup()
- logging,观察测试代码的结果
- URL:
https://docs.djangoproject.com/en/2.2/
- Common Web application tools中的logging选项
- 进入logging选项,找到Examples,找到如下代码复制到setting文件中(建议EOF)
- 检查setting.py中
DEBUG = True
是否为True,False无法显示
- URL:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), # 修改成'DEBUG' }, },
-
管理器对象
- Django会为每个模型类提供一个objects对象
- django.db.models.manager.Manager类型,与数据库进行交互,即模型类不能直接与数据库进行交互
- 定义模型类的时候,元类创建模型类的时候,添加了类属性objects,指向一个描述器类对象
- 定义模型类的时候没有指定管理器,Django默认为模型类提供一个管理器;如果在模型类中手动设置管理器后,Django不再提供默认的objects管理器
测试代码
import os
import django
from django.db.models.manager import ManagerDescriptor
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
# employee.models.Employee相当于table
from employee.models import Employee
# 源码初读
mgr = Employee.objects # 元类创建Employee的时候,注入类属性objects,且objects是描述器,与数据库进行交互,实现原理未知
e = Employee() # Employee实例
print(type(e)) # Employee类
# 惰性的,迭代、序列化、if语句中会立即访问数据库
print(mgr.filter(pk=10010).__dict__) # 创建查询集不会立即访问数据库,创建了django.db.models.sql.query.Query object等
for i in mgr.filter(pk=10010): # 循环,立即查询
print(i.__dict__) # Employee的实例,扫描类属性,根据类属性为Employee实例创建相应的实例属性
print(e.__dict__) # 与书面循环中的i进行对比,证明确实为Employee的实例,源码实现看不懂
** 返回值
{'model': <class 'employee.models.Employee'>, '_db': None, '_hints': {}, 'query': <django.db.models.sql.query.Query object at 0x000002B23E7B3470>, '_result_cache': None, '_sticky_filter': False, '_for_write': False, '_prefetch_related_lookups': (), '_prefetch_done': False, '_known_related_objects': {}, '_iterable_class': <class 'django.db.models.query.ModelIterable'>, '_fields': None}
{'_state': <django.db.models.base.ModelState object at 0x000002B23E7EE048>, 'emp_no': 10010, 'birth_date': datetime.date(1963, 6, 1), 'first_name': 'Duangkaew', 'last_name': 'Piveteau', 'gender': 'F', 'hire_date': datetime.date(1989, 8, 24)}
(0.000) SELECT @@SQL_AUTO_IS_NULL; args=None
{'_state': <django.db.models.base.ModelState object at 0x000002B23E75EE48>, 'emp_no': None, 'birth_date': None, 'first_name': '', 'last_name': '', 'gender': None, 'hire_date': None}
(0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`emp_no` = 10010; args=(10010,)
查询 – 使用objects管理器
结果集方法
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
# 返回所有记录
print(mgr.all()) # 惰性,返回表中所有记录的查询集
print(mgr.all()[10:15]) # 切片查询,sql语句为limit 5 offset 10,即limit '切片stop-start值' offset '切片start值'
# 过滤
print(mgr.filter(emp_no=10010)) # 惰性,返回满足条件的数据,列表
print(mgr.exclude(pk=10010)) # 惰性,排除满足条件的数据,列表。emp_no为主键,会默认生成一个pk名,两者等价
# 排序
print(mgr.all().order_by('-pk')) # 排序,注意参数为字符串,返回列表;字符串前面加-,表示降序,不加-,表示升序
# 投影
value(),可以投影,参数为Field字符串,可以为多个字段,注意和dict.values(self)的不一样
print(mgr.filter(emp_no=10010).values('emp_no','first_name','gender')) # 查询集,返回一个对象字典的列表,元素为字典,字典的键值对分别为Field和Record
# 判断查询集中是否有数据
print(mgr.all().exists()) # 有数据,返回True。sql查询语句为:SELECT (1) AS `a` FROM `employees` LIMIT 1; args=()
print(mgr.filter(emp_no=10030).exists()) # 无数据,返回False
返回单值
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
# 返回单个对象
print(mgr.filter(emp_no=10010).get()) # 仅返回单个满足条件的对象
# print(mgr.all().get()) # 如果能返回多条,抛MultipleObjectsReturned异常,get() returned more than one Employee
print(mgr.filter(emp_no=10030).get()) # 未查询到数据,抛DoesNotExist异常: Employee matching query does not exist
# 返回第一个和最后一个对象
print(mgr.exclude(pk=10010).order_by('-emp_no').first()) # 返回第一个对象,本质limit 1
print(mgr.filter(emp_no__lt=10010).order_by('emp_no').last()) # 返回第二个对象,本质降序limit 1
print(mgr.filter(emp_no=10030).first()) # 未查询到数据,返回None
print(mgr.filter(emp_no=10030).last()) # 未查询到数据,返回None
# 返回当前查询的记录数
print(mgr.filter(emp_no__gt=10010).count()) # 返回查询记录数
Field Lookup表达式
- 字段查询表达式可以作为filter()、exclude()、get()方法的参数,实现where子句的selection
- 语法:
属性名称__比较运算符=值
注意:属性名和运算符之间使用双下划线
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
# exact,严格等于
print(mgr.filter(emp_no__exact=10010))
print(mgr.filter(emp_no=10020)) # 可省略不写
查询到返回列表查询值,未查询到,不抛异常返回空列表
# contains,是否包含,等价like语句,除左like,其余禁止使用
print(mgr.filter(emp_no__contains='1')) #like binary '1%',binary在sql语句中表示忽略大小写
print(mgr.exclude(first_name__contains='P')) # like binary '%P%'
# startswith、endswith,注意和str的startswith、endswith不一样,但是效果一样
print(mgr.filter(first_name__startswith='P')) # like binary P%,可以使用
print(mgr.filter(last_name__endswith='P')) # like binary %P,禁止使用
# 判断是否为null
print(mgr.filter(emp_no__isnull=False)) # where emp_no is not null 不为null,返回查询集,否则返回空查询集
print(mgr.filter(emp_no__isnull=True)) # where emp_no is null,为null,返回查询集,否则返回空查询集
isnotnull用法未知
# 是否在指定范围数据中
print(mgr.filter(emp_no__in=[10010,10013])) # where emp_no in (10010,10013)
>、>=、<、<=
print(mgr.filter(emp_no__gt=10010)) # where emp_no > 10010
print(mgr.filter(emp_no__gte=10010)) # where emp_no >= 10010
print(mgr.filter(emp_no__lt=10010)) # where emp_no < 10010
print(mgr.filter(emp_no__lte=10010)) # where emp_no <= 10010
日期类型处理,sql语句:extract from,从日期中提取month、day、hour、minute、second
print(mgr.filter(hire_date__year=1990)) # where hire_date between '1990-01-01' and '1990-12-31'
print(mgr.filter(hire_date__month=12)) # where (extract month from hire_date) = 12
print(mgr.filter(hire_date__day=12)) # where (extract day form hire_date) = 12
Q对象
- #Q对象,可以使用&、|操作符组成逻辑表达公,~表示not
import os
import django
from django.db.models import Q
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
# 与,and
print(mgr.filter(pk__gt=10010).filter(pk__lt=10015)) # where emp_no > 10010 and emp_no < 10015
print(mgr.filter(pk__gt=10010,pk__lt=10015))
print(mgr.filter(Q(pk__gt=10010) & Q(pk__lt=10015)))
print(mgr.filter(Q(pk__gt=10010),Q(pk__lt=10015))) # 四者sql语句相同,返回值相同
# 或,or
print(mgr.filter(Q(pk__gt=10015) | Q(pk__lt=10005))) # where emp_no > 10015 or emp_no < 10005
聚合、分组
- 聚合类
from django.db.models import Avg,Max,Min,Count
- 配合aggregate()方法使用,参数为聚合类,返回字典
注意:objects可以直接使aggregate方法,查询集也可以使aggregate方法
import os
import django
from django.db.models import Avg,Max,Min,Count,Sum
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
print(mgr.filter(emp_no__gt=10010).aggregate(Avg('pk'))) # select avg(gender)
print(mgr.filter(emp_no__gt=10010).aggregate(Count('gender'))) # select count(gender)
print(mgr.filter(emp_no__lt=10010).aggregate(Sum('pk'))) # select sum(emp_no)
print(mgr.filter(emp_no__lt=10010).aggregate(Max('emp_no'))) # select max(emp_no)
print(mgr.filter(emp_no__lt=10010).aggregate(Min('emp_no'))) # select min(emp_no)
print(mgr.aggregate(Max('pk'),min=Min('pk'))) # 别名
- 聚合
- annotate()方法用来聚合分组,参数为聚合类
- annotate()返回查询集,aggregate()返回字典
- 配合values()方法使用;
- values()放在annotate()之前,意为指定分组字段,未指定分组字段,默认使用主键分组
- values()放在annotate()之后,意为取分组结果中的字段
- annotate()方法用来聚合分组,参数为聚合类
annotate()方法,只聚合,分组需要配合values使用
aggregate()方法,只能聚合,配合values没有效果
import os
import django
from django.db.models import Avg,Max,Min,Count,Sum
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
mgr = Employee.objects
print(mgr.annotate(Count('gender')).values('gender')) # group by emp_no
print(mgr.values('gender').annotate(Count('gender'))) # group by gender
print(mgr.values('gender').aggregate(Count('gender'))) # 不会分组,只统计指定Field的Record
一对多模型
Django不支持联合主键,只支持单一主键,提倡
Django没有提供多表连接方法,通过E-R模型,实现多表连接
ForeignKey类,多端表(员工表)中使用
- 表中存在联合主键,解决方案
- 取消表中所有联合主键,并删除所有外键约束后保存,成功后再继续
- 为表增加一个id字段,自增主键。保存成功,自动填充数据
- 重建原来的外键约束即可
模型创建
- ForeignKey,外键类
- 参数:
- to,必须给定;设置与其建立外键约束的表,注意传入的是Employee类名
- on_delete,必须给定;设置外键约束,此处为了测试,设置为级联。实际生产中都是假删除,增加一个字段对删除的record标记,此时级联几乎没有作用
- null,设置外键值不能为Null
- related_name,设置多端salaries_set的属性名
- 参数:
- db_column,设置字段名;未指定,使用属性名;
- Django习惯上给外键默认起名xxx_id,未设置db_column,外键字段名=‘emp_no_id’,实际上数据库中没有emp_no_id字段;print(Salary.objects.all()),抛1054, “Unknown column ‘salaries.emp_no_id’ in ‘field list’”
- 解决方案,设置外键字段名,db_column=‘emp_no’
- 设置外键字段名后,Salary类属性依然是emp_no_id,但是使用emp_no可以查询数据库
from django.db import models
# Create your models here.
class Employee(models.Model):
class Meta:
db_table = 'employees'
emp_no = models.IntegerField(primary_key=True)
birth_date = models.DateField(null=False)
first_name = models.CharField(null=False,max_length=14)
last_name = models.CharField(null=False,max_length=16)
gender = models.SmallIntegerField(null=False)
hire_date = models.DateField(null=False)
def __repr__(self):
# 不要使用多增加的salaries_set属性
return "<Employee: {} {} {} >".format(self.emp_no,self.first_name,self.last_name)
__str__ = __repr__
class Salaries(models.Model):
class Meta:
db_table = 'salaries'
# 额外手动增加的,Django不支持联合主键(原表候选键为emp_no,from_date)
id = models.AutoField(primary_key=True) # 唯一主键,非null且唯一
from_date = models.DateField(null=False)
to_date = models.DateField(null=False)
salary = models.IntegerField(null=False)
emp_no = models.ForeignKey(to='Employee',on_delete=models.CASCADE,null=False,db_column='emp_no)
def __repr__(self):
# 危险,每次访问self.emp_no,查询数据库
# return "<Salaries: {} {}".format(self.emp_no,self.salary)
return "<Salaries: {} {}".format(self.emp_no_id,self.salary)
__str__ = __repr__
从多端往一端,查多端self.emp_no
外键,多端(对应员工表),emp_no属性,指向ForwardManyToOneDescriptor object。
- 每次查询self.emp_no属性,都会为Salary实例填充emp_no属性,且指向ForwardManyToOneDescriptor object
- 通过Salary实例属性emp_no,可以直接访问一端的字段数据
- 多增加了emp_no_id属性,实际数据库没有emp_no_id字段,需要设置
import os
import django
from django.db.models import Avg,Max,Min,Count,Sum
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee,Salaries
em_mgr = Employee.objects
sa_mgr = Salaries.objects
# 从多端往一端查,使用Salaries().'emp_no', 指向ForwardManyToOneDescriptor object
# 通过ForwardManyToOneDescriptor object实例,向前多对一描述符,即多端对一端查询
print(*Salaries.__dict__.items(),sep='\n')
从一端往多端,查一端self.salary_set
主键,一端(对应部门表),多增加了salaries_set(由’外键所在表名_set’组成)属性
- 每次查询self.salary_set属性,都会为Employee实例填充salaries_set属性,且指向ReverseManyToOneDescriptor object
- 通过Employyee实例属性salaries_set,不能直接访问多端的数据,需要使用objects管理器的查询方法
- objects的查询集方法没有salaries_set属性,需要遍历按个处理
- 注意和Salary实例属性emp_no的区别
- salary_set属性,指向ReverseManyToOneDescriptor object,可以操作多端表
import os
import django
from django.db.models import Avg,Max,Min,Count,Sum
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee,Salaries
em_mgr = Employee.objects
sa_mgr = Salaries.objects
# 从一端往多端查,使用Employee.salaries_set属性
# 通过ReverseManyToOneDescriptor object,反转多对一描述符,即一端对多端查询
print(*Employee.__dict__.items(),sep='\n')
一对多测试
import os
import django
from django.db.models import Avg,Max,Min,Count,Sum
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee,Salaries
em_mgr = Employee.objects
sa_mgr = Salaries.objects
# 从一端往多端查询,通过Employee().salary_set,已设置成salary
for em in em_mgr.filter(pk__lt=10005): # 返回查询集,遍历按个处理
print(em.emp_no,em.first_name,em.salary.values('salary')) # em.salary.values('salary'),配合管理器查询方法投影多端字段
# 从多端往一端拆线呢,通过Salary().emp_no
for sa in sa_mgr.filter(emp_no__gt=10002):
print(sa.emp_no.emp_no,sa.emp_no.first_name,sa.salary,) # 这种查询会导致查询集中的n个Salaries实例均填充emp_no属性,并且查询n次数据库
# 从多端往一端查询,改进
slst = sa_mgr.filter(emp_no__lt=10003)
first = slst[0] # 指定一个Salaries实例
emp = first.emp_no # 访问Salaires.emp_no
print(slst)
for sa in slst: # 通过指定first,实现只出10001员工的信息
print(emp.emp_no,emp.first_name,sa.salary) # 为什么10002不答应,因为实现原理是表中的记录必须一一对应?
多对多
distinct
- 去重查询,如果表中有多个重复记录,且只想直到不重复记录时使用
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee,Salaries
em_mgr = Employee.objects
sa_mgr = Salaries.objects
print(sa_mgr.all().values('emp_no').distinct()) # 使用语法
raw – 裸sql语句实现
- 如果查询非常复杂,使用Django不方便,可以直接使用SQL语句
注意,测试失败,此处存疑
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
em_mgr = Employee.objects
sql = """\
select distinct em.emp_no,em.first_name,em.last_name,sa.salary
from employees as em join salaries as sa
on em.emp_no = sa.emp_no
where sa.salary > 70000
"""
em = em_mgr.raw(sql)
print(em) # 惰性可迭代对象
print(list(em_mgr.raw(sql))) # 测试不成功,此处存疑
- 测试代码
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup()
from employee.models import Employee
em_mgr = Employee.objects
# 注意,增加的属性salaries_set不要修改成salaries中的字段名,此处salaries_set修改成'salary',导致与sa.salary字段名冲突,抛异常
# TypeError: Direct assignment to the reverse side of a related set is prohibited. Use salary.set() instead.
sql = """\
select em.emp_no,em.first_name,em.last_name,sa.salary
from employees as em join salaries as sa
on em.emp_no = sa.emp_no
where sa.salary > 70000;
"""
print(em_mgr.raw(sql)) # 惰性可迭代对象
print(list(em_mgr.raw(sql))) # 测试成功
总结
- emp_no属性,ForwardManyToOneDescriptor object,每次访问实例的emp_no属性,都会为实例填充emp_no属性,并且查询一端的数据库
- 多端通过Salary(代指多端模型类)实例属性emp_no(代指将ForeignKey实例的变量),直接访问一端的Field
- salaries_set属性,ReverseManyToOneDescriptor object,每次访问实例的salaries_set属性,都会为实例填充salaries_set属性,并且查询多端的数据库
- 一端通过Employee(代指一端模型类)实例属性salaries_set(代指一端增加的属性)配合管理器objects查询集方法访问多端
- 查询出来的单个salaries对象,可以直接操作多端的Field
- 说白了,就是为一端和两多的每条记录,即每个实例添加了对应的实例属性,通过这个增加的实例属性,实现一端往多端、多端往一端查询到效果
总结
- emp_no属性,ForwardManyToOneDescriptor object,每次访问实例的emp_no属性,都会为实例填充emp_no属性,并且查询一端的数据库
- 多端通过Salary(代指多端模型类)实例属性emp_no(代指将ForeignKey实例的变量),直接访问一端的Field
- salaries_set属性,ReverseManyToOneDescriptor object,每次访问实例的salaries_set属性,都会为实例填充salaries_set属性,并且查询多端的数据库
- 一端通过Employee(代指一端模型类)实例属性salaries_set(代指一端增加的属性)配合管理器objects查询集方法访问多端
- 查询出来的单个salaries对象,可以直接操作多端的Field
- 说白了,就是为一端和两多的每条记录,即每个实例添加了对应的实例属性,通过这个增加的实例属性,实现一端往多端、多端往一端查询到效果