五、模型层( ORM

Django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中表的增删改查和创建等操作。当然,其内部是通过pymysql完成的,发送一些mysql语句。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

O是object,也就类对象的意思。


R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思。


M是mapping,是映射的意思。


ORM的优点


数据模型类都在一个地方定义,更容易更新和维护,也利于重用代码。


ORM 有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。


它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。


基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。


新手对于复杂业务容易写出性能不佳的 SQL,有了ORM不必编写复杂的SQL语句, 只需要通过操作模型对象即可同步修改数据表中的数据.


开发中应用ORM将来如果要切换数据库.只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】


ORM 也有缺点


ORM 库不是轻量级工具,需要花很多精力学习和设置,甚至不同的框架,会存在不同操作的ORM。


对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。


ORM操作数据库的性能要比使用原生的SQL差。


ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM并不表示当前项目不能使用别的数据库操作工具了。】


我们可以通过以下步骤来使用django的数据库操作


1. 配置数据库连接信息

2. 在models.py中定义模型类

3. 生成数据库迁移文件并执行迁文件[注意:数据迁移是一个独立的功能,这个功能在其他web框架未必和ORM一块的]

4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作

配置数据库链接

在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用 sqlite 数据库。

  1. 使用 MySQL 数据库首先需要安装驱动程序

pip install PyMySQL

  1. 在Django的工程同名子目录的 __init__ .py文件中添加如下语句
from pymysql import install_as_MySQLdb
install_as_MySQLdb() # 让pymysql以MySQLDB的运行模式和Django的ORM对接运行
  1. 作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
  2. 修改 DATABASES 配置信息
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': '123',  # 数据库用户密码
        'NAME': 'student'  # 数据库名字
}

在MySQL中创建数据库

create database student; # mysql8.0默认就是utf8mb4;
create database student default charset=utf8mb4; # mysql8.0之前的版本
  1. 注意3: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
LOGGING = {





    
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
}  

定义模型类

定义模型类

  • 模型类被定义在"子应用/models.py"文件中。
  • 模型类必须直接或者间接继承自django.db.models.Model类。

接下来以学生管理为例进行演示。[系统大概3-4表,学生信息,课程信息,老师信息]

在models.py 文件中定义模型类。

from django.db import models
from datetime import datetime
# 模型类必须要直接或者间接继承于 models.Model
class BaseModel(models.Model):
    """公共模型[公共方法和公共字段]"""
    # created_time = models.IntegerField(default=0, verbose_name="创建时间")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    # auto_now_add 当数据添加时设置当前时间为默认值
    # auto_now= 当数据添加/更新时, 设置当前时间为默认值
    updated_time = models.DateTimeField(auto_now=True)
    class Meta(object):
        abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型.
class Student(BaseModel):
    """Student模型类"""





    
    #1. 字段[数据库表字段对应]
    SEX_CHOICES = (
        (0,"女"),
        (1,"男"),
        (2,"保密"),
    # 字段名 = models.数据类型(约束选项1,约束选项2, verbose_name="注释")
    # SQL: id bigint primary_key auto_increment not null comment="主键",
    # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
    # SQL: name varchar(20) not null comment="姓名"
    # SQL: key(name),
    name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" )
    # SQL: age smallint not null comment="年龄"
    age = models.SmallIntegerField(verbose_name="年龄")
    # SQL: sex tinyint not null comment="性别"
    # sex = models.BooleanField(verbose_name="性别")
    sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2)
    # SQL: class varchar(5) not null comment="班级"
    # SQL: key(class)
    classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级")
    # SQL: description longtext default "" not null comment="个性签名"
    description = models.TextField(default="", verbose_name="个性签名")
    #2. 数据表结构信息
    class Meta:
        db_table = 'tb_student'  # 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: users_student
        verbose_name = '学生信息表'  # 在admin站点中显示的名称
        verbose_name_plural = verbose_name  # 显示的复数名称
        # 除此之外还可以设置联合索引 联合唯一索引等(注意可能会与unique=True发生冲突)
    #3. 自定义数据库操作方法
    def __str__(self):
        """定义每个数据对象的显示信息"""





    
        return "<User %s>" % self.name

真正的项目,一些登陆注册等的功能,对于用户表,后面会讲到用户认证组件Auth模块,我们一般在models.py里自定义一个class User()继承自带的Auth表。


(1) 数据库表名


模型类如果未指明表名db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名。


可通过db_table 指明数据库表名。


(2) 关于主键


django会为表创建自动增长的主键列,每个模型只能有一个主键列。


如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建自动增长的主键列。

class Student(models.Model):
    # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # 设置主键

默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。


(3) 属性命名限制


不能是python的保留关键字。


不允许使用连续的2个下划线,这是由django的查询方式决定的。__ 是关键字来的,不能使用!!!


定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

属性名 = models.字段类型(约束选项, verbose_name="注释")

(4)字段类型

image.png

注:手机号不要用 IntegerField 超过了范围。

(5)约束选项

image.png

verbose_name admin里显示的名字



注意:null是数据库范畴的概念,blank是表单验证范畴的(同理default也是)。更改一些class属性参数的时候可以不用重新数据库迁移 ,而修改数据库结构的需要重新迁移。


数据可范畴出错会发生报错,用户输入范畴不符合规定会提示不能为空,或者自增、默认值。


后加东西要是设置默认值


(6) 外键


在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:


CASCADE 级联,删除主表数据时连通一起删除外键表中数据


PROTECT 保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据


SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用


SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用


SET() 设置为特定值或者调用特定方法,例如:

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]
class UserModel(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET(get_sentinel_user),
    )




    

DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出 IntegrityError 异常

商品分类表

id category
1 蔬菜
2 电脑

商品信息表

id goods_name cid
1 冬瓜 1
2 华为笔记本A1 2
3 茄子 1

当模型字段的on_delete=CASCADE, 删除蔬菜(id=1),则在外键cid=1的商品id1和3就被删除。


当模型字段的on_delete=PROTECT,删除蔬菜,mysql自动检查商品信息表,有没有cid=1的记录,有则提示必须先移除掉商品信息表中,id=1的所有记录以后才能删除蔬菜。


当模型字段的on_delete=SET_NULL,删除蔬菜以后,对应商品信息表,cid=1的数据的cid全部被改成cid=null


当模型字段的on_delete=SET_DEFAULT,删除蔬菜以后,对应商品信息表,cid=1的数据记录的cid被被设置默认值。

数据库迁移

将模型类定义表架构的代码转换成SQL同步到数据库中,这个过程就是数据迁移。django中的数据迁移,就是一个类,这个类提供了一系列的终端命令,帮我们完成数据迁移的工作。


(1)生成迁移文件


所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件.


python manage.py makemigrations

(2)同步到数据库中


python manage.py migrate

补充:在django内部提供了一系列的功能,这些功能也会使用到数据库,所以在项目搭建以后第一次数据迁移的时候,会看到django项目中其他的数据表被创建了。其中就有一个django内置的admin站点管理。(这个后面会写)


# admin站点默认是开启状态的,我们可以通过 http://127.0.0.1:8000/admin

# 这个站点必须有个管理员账号登录,所以我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。

python manage.py createsuperuser

后续 :稍微补充下admin知识

(后台超级管理员看到的表需要注册,且一些自增字段不会显示)

这里我注册用户名root 密码root123 进入admin

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

这是因为我之前用auth组件设置过一个普通用户,浏览器中还保存着sessionid,这个普通用户显然不是管理员,无权登录。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

我们进入自己的root账户

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

在admin.py里注册表

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

记得在models.py里加上def__str__ 这样后台也就能显示相应的中文表示的对象了。

Django自带数据库可视化工具

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

数据库基本操作

脚本orm操作数据库

注意导入的顺序  不然会报错

(父级models类里有__str__和__repr__方法,也可以把__repr__重写)

(1)save方法

通过创建模型类对象,执行对象的save()方法保存到数据库中。

(2)create方法

通过模型类.objects.create()保存,返回生成的模型类对象。

1、基础查询

ORM中针对查询结果的限制,提供了一个查询集[QuerySet].这个QuerySet,是ORM中针对查询结果进行保存数据的一个类型,我们可以通过了解这个QuerySet进行使用,达到查询优化,或者限制查询结果数量的作用。

后面会详细讲QuerySet,orm进阶及优化搜索,到这里要先了解一些基本操作。

(1)all()

查询所有对象,返回queryset对象。查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。

(2)filter()

筛选条件相匹配的多个对象,返回queryset对象。

(3)get()

返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。

(4)first()、last()

分别为查询集的第一条记录和最后一条记录

(5)exclude()

筛选条件不匹配的对象,返回queryset对象。

(6)order_by()

对查询结果排序

(7)count()

查询集中对象的个数

结果其实与len(Student.objects.all())一样

(8)exists()

判断查询集中是否有数据,如果有则返回True,没有则返回False

(9)values()、values_list()

value()把结果集中的模型对象转换成字典,并可以设置转换的字段列表,达到减少内存损耗,提高性能


values_list(): 把结果集中的模型对象转换成元组(即没有键只有值),并可以设置转换的字段列表,达到减少内存损耗,提高性能


二者都返回QuerySet 只是里面的类型不同

(10)distinct()

从返回结果中剔除重复纪录。返回queryset。

(11)reverse()

对已经排序了的queryset进行反转 相当于order_by(’加个负号‘)

小总结:


返回QuerySet:

all()、filter()、exclude()、order_by()、values()、values_list()、reverse、distinct()


直接 Students.objects相当于.all() 可以在后面根对QuerySet的操作


返回对象:

get()、first()、last()


返回数字:

count()


返回布尔:

exist()

2、 模糊查询

(1)模糊查询之contains

说明:如果要包含%无需转义,直接写即可。

例:查询姓名包含'华'的学生。


Student.objects.filter(name__contains='华')

(2)模糊查询之startswith、endswith


例:查询姓名以'文'结尾的学生


Student.objects.filter(name__endswith='文')

以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.

(3)模糊查询之isnull

例:查询个性签名不为空的学生。

# 修改Student模型description属性允许设置为null,然后数据迁移
description = models.TextField(default=None, null=True, verbose_name="个性签名")
# 添加测试数据
NSERT INTO student.db_student (name, age, sex, class, description, created_time, updated_time) VALUES ('刘德华', 17, 1, '407', null, '2020-11-20 10:00:00.000000', '2020-11-20 10:00:00.000000');
# 代码操作
tudent_list = Student.objects.filter(description__isnull=True)




    

4)模糊查询之in


例:查询编号为1或3或5的学生


Student.objects.filter(id__in=[1, 3, 5])

(5)模糊查询之比较查询


gt 大于 (greater then)


gte 大于等于 (greater then equal)


lt 小于 (less then)


lte 小于等于 (less then equal)


例:查询编号大于3的学生


Student.objects.filter(id__gt=3)

(6)模糊查询之日期查询


year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。


例:查询2010年被添加到数据中的学生。


Student.objects.filter(born_date__year=1980)

例:查询2016年6月20日后添加的学生信息。

from django.utils import timezone as datetime
student_list = Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2016,6,21)).all()
print(student_list)

3、进阶查询

(1) F查询

之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。

语法如下:

"""F对象:2个字段的值比较"""
# 获取从添加数据以后被改动过数据的学生
from django.db.models import F
# SQL: select * from db_student where created_time=updated_time;
student_list = Student.objects.exclude(created_time=F("updated_time"))
print(student_list)

(2) Q查询

多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。

例:查询年龄大于20,并且编号小于30的学生。

Student.objects.filter(age__gt=20,id__lt=30)
Student.filter(age__gt=20).filter(id__lt=30)


如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符 ,Q对象被义在django.db.models中。

语法如下:

Q(属性名__运算符=值)
Q(属性名__运算符=值) | Q(属性名__运算符=值)

例:查询年龄小于19或者大于20的学生,使用Q对象如下。

from django.db.models import Q
student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()

Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或* *

例:查询年龄大于20,或编号小于30的学生,只能使用Q对象实现

Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))

Q对象左边可以使用~操作符,表示非not。但是工作中,我们只会使用Q对象进行或者的操作,只有多种嵌套复杂的查询条件才会使用&和~进行与和非得操作

例:查询编号不等于30的学生。

Student.objects.filter(~Q(pk=30))

(3)聚合查询

使用aggregate()过滤器调用聚合函数。聚合函数包括: Avg 平均, Count 数量, Max 最大, Min 最小, Sum 求和,被定义在django.db.models中。

例:查询学生的平均年龄。

from django.db.models import Sum,Count,Avg,Max,Min
Student.objects.aggregate(Avg('age'))

注意:aggregate的返回值是一个字典类型,格式如下:

{'属性名__聚合类小写':值} 如{'age__avg': 13.2}

使用count时一般不使用aggregate()过滤器。

例:查询学生总数。

Student.objects.count() # count函数的返回值是一个数字。

(4)分组查询

annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对

QuerySet对象.annotate()
# annotate() 进行分组统计,按前面select 的字段进行 group by
模型对象.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
# 查询指定模型, 按id分组 , 将course下的sid字段计数,返回结果是 name字段 和 course计数结果 
# SQL原生语句中分组之后可以使用having过滤,在django中并没有提供having对应的方法,但是可以使用filter对分组结果进行过滤
# 所以filter在annotate之前,表示where,在annotate之后代表having
# 同理,values在annotate之前,代表分组的字段,在annotate之后代表数据查询结果返回的字段

(5)原生查询

执行原生SQL语句,也可以直接跳过模型,才通用原生pymysql.

 ret = Student.objects.raw("SELECT id,name,age FROM db_student")  # student 可以是任意一个模型
 # 这样执行获取的结果无法通过QuerySet进行操作读取,只能循环提取
 print(ret,type(ret))
 for item in ret:
    print(item,type(item))
【Django学习笔记 - 9】:装饰器在类视图中的使用、中间件(开发中间件)、知识点小补充(子应用的集中管理)
【Django学习笔记 - 9】:装饰器在类视图中的使用、中间件(开发中间件)、知识点小补充(子应用的集中管理)