6 Django模型(ORM)

1 ORM简介

MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动

ORM是“对象-关系-映射”的简称。

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

image

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

2 配置数据库链接

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

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

    pip install PyMySQL
    
  2. 在Django的工程同名子目录的__init__.py文件中添加如下语句

    from pymysql import install_as_MySQLdb
    install_as_MySQLdb() # 让pymysql以MySQLDB的运行模式和Django的ORM对接运行
    

    作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。

  3. 修改DATABASES配置信息

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 数据库主机
            'PORT': 3306,  # 数据库端口
            'USER': 'root',  # 数据库用户名
            'PASSWORD': '123',  # 数据库用户密码
            'NAME': 'student'  # 数据库名字
        }
    }
    
  4. 在MySQL中创建数据库

    create database student; # mysql8.0默认就是utf8mb4;
    create database student default charset=utf8mb4; # mysql8.0之前的版本
    

    也可以借助navicat客户端工具创建

  5. 注意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',
            },
        }
    }  
    

3 定义模型类

定义模型类

  • 模型类被定义在"子应用/models.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,"保密"),
	)
    该变量在模板中使用:
    {{ stu.get_sex_display }}

    # 字段名 = 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),
    # 如果db_index=True 则代表着为此字段设置索引。
    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)
    # db_column="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  # 显示的复数名称
        ordering= ['-age']  # 指定排序

	#3. 自定义数据库操作方法
	def __str__(self):
		"""定义每个数据对象的显示信息"""
		return "<User %s>" % self.name

3-1 数据库表名

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

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


3-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-3 属性命名限制

  • 不能是python的保留关键字。

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

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

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

3-4 字段类型

类型 说明
AutoField 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性
自定义一个主键:
my_id=models.AutoField(primary_key=True)
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 浮点数,必须提供两个参数:
参数 描述
max_digits 总位数(不包括小数点和符号)
decimal_places 小数位数
举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:
models.FloatField(..., max_digits=5, decimal_places=2)
DateField 日期
参数Argument描述
参数auto_now表示每次保存对象时,自动设置该字段为当前时间。
参数auto_now_add表示当对象第一次被创建时自动设置当前时间,通常表示对象创建时间。
参数auto_now_add和auto_now是相互排斥的,一起使用会发生错误。
TimeField 时间,参数同DateField
DateTimeField 日期时间,参数同DateField
FileField 上传文件字段,django在文件字段中内置了文件上传保存类, django可以通过模型的字段存储自动保存上传文件, 但是, 在数据库中本质上保存的仅仅是文件在项目中的存储路径!!
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片
两个可选参数:height_field和width_field,
如果提供这两个参数,则图片将按提供的高度和宽度规格保存.
EmailField 一个带有检查Email合法性的 CharField,不接受 max_length 参数.
URLField 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应).
admin 用一个 文本框表示该字段保存的数据(一个单行编辑框)
SlugField "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs
XMLField 一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径.
FilePathField 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的.
参数 描述
path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目.
Example: "/home/images".
match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名.
IPAddressField 一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
CommaSepara
tedIntegerField
用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.

3-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
choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,
而且这个选择框的选项就是choices 中的选项。

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


3-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
  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被被设置默认值。

选项 说明
ManyToManyField 多对多关系
to 设置关联表
through='' 关系表的第三张表名称
through_fields=('','') 哪两张的多对多表
ForeignKey 一对多,多对一外键
to 设置关联表
to_field 关联表要关联的键名,默认为关联表中的id,可以不写
on_delete 当删除关联表中的数据的时候,从表做什么行为,
CASCADE 当关联表中数据删除的时候,外键所在表中的数据也会被删除
SET_NULL 当关联表中数据删除的时候,外键所在表中的外键设置为空
SET_DEFAULT 当关联表中数据删除的时候,外键所在表中的外键设置一个默认值
PROTECT 关联保护,当关联表的数据被删除的时候,报异常,
DO_NOTHING 当关联表中数据删除的时候,外键所在的表不做任何事情
SET 删除关联数据
1.使用用户认证组件的前提:要用到Django的auth_user表
  - 自己创建一个用户UserInfo表,在表中加入自己想要的字段
  - 继承AbstractUser类,那么AbstractUser类中的字段都可以使用
  - 自己的UserInfo表覆盖了Django的auth_user表
  - 在用用户认证组件时,就用自己的UserInfo表即可
  - 在settings.py文件中添加一个健值:
    AUTH_USER_MODEL = "app01.UserInfo"

2.当user表跟blog表绑定一对一关系时,那么这两张表无论哪一张表对category表
  绑定一对多关系,都可以查询到,且绑定一个即可。

3.建立一对多或者多对一的外键时,在多的表中建立Foreignkey

4.自关联,可在属性中自己
   # 子评论(自管理,可以用"self")
   models.ForeignKey("self", null=True, on_delete=models.CASCADE)

5.多对多的键,在Django数据库生成中,会自动生成 xxx_id 的命名形式;
  对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名

6.每张表创建时,如果没有写主键值,Django会自动生成主键值

4 数据迁移

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


4-1 生成迁移文件

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

python manage.py makemigrations
python3 manage.py makemigrations

4-2 同步到数据库中

python manage.py migrate
python3 manage.py migrate

注意1:确保配置文件中的INSTALLED_APPS中写入我们创建的app名称

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    "book"
]

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

# admin站点默认是开启状态的,我们可以通过http://127.0.0.1:8000/admin
# 这个站点必须有个管理员账号登录,所以我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。
python manage.py createsuperuser

image

image

image


4-3 添加新的字段

如果在Django数据库迁移过后,还想添加新的字段,那么就要在每个字段中添加一个默认值;在执行以下命令

classmate = models.CharField(default='')
python manage.py makemigrations
python manage.py migrate

5 数据库基本操作

5-1 添加记录

1 save方法

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

```python
student = Student(
    name="刘德华",
    age=17,
    sex=True,
    clasmate=301,
    description="一手忘情水"
)
student.save()
print(student.id) # 判断是否新增有ID
```

2 create方法

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

```python
student = Student.objects.create(
    name="赵本山",
    age=50,
    sex=True,
    class_number=301,
    description="一段小品"
)
print(student.pk)  # 判断是否新增有ID
```

5-2 基础查询

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

可以在models.py文件中添加__str__方法,方便与查看:

def __str__(self):
    return self.name+" "+str(self.age)

1 all()

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

```python
students = Student.objects.all()
print("students:",students)
# <QuerySet [<Student: Student object (1)>, <Student: Student object (2)>]>

student = Student.objects.all()[0]
print(student)  # <Student: Student object (1)>
print(student.name)  # 查询object(1)中的name属性
```

2 filter()

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

```python
# 查询所有的女生
students = Student.objects.filter(sex=0)
# 查询所有女生并且年龄等于18的
students1 = Student.objects.filter(sex=0, age=18)
```

3 get()

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

```python 
student = Student.objects.get(pk=1)
print(student)
print(student.description)
get使用过程中的注意点:get是根据条件返回多个结果或者没有结果,都会报错
try:
    student = Student.objects.get(name="刘德华")
    print(student)
    print(student.description)
except Student.MultipleObjectsReturned:
    print("查询得到多个结果!")
except Student.DoesNotExist:
    print("查询结果不存在!")
```

4 first()、last()

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

```python 
# 没有结果返回none,如果有多个结果,则返回object模型对象
students = Student.objects.all()
# print(students.name)
print(students[0].name)
stu01 = Student.objects.first()
stu02 = Student.objects.last()

print(stu01)  # <Student: Student object (1)>
print(stu01.name)  # 张三
print(stu02)  # <Student: Student object (3)>
print(stu02.name)  # 小茜
```

5 exclude()

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

```python
# 查询张三以外的所有的学生
students = Student.objects.exclude(name="张三")
# 查询张三并且年龄38以外的所有的学生
students = Student.objects.exclude(name="张三", age=38)
```

6 order_by()

对查询结果排序,是queryset类型的一个内置方法

```python 
# order_by("字段")  # 按指定字段正序显示,相当于 asc  从小到大
# order_by("-字段") # 按字段倒序排列,相当于 desc 从大到小
# order_by("第一排序","第二排序",...)

# 查询所有的男学生按年龄从高到低展示
# students = Student.objects.all().order_by("-age","-id")
students = Student.objects.filter(sex=1).order_by("-age", "-id")
print(students)
```

7 count()

查询集中对象的个数,是queryset类型的一个内置方法

```python 
# 查询所有男生的个数
count = Student.objects.filter(sex=1).count()
print(count)
```

8 exists()

判断查询集中是否有数据,如果有则返回True,没有则返回False,是queryset类型的一个内置方法

````python
# 查询Student表中是否存在学生记录
print(Student.objects.exists())
````

9 values()、values_list()

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

     <QuerySet [{'key': 'value'},{'key': 'value'}]>
    
  • values_list(): 把结果集中的模型对象转换成列表,并可以设置转换的字段列表(元祖),达到减少内存损耗,提高性能

     <QuerySet [('k1', 'k1'),('k2', 'k2')]>
    
    # values 把查询结果中模型对象转换成字典
    student_list = Student.objects.filter(classmate="301")
    student_list = student_list.order_by("-age")
    student_list = student_list.filter(sex=1)
    ret1 = student_list.values() # 默认把所有字段全部转换并返回
    ret2 = student_list.values("id","name","age") # 可以通过参数设置要转换的字段并返回
    ret3 = student_list.values_list() # 默认把所有字段全部转换并返回
    ret4 = student_list.values_list("id","name","age") # 可以通过参数设置要转换的字段并返回
    print(ret4)
    return JsonResponse({},safe=False)
    

    可以转成python可用数据类型:

    import json
    stu_list = Student.object.all().values('name','age')
    print(stu_list)
    #  <QuerySet [{'key': 'value'},{'key': 'value'}]>
    print(json.dumps(list(stu_list), ensure_ascii=False))
    # [{'key': 'value'},{'key': 'value'}]
    

10 distinct()

从返回结果中剔除重复纪录。是queryset类型的一个内置方法,返回queryset。

```python
# 查询所有学生出现过的年龄
print(Student.objects.values("age").distinct())
```

5-3 模糊查询

1 模糊查询之contains

说明:如果要包含%无需转义,直接写即可。
例:查询姓名包含'华'的学生。

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

2 模糊查询之startswith、endswith

例:查询姓名以'文'开头的学生startswith

Student.objects.filter(name__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');

# 代码操作(查询为空的True,不为空的False)
tudent_list = Student.objects.filter(description__isnull=False)

4 模糊查询之in

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

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

5 模糊查询之range

列:查询年龄在20-30之间的学生

Student.objects.filter(age__range=(20, 30))

6 模糊查询之比较查询

  • gt 大于 (greater then)
  • gte 大于等于 (greater then equal)
  • lt 小于 (less then)
  • lte 小于等于 (less then equal)

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

Student.objects.filter(id__gt=3)

7 模糊查询之日期查询

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)

5-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.filter(chinese__gt=F("math_score"))
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))

实战案例应用1:

from django.db.models import Q

search_list = ["name__startswith", "email_startswith", ]
search_value = "文"
conn = Q()
conn.connecotr = "OR"

if search_value:
    for item in search_list:
        conn.children.append(item, search_value)

queryset = Student.objects.filter(conn)

# 模糊查询
过滤出数据库中,name和email字段中“文”的数据信息

实战案例应用2:

from django.db.models import Q
def customer_list(request):
    keyword = request.GET.get('keyword', '').strip()
    con = Q()
    if keyword:
        con.connector = "OR"
        con.children.append(('username__contains', keyword))
        con.children.append(('mobile__contains', keyword))
        con.children.append(('level__title__contains', keyword))

    queryset = models.Customer.objects.filter(con).filter(active=1).select_related('level', 'creator')  # select_related()主动跨表

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

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

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

注意2:如果想自己设定key值:

Student.objects.aggregate(avg_age=Avg('age'))

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

例:查询学生总数。

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

4 分组查询

QuerySet对象.annotate()
# annotate() 进行分组统计,按前面select 的字段进行 group by
# annotate() 返回值依然是 queryset对象字典类型,增加了分组统计后的键值对
# values() 对应是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之后代表数据查询结果返回的字段
# 查询不同性别学生的语文平均成绩
stu = Student.objects.values('sex').annotate(sex_avg=Avg('chinese'))

# 查询每个班级的数据成绩
stu = Student.objects.values('classmate').annotate(Avg('math_score'))
print(stu)

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

5-5 修改记录

1 使用save更新数据

基于模型类对象save,save更新值时会把这条记录的所有属性值都修改。

student = Student.objects.filter(name='刘德华').first()
print(student)
student.age = 19
student.classmate = "303"
# save之所以能提供给我们添加数据的同时,还可以更新数据的原因?
# save会找到模型的字段的主键id的值,
# 主键id的值如果是none,则表示当前数据没有被数据库,所以save会自动变成添加操作
# 主键id有值,则表示当前数据在数据库中已经存在,所以save会自动变成更新数据操作
student.save()

2 update更新(推荐)

使用模型类.objects.filter().update(),会返回受影响的行数,只有queryset对象可以调用update()

# update是全局更新,只要符合更新的条件,则全部更新,因此强烈建议加上条件!!!
student = Student.objects.filter(name="赵华",age=22).update(name="刘芙蓉",sex=True)
print(student)

# 将年龄小于30岁的学生的语文成绩降低20分
Student.objects.filter(age__lt=30).update(chinese=F('chinese')-20)

5-6 删除记录

1 模型类对象.delete

student = Student.objects.get(id=13)
student.delete()

2 模型类.objects.filter().delete()

Student.objects.filter(id=14).delete() 

代码:

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

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

6 关联添加

模型

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):
    name = models.CharField(max_length=32, unique=True, verbose_name="课程名称")

    class Meta:
        db_table = "db_course"


class Student(models.Model):
    sex_choices = (
        (0, "女"),
        (1, "男"),
        (2, "保密"),
    )

    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年龄", default=18)  # 年龄
    sex = models.SmallIntegerField(choices=sex_choices)
    birthday = models.DateField()

    # 一对多
    # on_delete= 关联关系的设置
    # models.CASCADE    删除主键以后, 对应的外键所在数据也被删除
    # models.DO_NOTHING 删除主键以后, 对应的外键不做任何修改
    # db_constraint=False  不做外键约束,反之亦然
    # to_field = None  自己设置关联的字段(默认关联主键)
    # 反向查找字段 related_name=""
    # clas 在数据库创建一个关联字段:clas_id
    clas = models.ForeignKey(to="Clas", on_delete=models.CASCADE, db_constraint=False,related_name="student_list")

    # 多对多
    # 建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    # db_table="db_student2course" 设定表名称
    courses = models.ManyToManyField("Course", db_table="db_student2course", related_name="students")

    # 一对一,使用同一对多
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE, related_name="stu")

    class Meta:
        db_table = "db_student"

    def __str__(self):
        return self.name


class StudentDetail(models.Model):
    tel = models.CharField(max_length=11)
    email = models.EmailField()
    description = models.TextField(null=True, verbose_name="个性签名")

    class Meta:
        db_table = "db_student_detail"

6-1 一对多与一对一

 stu = Student.objects.create(name="王五", age=23, sex=1, birthday="1991-11-12", clas_id=9, stu_detail_id=6)
  print(stu.name)
  print(stu.age)
  print(stu.sex)
  print(stu.clas_id)  # 6
  print(stu.stu_detail_id)  # 5
  print(stu.clas)  # object模型类对象
  print(stu.stu_detail)  # object模型类对象

  # 查询stu这个学生的班级名称
  print(stu.clas.name)
  # 查询stu这个学生的手机号
  print(stu.stu_detail.tel)

6-2 多对多

 # stu = Student.objects.create(name="rain", age=33, sex=1, birthday="1996-11-12", clas_id=9, stu_detail_id=7)

    # (1) 添加多对多的数据

    # 添加多对多方式1
    c1 = Course.objects.get(title="思修")
    c2 = Course.objects.get(title="逻辑学")
    stu.courses.add(c1,c2)

    # 添加多对多方式2
    stu = Student.objects.get(name="张三")
    stu.courses.add(5,7)

    # 添加多对多方式3
    stu = Student.objects.get(name="李四")
    stu.courses.add(*[6,7])

    # (2) 删除多对多记录

   stu = Student.objects.get(name="李四")
   stu.courses.remove(7) # 添加的3种方式都可以用

    # (3) 清空多对多记录:clear方法

    stu = Student.objects.get(name="rain")
    stu.courses.clear()

    # (4) 重置多对多记录:set方法

    stu = Student.objects.get(name="李四")
    stu.courses.set([5,8])

    # (5) 多对多记录查询: all
    # 查询李四所报课程的名称
    stu = Student.objects.get(name="李四")
    courses = stu.courses.all()
    courses = stu.courses.all().values("title")
    print(courses)  # <QuerySet [<Course: Course object (5)>, <Course: Course object (8)>]>


7 关联查询

7-1 基于对象查询(子查询)

    # **********************************  一对多查询
    # 查询张三所在班级的名称
    # stu = Student.objects.get(name="张三")
    # print(stu.clas.name)

    # 查询计算机科学与技术2班有哪些学生

    # clas = Clas.objects.get(name="计算机科学与技术2班")
    # 反向查询方式1:
    # ret = clas.student_set.all()  # 反向查询按表名小写_set
    # print(ret) # <QuerySet [<Student: 张三>, <Student: 李四>]>
    # 反向查询方2:

    # print(clas.student_list.all())  # <QuerySet [<Student: 张三>, <Student: 李四>]>

    # **********************************  一对一查询

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

    # 查询110手机号的学生姓名和年龄

    # stu_detail = StudentDetail.objects.get(tel="110")
    # 反向查询方式1: 表名小写
    # print(stu_detail.student.name)
    # print(stu_detail.student.age)
    # 反向查询方式2: related_name
    # print(stu_detail.stu.name)
    # print(stu_detail.stu.age)

    # **********************************  多对多查询

    # 查询张三所报课程的名称

    # stu = Student.objects.get(name="张三")
    # print(stu.courses.all())  # QuerySet [<Course: 近代史>, <Course: 篮球>]>

    # 查询选修了近代史这门课程学生的姓名和年龄
    # course = Course.objects.get(title="近代史")
    # 反向查询方式1: 表名小写_set
    # print(course.student_set.all()) # <QuerySet [<Student: 张三>, <Student: 李四>]>

    # 反向查询方式2:related_name
    # print(course.students.all())
    # print(course.students.all().values("name","age")) # <QuerySet [{'name': '张三', 'age': 22}, {'name': '李四', 'age': 24}]>

(1)正向查询按字段:通过关联属性查询属于正向查询,反之则称为反向查询

(2)反向查询按表名小写或者related_name


7-2 基于双下划线查询(join查询)

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

    # (1) 查询年龄大于22的学生的姓名以及所在名称班级
    # select db_student.name,db_class.name from db_student inner join db_class on db_student.clas_id = db_class.id where db_student.age>22;

    # 方式1 : Student作为基表
    ret = Student.objects.filter(age__gt=22).values("name","clas__name")
    print(ret)
    # 方式2 :Clas表作为基表
    ret = Clas.objects.filter(student_list__age__gt=22).values("student_list__name","name")
    print(ret)

    # (2) 查询计算机科学与技术2班有哪些学生

    ret = Clas.objects.filter(name="计算机科学与技术2班").values("student_list__name")
    print(ret)  #<QuerySet [{'student_list__name': '张三'}, {'student_list__name': '李四'}]>

    # (3) 查询张三所报课程的名称

    ret = Student.objects.filter(name="张三").values("courses__title")
    print(ret) # <QuerySet [{'courses__title': '近代史'}, {'courses__title': '篮球'}]>

    # (4) 查询选修了近代史这门课程学生的姓名和年龄

    ret = Course.objects.filter(title="近代史").values("students__name","students__age")
    print(ret) # <QuerySet [{'students__name': '张三', 'students__age': 22}, {'students__name': '李四', 'students__age': 24}]>

    # (5) 查询李四的手机号

    ret = Student.objects.filter(name='李四').values("stu_detail__tel")
    print(ret)  # <QuerySet [{'stu_detail__tel': '911'}]>


    # (6) 查询手机号是110的学生的姓名和所在班级名称

    # 方式1
    ret = StudentDetail.objects.filter(tel="110").values("stu__name","stu__clas__name")
    print(ret) # <QuerySet [{'stu__name': '张三', 'stu__clas__name': '计算机科学与技术2班'}]>

    # 方式2:
    ret = Student.objects.filter(stu_detail__tel="110").values("name","clas__name")
    print(ret) # <QuerySet [{'name': '张三', 'clas__name': '计算机科学与技术2班'}]>

(1)正向关联按关联字段

(2)反向按表名小写或related_name


7-3 关联分组查询

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

# (1)查询每一个班级的名称以及学生个数

ret = Clas.objects.values("name").annotate(c = Count("student_list__name"))
print(ret) # <QuerySet [{'name': '网络工程1班', 'c': 0}, {'name': '网络工程2班', 'c': 0}, {'name': '计算机科学与技术1班', 'c': 0}, {'name': '计算机科学与技术2班', 'c': 1}, {'name': '软件1班', 'c': 3}]>

# (2)查询每一个学生的姓名,年龄以及选修课程的个数

ret = Student.objects.values("name","age").annotate(c=Count("courses__title"))
print(ret) # <QuerySet [{'name': 'rain', 'c': 0}, {'name': '张三', 'c': 2}, {'name': '李四', 'c': 2}, {'name': '王五', 'c': 0}]>

ret = Student.objects.all().annotate(c=Count("courses__title")).values("name","age","sex","c")
# .all() 按照这个学生的每一个字段进行group by

# (3) 每一个课程名称以及选修学生的个数
ret = Course.objects.all().annotate(c = Count("students__name")).values("title","c")
print(ret) # <QuerySet [{'title': '近代史', 'c': 2}, {'title': '思修', 'c': 0}, {'title': '篮球', 'c': 1}, {'title': '逻辑学', 'c': 1}, {'title': '轮滑', 'c': 0}]>

rel = Course.objects.values('title').annotate(c=Count('students__pk'))
print(rel)

# (4)  查询选修课程个数大于1的学生姓名以及选修课程个数
ret = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name","c")
print(ret) # <QuerySet [{'name': '张三', 'c': 2}, {'name': '李四', 'c': 2}]>

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

8 多表操作

一对多:

确定表关系是一对多:在多对应的多表中创建关联字段 publish_id

多对多:

确定表关系是多对多:创建第三张关系表:id book_id auther_id

一对一:

确定表关系是一对一:在两张表中的任意一张表中建立关联字段 + Unique约束

注意:

建立关联字段是为了方便查询,建立约束条件为了防误删除


8-1 创建模型

实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
模型建立如下:

from django.db import models
# Create your models here.

from django.db import models


# Create your models here.


# 作者表
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    # 与AuthorDetail建立一对一的关系
    authorDetail = models.OneToOneField(to="AuthorDetail",
                                        to_field="nid",
                                        on_delete=models.CASCADE)


# 作者详情表
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=64)


# 出版社表
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()


# 书籍
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # 与Publish建立一对多的关系,外键字段建立在多的一方
    publish = models.ForeignKey(to="Publish",
                                to_field="nid",
                                on_delete=models.CASCADE)
    """
    sql语句:
    publish_id int,
    foreign key (publish_id) references publish(id)
    """

    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    authors = models.ManyToManyField(to='Author', )
    """
    sql语句:
    create table book2author(
        id int primary key auto_increment,
        book_id int,
        author_id int,
        foreign key (book_id) references book(id),
        foreign key (author_id) references author(id)
    """

注意事项:

  • 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称  
  • id 字段是自动添加的
  • 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
  • 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
  • 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
  • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

8-2 添加表纪录

image

一对多添加:

方式1:
   publish_obj=Publish.objects.get(nid=1)
   book_obj=Book.objects.create(title="红楼梦",publishDate="2012-12-12",price=100,publish=publish_obj)

方式2(推荐):
   book_obj=Book.objects.create(title="西游记",publishDate="2012-12-12",price=100,publish_id=1)

核心:book_obj.publish与book_obj.publish_id是什么?

print(book_obj.publish) 
# 打印与这本书籍关联的出版社对象 Publish object (1) 可以.属性;也可以在相应class中定义函数__str__才可以返回可查看值

print(book_obj.publish_id) 
# 打印出的是 ID值 1 

查询:

# 查询西游记对应的邮箱
ret = Book.objects.filter(title="西游记")[0].publish.email
print(ret)

多对多添加:

image

# 多对多的关系
ret = Book.objects.create(title="金1瓶梅", price=200, publishDate="2012-2-2", publish_id=1)

egon = Author.objects.get(nid=2)
alex = Author.objects.get(nid=1)

# 绑定多对多关系的API接口 为书籍绑定的做作者对象
# 方式一:
ret.authors.add(egon,alex)
# 方式二:
ret.authors.add(2,1)
ret.authors.add(*[2,1])

# 解除多对多关系的API接口
# 方法一:单一解除
book = Book.objects.filter(nid=5).first()
book.authors.remove(1,2)  # 解除5-1&2 的关系
book.authors.remove(*[1,2])  # 解除5-1&2 的关系

# # 方法二:全部解除
book.authors.clear()  # 清空id = 5 的全部关系
book_obj.authors.set()  #先清空再设置

# 查询nid=5的这本书关联的所有作者名字
print(book.authors.all())
# <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>
print(book.authors.all().values("name"))
# < QuerySet[{'name': 'alex'}, {'name': 'egon'}] >

return HttpResponse('ok')
posted @ 2022-08-10 16:30  角角边  Views(294)  Comments(0)    收藏  举报