六、模型层(ORM)重难点

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

django框架与mysql数据库 django框架orm_ORM

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

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

M是 mapping,是映射的意思。

映射:

类:sql语句 table表

类成员变量:table表中的字段类型和约束

类对象:sql表的表记录

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. 通过模型类对象提供的方法或属性完成数据表的增删改查操作

6.1 配置数据库连接

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

  1. 使用MySQL数据库首先需要安装驱动程序
pip3 install pymysql
  1. 在 django的工程同名子目录的 __init__.py文件中添加如下语句
from pymysql import install_as_MySQLdb
install_as_MySQLdb()  # 让pymysql以 MySQLDB的运行模式和django的ORM对接运行

作用是让django 的 ORM能以mysqldb的方式来调用pymysql。

  1. 修改DATABASES配置信息
DATABASES = [
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': '123',  # 数据库用户密码
        'NAME': 'student',  # 数据库名字
    }
]
  1. 在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',
        },
    }
}

6.2 定义模型类

定义模型类

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

接下来以学生管理为例进行演示。[系统大概 3-4个表,学生信息,课程信息,老师信息],创建子应用 student,注册子应用并引入子应用路由。

settings.py,代码:

INSTALLED_APPS = [
    # ...
    'student',
]

urls.py,总路由代码:

urlpatterns = [
    # 省略,如果前面有重复的路由,改动如下。
    path('student/', include('student.urls')),
]

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  # 显示的复数???名称??
        
    
    # 3. 自定义数据库操作方法
    def __str__(self):
        """定义每个数据对象的显示信息"""
        return "<User %s>" % self.name

6.2.1 数据库表名

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

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

6.2.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。

6.2.3 属性命名限制

  • 不能是python的保留关键字。
  • 不允许使用连续的2个下划线,这是由django的查询方式决定的。__是关键字来的,不能使用!!!!!
  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名 = models.字段类型(约束选项,verbose_name="注释")

6.2.4 字段类型

类型

说明

AutoField

自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为 id的自动增长属性

BooleanField

布尔字段,值为True或False

NullBooleanField

支持Null,True,False三种值

CharField

字符串,参数max_length表示最大字符个数,对应MySQL中的 varchar

TextField

大文本字段,一般打断文本(超过4000个字符)才使用。

IntegerField

整数

DecimalField

十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数,常用于表示分数和价格 Decimal(max_digits=7,decimal_places=2) ==> 99999.99 ~ 0.00

FloatField

浮点数

DateField

日期

参数auto_now表示每次保存对象时,自动设置该字段为当前时间。

参数auto_now_add表示当对象第一次被创建时自动设置当前。

参数auto_now_add和auto_now是相互排斥的,一起使用会发生错误。

TimeField

时间,参数同DateField

DateTimeField

日期时间,参数同DateFiled

FileField

上传文件字段,django在文件字段中内置了文件上传保存类,django可以通过模型的字段存储自动保存上传文件,但是,在数据库中本质上保存的仅仅是文件在项目中的存储路径!!

ImageField

继承于FileField,对上传的内容进行校验,确保是有效的图片。

6.2.5 约束选项

选项

说明

null

如果为True,表示允许为空,默认值是False,相当于python的None

blank

如果为True,则该字段允许为空白,默认值是False,相当于python的空字符串,“ ”

db_column

字段的名称,如果未指定,则使用属性的名称。

db_index

若值为True,则在表中会为此字段创建索引,默认值是False,相当于SQL语句中的key。

default

默认值,当不填写数据时,使用该选项的值作为数据的默认值。

primary_key

如果为True,则该字段会成为模型的主键,默认值是False,一般不用设置,系统默认设置。

unique

如果为True,则该字段在表中必须有唯一值,默认值是False,相当于SQL语句中的unique

注意:null是数据库范畴的概念,blank是表单验证范畴的。

6.2.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 medels


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

笔记本

2

3

茄子

1

  1. 当模型字段的 on_delete=CASCADE,删除蔬菜(id=1),则在外键cid=1的商品 id1和3就被删除。
  2. 当模型字段的 on_delete=PROTECT,删除蔬菜,mysql自动检查商品信息表,有没有cid=1的记录,有则提示必须先移除掉商品信息表中,id=1的所有记录以后才能删除蔬菜。
  3. 当模型字段的 on_delete=SET_NULL,删除蔬菜以后,对应商品信息表,cid=1的数据的 cid 全部被改成 cid=null
  4. 当模型字段的 on_delete=SET_DEFAULT,删除蔬菜以后,对应商品信息表,cid=1的数据记录的 cid 被设置默认值。

6.3 数据迁移

将模型类中定义表架构的代码转换成 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



-- 作业:
-- 1. 完成学生的创建,导入上面的数据。
-- 2. 使用原来学过的SQL语句,然后对上面导入的学生信息,完成增删查改的操作。
   -- 2.0 查询所有的学生信息(name,age)
       SELECT name,age FROM db_student
   -- 2.1 查询年龄在18-20之间的学生信息[name,age,sex]
       select name,age,sex from db_student where age >=18 and age <=20;
   -- 2.2 查询年龄在18-20之间的男生信息[name,age,sex]
       select name,age,if(gender=1,'男','女') as sex from db_student where age >=18 and age <=20 and gender=1;
   -- 2.3 查询401-406之间所有班级的学生信息[name,age,sex,class]
       select name,age,sex,class from db_student where class between 401 and 406;
   -- 2.4 查询401-406之间所有班级的总人数
       select count(1) as c from db_student where class between 401 and 406;
   -- 2.5 添加一个学生,男,刘德华,302班,17岁,"给我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00'
       insert into db_student (name,sex,class, age, description, created_time,updated_time) values ('刘德华',1,'302', 17, "给我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00');
   -- 2.6 修改刘德华的年龄,为19岁。
       update db_student set age=19 where name='刘德华';
   -- 2.7 刘德华毕业了,把他从学生表中删除
       delete  from db_student where name='刘德华';
   -- 2.8 找到所有学生中,年龄最小的5位同学和年龄最大的5位同学
       select * from db_student order by age asc limit 5;
       select * from db_student order by age desc limit 5;
   -- 2.9 【进阶】找到所有班级中人数超过4个人班级出来
       select class,count(id) as total from db_student group by class having total >= 4;
   -- 2.10【进阶】把上面2.8的要求重新做一遍,改成一条数据实现
       (select * from db_student order by age asc limit 5) union all (select * from db_student order by age desc limit 5);

6.4 数据库基本操作

6.4.1 添加记录

注意:::

"在讲django1.x版本时,增删改查的模型类对象前都有一个 models 此处应该:"
student = models.Student.objects.filter(pk=1).first()
student.delete()


"模型类前面多了一个 models  "

(1)save方法

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

student = Student(
	name="刘德华",
    age = 50,
    gender=1,
    birthday="1968-12-23"
)
student.save()
print(student.id)  # 判断是否新增有id

(2)create方法

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

student = Student.objects.create(
	name="jack",
    age=22,
    gender=1,
    birthday="2000-02-18"
)
print(student.id)

6.4.2 基础查询

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

queryset很重要

"""
l1 = [1,2,3]
准确的说 l1 叫做 列表对象!!
"""


"""
all函数: 返回的是一个queryset ==> 非常接近列表数据,里面的元素是模型类对象

about queryset: QuerySet(查询集)是一个类似于list的数据类型,里面的元素是统一类型,
如:模型类对象/字典...等

"""
(1)all()

查询所有对象,返回queryset对象,查询集,也称查询结果集,

QuerySet,表示从数据库中获取的对象集合。

# API 1. all函数: 返回的是一个queryset ==> 非常接近列表数据,里面的元素是模型类对象
# 查询所有学生
student_list = Student.objects.all()
print("student>>>:", student_list)
(2)first() last()
" : 返回的是模型类对象!!!!!"

# 查询第一个学生
stu = Student.objects.all()[0]
print(stu.name)
print(stu.age)

# print(student_list.name)  # 肯定是不可以的,内部的对象才可以用 .name

stu = Student.objects.first()
print(stu.name)
stu = Student.objects.last()
print(stu.name)
**(3)filter() **
"""
filter()方法:where语句  ==> 返回的是queryset查询集
"""
# 查询所有的女生
student_list = Student.objects.filter(gender=0)
student_list = Student.objects.filter(gender=0, age=22)  # 逻辑 与,
# student_list>>>: <QuerySet [<Student: 小雨 22>]>
print("student_list>>>:", student_list)
**(4)exclude() **
": 排除符合条件的记录 与 filter相反"

# 查询除了张三以外的所有学生记录
student_list = Student.objects.exclude(name="张三")
print("student_list>>>:", student_list)
(5)get()
"查询结果必须是有且只有一条符合条件的记录,所以返回的是一个查询到的模型类对象"


stu = Student.objects.get(gender=0)  # 结果多了报错
stu = Student.objects.get(gender=2)  # 没有结果报错
stu = Student.objects.get(id=5)  # stu>>>: 王五 22
print("stu>>>:", stu)
(6)order_by()
" 是queryset类型的一个内置方法,返回是一个queryset"

# 将所有学生按照年龄从高到底排序
student_list = Student.objects.all().order_by('-age')
student_list = Student.objects.all().order_by('-age', '-id')  # 默认升序,加 - 变成降序排列
print("student_list>>>:", student_list)
(7)count()
"返回int对象,也是queryset的内置方法"

# 查询学生个数
count = Student.objects.all().count()
print("count>>>:", count)
# 查询女生的个数
count1 = Student.objects.filter(gender=0).count()
print("count>>>:", count1)
(8)exist()
"也是queryset的内置方法,判断是否存在记录,返回的是一个布尔值"
# 相比于我们的判断,性能更高

# 查询学生表中是否存在记录
res = Student.objects.all().exists()
print("result>>>:", res)  # True
(9)values() 和 values_list()
"""
翻译成的是sql中的 select语句
依然得到queryset,但是里面不是模型类对象,而是字典!!!
"""

student_list = Student.objects.all().values("name", "age")
student_list = Student.objects.all().values_list("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [('张三', 18), ('李四', 33), ('王五', 22)]>

# values用的比较多!!!方便我们序列化
import json
print(json.dumps(list(student_list), ensure_ascii=False))

# 查询所有男生的姓名和年龄
student_list = Student.objects.filter(gender=1).values("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [{'name': '张三', 'age': 18}, {'name': '李四', 'age': 33}, {'name': '王五', 'age': 22}]>
**(10)distinct() **
"""
去重方法,queryset类型内置方法
已经包含id主键的时候根本不可能重复,所以也就没有去重的必要
当查询有重复可能的字段的时候,就有必要了。
"""
Student.objects.all().distinct()  # 无法去重

res = Student.objects.values("age").distinct()  # 此时可能重复,才能去重
print("result>>>:", res)

6.4.3 模糊查询

(1)模糊查询 之 contains

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

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

Student.objects.filter(name__contains="华")
(2)模式查询 之 startswith,endswith

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

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

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

(3)模糊查询 之 isnull

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

Student.objects.filter(description__isnull=True)
(4)模糊查询 之 比较查询

gt --- greater then

gte--- greater then equal

lt --- less then

lte --- less then equal

# gt lt gte lte
# 查询
stu1_list = Student.objects.filter(age__gt=30)  # 年龄大于30
stu2_list = Student.objects.filter(age__gte=30) # 年龄大于等于30
stu3_list = Student.objects.filter(age__lte=30) # 年龄小于等于30
stu4_list = Student.objects.filter(age__lt=30) # 年龄小于30
print(stu1_list)
print(stu2_list)
print(stu3_list)
print(stu4_list)
(5)模糊查询 之 range
# 查询年龄在20-30之间的学生
stu_list = Student.objects.filter(age__range=(20, 30))
print(stu_list)
(6)模糊查询 之 in
# 查询年龄是 22, 33的所有学生
stu_list = Student.objects.filter(age__in=[22, 30])
print(stu_list)
(7)模糊查询 之 日期查询

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

# 查询出生在1992年2月的学生
stu_list = Student.objects.filter(birthday__year=1992, birthday__month=2)
print(stu_list)

6.4.4 进阶查询

比较难,只是用的没有上面两种方式多

(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)


# 1. F函数/对象:两个属性比较,使用F对象
查询语文成绩大于数学成绩的学生
stu_list = Student.objects.filter(chinese_score__gt=F("math_score"))
print(stu_list)
(2)Q查询

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

# 逻辑判断
stu_list = Student.objects.filter(gender=0, age=19)  # 表示且
stu1_list = Student.objects.filter(gender=0).filter(age=25)  # 表示且

# 年龄大于等于30 或 性别为女
stu2_list = Student.objects.filter(Q(age__gte=30) | Q(gender=0))

# 年龄大于等于30 且 性别非女
stu2_list = Student.objects.filter(Q(age__gte=30) & ~Q(gender=0))
print(stu2_list)

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

语法如下:

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

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

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

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

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

(3)聚合函数 aggregate (重点!!!!!)

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

from django.db.models import Sum, Count, Avg, Max, Min

aggregate()  返回的是一个字典
# 查询所有学生的平均年龄
ret1 = Student.objects.aggregate(avg_age=Avg("age"))  # 也可以自定键名
ret2 = Student.objects.aggregate(Avg("age"))
print(ret1)  # {'avg_age': 24.1667}
print(ret2)  # {'age__avg': 24.1667}

# 语文最高的成绩
ret = Student.objects.aggregate(max_chi_score=Max("chinese_score"))
print(ret)  # {'max_chi_score': 100}
(4)分组函数 annotate() 重点!!!!
#annotate()  values在annotate前对应==> group by字段
# 查询不同性别学生的语文平均成绩
ret = Student.objects.values("gender").annotate(avg_chi=Avg("chinese_score"))
print(ret)
# <QuerySet [{'gender': 1, 'avg_chi': 95.3333}, {'gender': 0, 'avg_chi': 99.6667}]>

# 查询每个班级的数学平均成绩
ret = Student.objects.values("classmate").annotate(avg_math=Avg("math_score"))
print(ret)

# 思考:按照所有的group by,也就是主键分组,也就是每个学生单独一个组,
# 在单表之下,没有什么意义,只有在关联表中才有意义
res = Student.objects.all().annotate(avg_math=Avg("math_score"))
print(res)
(5)原生SQL
# 原生sql,得到的结果只能循环提取,不能有很多其他操作

ret = Student.objects.raw("select id,name,age from db_student")
# 这样执行获取的结果无法通过queryset进行操作读取,只能循环提取
print(ret, type(ret))
for item in ret:
    print(item, type(item), item.name)

6.4.5 修改记录

(1)使用save更新数据
# 方式1:基于模型对象 save操作  不推荐
stu = Student.objects.get(name="李四")
print(stu.name, stu.age)
stu.chinese_score = 88
stu.save()  # 将stu中所有属性都更新一边,性能很差!!!!


# save之所以能提供给我们添加数据的同时,还可以更新数据的原因?
# save会找到模型的字段的主键id的值,
# 主键id的值如果是none,则表示当前数据没有被数据库,所以save会自动变成添加操作
# 主键id有值,则表示当前数据在数据库中已经存在,所以save会自动变成更新数据操作
(2)update更新(推荐)

使用模型类.objects.filter().update(),会返回受影响的行数

# 方式2:queryset对象的update方法,queryset才能调用,
# update是全局更新,只要符合更新的条件,则全部更新,因此强烈建议加上条件!!!

stu_list = Student.objects.filter(name="李四").update(chinese_score=100, math_score=99)
stu2_list = Student.objects.filter(age__gt=30).update(chinese_score=101, math_score=101)
print(stu_list)
print(stu2_list)

# 将年龄小于30岁的学生,语文成绩降低20分
from django.db.models import F, Q
Student.objects.filter(age__lt=30).update(chinese_score=F("chinese_score")-20)

6.4.6 删除记录

删除记录也有两种方法

(1)模型类对象.delete
# 1:基于模型类对象删除,内置于模型类对象中的delete方法
pk2=2 == id=2
    #
(2)模型类.objects.filter().delete()
# 2:基于queryset删除,两个delete不同,内置于queryset中的delete方法
Student.objects.filter(chinese_score__lt=80).delete()

代码:

# 1. 先查询到数据模型对象。通过模型对象进行删除
# student = Student.objects.filter(pk=13).first()
# student.delete()


# 2. 直接删除
ret = Student.objects.filter(pk=100).delete()
print(ret)
# 务必写上条件,否则变成了清空表了。ret = Student.objects.filter().delete()

6.5 创建关联模型

-- student

id name  age class_name class_tutor class_num
1  rain  22  s12        jack        1
2  alvin 24  s12        jack        1


-- 一对多,一定是在多的表中创建关联字段


-- 多对多,多对多的关系的确立是通过创建第三张关系表来完成的


-- 一对一,垂直分割,绝对的一对一
# 相当于把一个表分割成两个及以上部分,


create table student(
    id int primary key auto_increment,
    name varchar(32),
    class_id int not null,
    foreign key (class_id) references class(id) on delete cascade
);

create table class(
    id int primary key auto_increment,
    name varchar(32)
);

create table course(
    id int primary key auto_increment,
    name varchar(32)
);

create table stu2course(
    id int primary key auto_increment,
    student_id int not null,
    course_id int not null,
    foreign key (student_id) references student(id),
    foreign key (course_id)  references course(id)
);

实例:我们来假定下面这些概念,字段和关系

  • 班级模型:班级名称,导师。
  • 课程模型:课程名称,讲师。
  • 学生模型:学生有 姓名,年龄,只有一个班级,所以和班级表是一对多的关系(one-to-many),选修了多个课程,所以和课程表是多对多的关系(many-to-many)
  • 学生详情:学生的家庭地址,手机号,邮箱等详细信息,和学生模型应该是一对一的关系(one-to-one)。

模型建立如下:

from django.db import models

# Create your models here.


class Clas(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="班级名称")
    
    class Meta:
        db_table = "db_class"
   

class Course(models.Model):
    
    title = models.CharField(max_length=32, verbose_name="课程名称")
    
    # 多对多时关系表的创建在哪个表中都可以
    # students = models.ManyToManyField("Student", db_table="db_stu2course")
    
    class Meta:
        db_table = "db_course"
  

class Student(models.Model):
    
    gender_choices = (
    	(0, '女'),
        (1, '男'),
        (2, '保密'),
    )
    
    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年龄", default=18)
    gender = models.SmallIntegerField(choices=gender_choices)
    
    # 建立一对多的关系:会在数据库中创建一个关联字段,名称叫做clas_id
    # 一对多的关系必须在多的表中建立,多对多则不用
    clas = models.ForeignKey(to="Clas", on_delete=models.CASCADE, db_constraint=False)
    # db_constraint=False表示不加约束,则仅仅创建一个clas_id的字段,没有外键约束
    
    # 建立多对多的关系:创建第三张关系表
    courses = models.ManyToManyField("Course", db_table="db_stu2course")
    # 这句话可以帮我们创建第三张关系表,里面有三个字段。
    
    # 一对一的关系:建立关联字段,在数据库中创建一个关联字段,名称叫做stu_detail_id
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)
    
    class Meta:
        db_table = "db_student"
  

class StudentDetail(models.Model):
    
    tel = models.CharField(max_length=11)
    addr = models.CharField(max_length=32)

    class Meta:
        db_table = "db_stu_detail"

6.6 关联添加

6.6.1 一对一 与 一对多

# 添加记录:一对多 与 一对一的关联属性

stu = Student.objects.create(name="张三", age=22, gender=1, clas_id=2, stu_detail_id=1,)
stu = Student.objects.create(name="李四", age=24, gender=1, clas_id=2, stu_detail_id=2,)
print(stu.name)
print(stu.age)
print(stu.clas_id)  # 2
print(stu.stu_detail_id)  # 2
print(stu.clas)  # 模型类对象,因为一个学生只能有一个班级类对象 Clas objects (2)

# 该学生的班级对象的名称
print(stu.clas.name)  # Clas.objects.get(pk=2)
# 计算机2班

stu = Student.objects.get(name="李四")
print(stu.name)  # 李四
print(stu.age)  # 24
print(stu.clas_id)  # 2
print(stu.clas.name)  # ORM可以帮调出来 计算机2班
print(stu.stu_detail.addr)  # 北京
print(stu.stu_detail.tel)  # 110

6.6.2 多对多

# ################多对多的关联记录的:增删改查!!!!

# 方式一:新添加的学生,绑定课程
stu = Student.objects.create(name="rain", age=28, gender=1, clas_id=3, stu_detail_id=3)
# stu是Student类的对象,所以就会有courses属性,stu.courses是第三张表的入口
# 添加多对多的数据,比如stu这个学生绑定两门课程,思修和逻辑学25
c1 = Course.objects.get(title="思修")
c2 = Course.objects.get(title="逻辑学")
stu.courses.add(c1, c2)  # 这句话ORM会找到关系表,添加记录c1和c2,stuid 3绑定两个课程2和5


# 添加多对多方式二:给查询出来的 张三绑定课程
stu = Student.objects.get(name="张三")
stu.courses.add(3, 4)  # 篮球和毛概,直接放课程id即可


# 添加多对多方式三
stu = Student.objects.get(name="李四")
stu.courses.add(*[1, 3])  # 学生绑定的课程不是写死的,后端处理完格式之后就是列表,所以就用此方法比较多


# 删除多对多记录
stu = Student.objects.get(name="李四")
stu.courses.remove(1)  # 删除李四的课程1近代史


# clear清除方法
stu = Student.objects.get(name="rain")
stu.courses.clear()


# set 重置方法,将原来已有的绑定去掉,重新绑定新的
stu = Student.objects.get(name="李四")
stu.courses.set([1, 2])


# all  查
# 查询李四所报课程的名称
stu = Student.objects.get(name="李四")
courses = stu.courses.all()  # 找到stu_id2的,查到course_id为1,2再去course中
print(courses)
# <QuerySet [<Course: Course object (1)>, <Course: Course object (2)>]>
# <QuerySet [<Course: 近代史>, <Course: 思修>]>  因为在course类中定义了str方法
print(courses.values("title"))

6.7 关联查询

6.7.1 基于对象查询(子查询)

"""
    正向查询:通过关联属性查询 属于正向查询,反之则成为反向查询

    反向查询

    基于对象的关联查询(子查询)
    :param request:
    :return:
"""

"# ####################一对多的关联,学生和班级"

# 查询张三所在的的班级名称(正向)
stu = Student.objects.get(name="张三")
print(stu.clas.name)  # 基于对象的关联字段

# 查询计算机2班有哪些学生 (反向)
clas = Clas.objects.get(name="计算机2班")
ret = clas.student_set.all()  # _setORM要求加的,表名这是一个集合变量
# 反向查询方式一: 按照表名小写_set
print(ret)  # <QuerySet [<Student: 张三>, <Student: 李四>]>

# 反向查询方式二::related_name
clas = Clas.objects.get(name="计算机2班")
print(clas.stu_list.all()) # <QuerySet [<Student: 张三>, <Student: 李四>]>
# stu_list是Student类中clas的related_name值,自定义的


"# ##########################  一对一的关联"

# 查询李四的手机号,正向查询
stu = Student.objects.get(name="李四")
print(stu.stu_detail.tel)

# 查询手机号为120的学生姓名和年龄,反向查询
stu_d = StudentDetail.objects.get(tel="120")

# 方式一:反向查询,表名小写
print(stu_d.student.name)
print(stu_d.student.age)

# 方式二:related_name
stu_d = StudentDetail.objects.get(tel=120)
print(stu_d.stu_det.name)
print(stu_d.stu_det.age)


"# ######################### 多对多关联查询"

# 查询张三所报课程的名称,正向
stu = Student.objects.get(name="张三")
print(stu.courses.all())

# 查询报名近代史的学生的姓名和年龄,反向
course = Course.objects.get(title="近代史")
# 反向查询方式一:表名小写_set
stu_lis = course.student_set.all()
print(stu_lis)

# 反向查询方式二:related_name
course = Course.objects.get(title="近代史")
print(course.stus_list.all().values("name", "age"))
  1. 正向查询按字段。
  2. 反向查询按表名小写,或者 related_name

6.7.2 基于双下划线查询(join查询)

"""
    基于双下划线 -- join
    :param request:
    :return:
"""
# ############# 一对多案例

# 查询张三的年龄
ret = Student.objects.filter(name="张三").values("age")
print(ret)  # <QuerySet [{'age': 22}]>

# 1. 查询年龄大于22的学生的姓名以及所在班级名称

# 方式一:Student作为基表,此时正向查询
ret = Student.objects.filter(age__gt=22).values("name", "clas__name")
print(ret)
# 方式二:Clas作为基表
ret2 = Clas.objects.filter(stu_list__age__gt=22).values("stu_list__name", "name")
print(ret2)


# 2. 查询计算机2班有哪些学生
ret = Clas.objects.filter(name="计算机2班").values("stu_list__name")
print(ret)


# #################### 多对多案例

# 3. 查询张三所报课程名称
ret = Student.objects.filter(name="张三").values("courses__title")
print(ret)

# 4. 查询选修了近代史这门课程的学生的姓名和年龄
ret = Course.objects.filter(title="近代史").values("stus_list__name","stus_list__age")
print(ret)

# 5. 一对一,查询李四的手机号
ret = Student.objects.filter(name="李四").values("stu_detail__tel")
print(ret)


# ################# 进阶

# 6. 查询手机号是120的学生的姓名和所在班级
# 方式1: 以StudentDetail作为基表,这种方式存在连续跨表的情况
ret = StudentDetail.objects.filter(tel="120").values("stu_det__name", "stu_det__clas__name")
print(ret)

# 方式2: 以Student作为基表就不存在连续跨表的问题
ret = Student.objects.filter(stu_detail__tel=110).values("name", "clas__name")
print(ret)

6.7.3 关联分组查询

# ############### 分组查询
from django.db.models import Avg, Count, Max,Min
ret = Student.objects.values("gender").annotate(c=Count("name"))
print(ret)  # <QuerySet [{'gender': 0, 'c': 1}, {'gender': 1, 'c': 3}]>

# 1. 查询每一个班级的名称以及学生个数
from django.db.models import Avg, Count, Max, Min
res = Clas.objects.values("name").annotate(c=Count("stu_list__name"))
print(res)
"""
<QuerySet [{'name': '网络工程1班', 'c': 1}, {'name': '网络工程2班', 'c': 0}, 
{'name': '计算机1班', 'c': 1}, {'name': '计算机2班', 'c': 2}, {'name': '软件1班', 'c': 0}]>
"""

# 2. 查询每一个学生的姓名以及选修课程的个数
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name").annotate(c=Count("courses__title"))
print(res)

# 2.1 查询每一个学生的姓名,年龄以及选修课程的个数
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name", "age").annotate(c=Count("courses__title"))
print(res)
# 更优解法
res1 = Student.objects.all()
res2 = Student.objects.all().annotate(c=Count("courses__title"))
print(res1)  # <QuerySet [<Student: 张三>, <Student: 李四>, <Student: rain>, <Student: 小雨>]>
print(res2)  # 和上面的结果看似一样,但是实际上对象内部有一个c属性
"所以可以这样:"
res3 = Student.objects.all().annotate(c=Count("courses__title")).values("name", "age", "gender", "c")
print(res3)


# 3. 查询每一个课程名称以及选修学生的个数
res = Course.objects.all().annotate(c=Count("stus_list__name")).values("title", "c")
print(res)


"where分组之前过滤,having分组之后过滤"

# 4. 查询选修课程个数大于 1 的学生姓名以及 选修课程个数
res = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name", "c")
print(res)


# 5. 查询每一个学生的姓名以及选修的课程个数 并 按照选修课程个数进行排序低到高
res = Student.objects.all().annotate(c=Count("courses__title")).order_by("c").values("name", "c")
print(res)

6.8 项目练习

学生管理系统