Django_ORM

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无法显示
    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()方法,只聚合,分组需要配合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
  • 说白了,就是为一端和两多的每条记录,即每个实例添加了对应的实例属性,通过这个增加的实例属性,实现一端往多端、多端往一端查询到效果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值