13

Django 模型层

 3 years ago
source link: http://www.cnblogs.com/Yunya-Cnblogs/p/13694386.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Django ORM

Django 模型层的功能就是与数据库打交道,其中最主要的框架为 ORM

ORM 为对象关系映射,说白了就是将 Python 中的类映射成数据表,将表中每一条记录映射成类的实例对象,将对象属性映射成表中的字段。

如果用原生的 SQL 语句结合 pymysql 来对数据库进行操作无疑是非常繁琐的,但是 Django 提供了非常强大的 ORM 框架来对数据库进行操作,在增删改查方面都有非常大的提升,学会使用 ORM 十分的必要。

注意:尽管 ORM 十分方便,但是也请不要过分依赖它从而忘记原生 SQL 命令。

ORM 作为 Django 中最难的一章基础知识点,应该是很多初学者的第一道门槛。

那么在学习 ORM 之前,我想写一些我的心得体会, ORM 的操作很方便,但是有些设计比较反人类,你可以使用原生 SQL 进行代替。

条条大路通罗马,不一定非要在一棵树上吊死。

准备工作

原生语句

如果想在操作 ORM 时还能看到原生的 SQL 语句,请在 settings.py 中添加上以下代码

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

测试脚本

Django 中允许对 py 文件做单独的测试,而不用启动 Django 项目,这是非常方便的。

注意:所有测试中的代码都必须在 if "__name__" == "__main__": 下进行,这意味着你的 from xx import xx 不能放在顶行

from django.test import TestCase

# Create your tests here.
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # 如果在pycharm中,这两句可以省略

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project01.settings")

    import django
    django.setup()

    # 测试代码均在下面进行
    from app01 import models

链接MYSQL

Django 中默认使用的数据库是 sqlit3 ,所以我们需要在配置文件中对其实行修改。

大体分为两个步骤

1.修改默认链接为 MySQL

2.链接声明,即声明链接 MySQL 的模块为 pymysql (默认是 MySQLdb

修改链接

打开项目全局文件夹下的 settings.py ,找到以下代码进行注释

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',  # 默认链接sqlite3
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

现在我们就要进行手动配置了,参照如下代码

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 默认链接sqlite3
        'NAME': 'db1', 			# 你的数据库名称
        'USER': 'root', 		# 你的登录用户名称
        'PASSWORD': '123',		# 你的登录密码,如果没有留空即可
        'HOST':	'localhost', 	# 链接地址
        'PORT': '3306',			# MySQL服务端端口号
        'CHARSET': 'utf8mb4',	# 默认字符编码
    }
}

现在,你的 Django 会抛出一个异常,不管他,直接进入下一个步骤

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'.
Did you install mysqlclient or MySQL-python?

链接声明

由于我们要将默认链接 MySQL 的模块从 MySQLdb 修改为 pymysql ,所以你要先安装 pymysql 模块

pip install pymysql

安装完成后打开项目全局文件夹下的 __init__ (实际上任意 APP 下的 __init__ 都可以),添加代码如下:

import pymysql
pymysql.install_as_MySQLdb()

现在我们的 Django 就可以使用 MySQL 进行连接了。

数据库操作

ORM 为对象关系映射,类就相当于数据表,属性就相当于字段。

因此我们有两条很常见的命令用于操作:

python manage.py makemigrations  # 创建模型映射表
python manage.py migrate # 同步模型表至数据库中

这两条命令在对数据库字段、数据表结构进行修改时都需要重新进行。

单表创建

在项目的 APP 中,打开 models.py ,开始创建表(该文件下可以建立一个表,也可以建立多个表)。

注意: ORM 创建表时如果不指定主键字段,将会默认创建一个名为 id 的主键字段。并且我们在使用时可以使用 pk 来代指主键

from django.db import models

class User(models.Model): # 必须继承
    # 自动创建 id 的主键
    username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)

接下来要运行创建模型映射表的命令,以及运行数据库同步命令。

python manage.py makemigrations  # 创建模型映射表
python manage.py migrate # 同步模型表至数据库中

当创建模型映射表命令运行完成后,会发现 APP 下的 migrations 文件夹中多一一个文件,文件中就存放的刚刚建立好的模型表。

当数据库同步命令执行完成后, MySQL 中才会真正的创建出一张真实的物理表。

模型表信息:

# project01项目/app01/migrations

# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2020-09-12 18:18
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('username', models.CharField(db_index=True, max_length=16, verbose_name='用户名')),
                ('age', models.IntegerField()),
                ('gender', models.BooleanField(choices=[[0, 'male'], [1, 'famale']], default=0)),
            ],
        ),
    ]

物理表(注意:物理表的命名会以 APP 名称开始):

desc app01_user;

+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int(11)     | NO   | PRI | NULL    | auto_increment |
| username | varchar(16) | NO   | MUL | NULL    |                |
| age      | int(11)     | NO   |     | NULL    |                |
| gender   | tinyint(1)  | NO   |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

字段操作

所有关于字段的增删改查都直接操纵对象属性即可,这里要提一下增加字段。

如果需要新增一个字段,而数据表中又有一些数据,此时 Django 会提醒你为原本的老数据设置一个默认值。

它会提供给你2个选项,选项1:立即为所有老数据设置默认值。选项2:放弃本次新增字段的操作,重新新增字段并设置默认值(也可以设置 null=True

示例如下:我们现在表中创建一条信息。

from app01 import models

res = models.User.objects.create(
	username= "云崖",
	age=18,
	gender=0,
)

print(res)

然后修改其字段,新增一个注册时间。

from django.db import models

class User(models.Model): # 必须继承
    # 自动创建 id 的主键
    username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
    register_time = models.DateField(auto_now_add=True)

在执行修改模型映射表的结构命令时,会让你做出选择。此时我们选择1,并且给定一个默认值就好。

PS D:\project\project01> python manage.py makemigrations
You are trying to add the field 'register_time' with 'auto_now_add=True' to user without a default; the database needs something to populate existing rows.

 1) Provide a one-off default now (will be set on all existing rows)  # 1.新增一个默认值
 2) Quit, and let me add a default in models.py # 2.退出,自己手动添加default参数设置默认值
Select an option: 1
Please enter the default value now, as valid Python
You can accept the default 'timezone.now' by pressing 'Enter' or you can provide another value.
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
[default: timezone.now] >>> "2020-01-28"  # 选择新增,给定一个时间
Migrations for 'app01':
  app01\migrations\0002_user_register_time.py
    - Add field register_time to user
PS D:\project\project01>

记得最后运行数据库同步

python manage.py migrate

返回信息

其实在创建数据表时,我们都会为其加上 __str__ 方法。

这是为了方便查询操作时看到那一条数据。

如下,如果不加的话返回结果是 QuerySet 中套上列表+对象,十分不利于查看信息。

from app01 import models

res = models.User.objects.all()

print(res)  # <QuerySet [<User: User object>]>

如果我们加上 __str__ 方法,那么就方便我们进行查看,到底查询的有那些记录。

(只要不对模型表本身结构做出修改,都不用重新执行模型表和物理表的两条命令)

from django.db import models

class User(models.Model): # 必须继承
    # 自动创建 id 的主键
    username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
    register_time = models.DateField(auto_now_add=True)

    def __str__(self) -> str:
        return "对象-%s"%self.username 
        # 每一个记录对象都是User的实例化对象,所以我们返回一下self的username即可

现在再来进行查询

from app01 import models

res = models.User.objects.all()

print(res)  # <QuerySet [<User: 对象-云崖>]>

记录操作

新增记录

新增记录语法有两种,均可使用。

models.表名.objects.create(col='xxx', col='xxx')  # 注意,返回值是创建好的对象记录本身

obj = models.表名(col='xxx', col='xxx')
obj.save()

每次的创建,都会返回一个创建成功的新对象。一个实例对象中包含字段及字段数据,换而言之就是一条记录。

示例演示:

from app01 import models

# 方式一:推荐使用
obj1 = models.User.objects.create(
    # 主键自动添加,AUTOFILED
    username="及时雨",
    age=21,
    gender=0,
    # register_time 创建时自动添加
)

# 方式二:
obj2 = models.User(
    # 主键自动添加,AUTOFILED
    username="玉麒麟",
    age=22,
    gender=0,
    # register_time 创建时自动添加
)

obj2.save()

print(obj1, obj2)  # 对象-及时雨 对象-玉麒麟

修改记录

修改记录有两种方式,推荐使用第一种,因为第二种修改会将该条记录的所有字段都进行修改,无论数据是否有更新。

models.表名.objects.filter(col='旧数据').update(col='新数据') 
# filter相当于where,不可以使用get,因为单一对象没有update方法,只有QuerySet对象才有。

obj = models.表名.objects.get(col='旧数据') # get() 只获取单个对象
obj.col = '新数据'
obj.save()

对于方式1而言,它可能修改多条记录。所以每次修改后,都会返回一个 int 类型的数字,这代表受到影响的行数

示例演示:

from app01 import models

# 方式一:推荐使用
num1 = models.User.objects.filter(pk=1).update(username="宋公明")

# 方式二:
obj = models.User.objects.get(pk=2)
obj.username="卢俊义"
obj.save()

print(num1)  # 1

删除记录

语法介绍:

models.表名.objects.filter(col='xxx').delete()
# 删除指定条件的数据 filter相当于where,不可以使用get,因为单一对象没有delete方法,只有QuerySet对象才有。

返回值是一个元组,包含删除成功的行数。

示例如下

from app01 import models

num_obj = models.User.objects.filter(pk=2).delete()

print(num_obj)  # (1, {'app01.User': 1})

批量增加

如果要插入的数据量很大,则可以使用 bulk_create 来进行批量插入。

它比单纯的 create 效率更高。

book_list = []
for i in range(100000):
	book_obj = models.Book(title="第%s本书"%i)
	book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)

单表查询

truncate app01_user;

INSERT INTO app01_user(username, age, gender,register_time) VALUES
    ("云崖",18,0,now()),
    ("及时雨",21,0,now()),
    ("玉麒麟",22,0,now()),
    ("智多星",21,0,now()),
    ("入云龙",23,0,now()),
    ("大刀",22,0,now());

QuerySET

在查询时候,一定要区分开 QuerySet 对象与单条记录对象的区别。

比如:我们的单条记录对象中保存有字段名等数据,均可进行 . 出来。它的格式应该是这样的: obj[col1,col2,col3]

QuerySet 对象是一个对象集合体,中间包含很多单条记录对象,它的格式是: QuerySet<[obj1,obj2,obj3]>

不同的对象有不同的方法,单条记录对象中能 . 出字段,而 QuerySet 对象中能点出 filter()/values() 等方法。所以一定要区分开。

QuerySet 集合对象:暂时可以理解为一个列表,里面可以包含很多记录对象,但是不支持负向的索引取值,并且 Django 不太希望你使用 index 进行取值,而应该使用 first() 以及 last() 进行取出单条记录对象。

基本查询

语法介绍:

models.表名.objects.all() # 拿到当前模型类映射表中的所有记录,返回QuerySet集合对象
models.表名.objects.filter(col='xxx')	# 相当于whlie条件过滤,可拿到多条,返回QuerySet集合对象
models.表名.objects.get(col='xxx')	# 返回单个记录对象。注意区分与QuerySet的区别

示例演示:(仔细看结果,区分 QuerySet 与单个对象的区别)

from app01 import models

all_queryset = models.User.objects.all()

filter_queryset = models.User.objects.filter(pk__gt=3)

obj = models.User.objects.get(pk=1)

print(all_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-玉麒麟>, <User: 对象-智多星>, <User: 对象-入云龙>, <User: 对象-大刀>]>
print(filter_queryset)
# <QuerySet [<User: 对象-智多星>, <User: 对象-入云龙>, <User: 对象-大刀>]>
print(obj)
# 对象-云崖

filter过滤

filter() 相当于 where 条件,那么在其中可以进行过滤操作

过滤使用 __ 双下划线进行操作。

注意:在 filter() 中,所有的条件都是 AND 关系

条件 说明 filter(col1=xxx,col2=xxx) 查询符合多个条件的记录 col__lt = xxx 字段数据小于xxx的记录 col__lte = xxx 字段数据小于等于xxx的记录 col_gt = xxx 字段数据大于xxx的记录 col_gte = xxx 字段数据大于xxx的记录 col_in = [x,y,z] 字段数据在[x,y,z]中的记录 col_range = [1,5] 字段数据在1-5范围内的记录 col_startswith = "xxx" 字段数据以"xxx"开头的记录,区分大小写 col_istartswith = "xxx" 字段数据以"xxx"开头的记录,不区分大小写 col_endswith = "xxx" 字段数据以"xxx"结尾的记录,区分大小写 col_iendswith = "xxx" 字段数据以"xxx"结尾的记录,不区分大小写 col__contains = ”xxx" 字段数据包含"xxx"的记录,区分大小写 col__icontains = ”xxx" 字段数据包含"xxx"的记录,不区分大小写 col__year = 2020 日期字段在2020年中的记录 col__year__lt = 2020 日期字段在2020年之后的记录 col__date__gte = datetime.date(2017, 1, 1) 日期字段在2017年1月1日之后的记录 col__month = 3 日期字段在3月的记录 col__day = 3 日期字段在3天的记录 col__hour = 3 日期字段在3小时的记录 col__minute = 3 日期字段在3分钟的记录 col__second = 3 日期字段在3秒钟的记录
User.objects.filter(pk__lt=2)  # 查找id小于2的对象

User.objects.filter(pk__lte=2) # 查找id小于等于2的对象

User.objects.filter(pk__gt=2)  # 查找id大于2的对象

User.objects.filter(pk__gte=2) # 查找id大于等于2的对象

User.objects.filter(pk__in=[1,2,5]) # 查找id为1,2,5的对象。

User.objects.filter(pk__range=[1,5])  # 查找id为1-5之间的对象。(左右都为闭区间)

User.objects.filter(name__contains="shawn")  # 查找name中包含shawn的对象。类似于正则/%shawn%/

User.objects.filter(name__icontains="shawn")  # 查找name中包含shawn的对象。(忽略大小写)

User.objects.filter(name__startswith="shawn")  # 查找name以shawn开头的对象 

User.objects.filter(name__istartswith="shawn")  # 查找name以shawn开头的对象(忽略大小写)

User.objects.filter(name__endswith="shawn")  # 查找name以shawn结尾的对象 

User.objects.filter(name__iendswith="shawn")  # 查找name以shawn结尾的对象 (忽略大小写)

User.objects.exclude(name__contains="shawn")  # 查找name不包含shawn的所有对象。
xx__date类型字段可以根据年月日进行过滤

User.objects.filter(birthday__year=2012)   # 查找出生在2012年的对象

User.objects.filter(birthday__year__gte=2012)  # 查找出生在2012年之后对象

User.objects.filter(birthday__date__gte=datetime.date(2017, 1, 1))  # 查找在2017,1,1之后出生的对象

User.objects.filter(birthday__month=3)  查找出生在3月份的对象

User.objects.filter(birthday__week_day=3) 

User.objects.filter(birthday__day=3)

User.objects.filter(birthday__hour=3)

User.objects.filter(birthday__minute=3)

User.objects.filter(birthday__second=3)

常用操作

下面将例举一些常用操作。

单词 查询操作 描述 all() models.表名.objects.all() <QuerySet [obj obj obj]>,相当于查询该表所有记录。返回值相当于列表套对象 filter() models.表名.objects.filter(col=xxx) <QuerySet [obj obj objJ]>,过滤条件可通过逗号分割,等同于where查询。返回值相当于列表套对象 get() models.表名.objects.get(col=xxx) obj,单条记录,即一个类的实例化对象,直接返回对象 以下方法多为配合第一个或第二个方法使用 values() models.表名.objects.values("col") <QerySet [{col,x1},{col,x2}]>,返回记录中所有字段的值。返回值相当于列表套字典 values_list() models.表名.objects.values_list("col") <QerySet [(x1),(x2)]>,返回记录中所有字段的值。返回值相当于列表套元组 distinct() models.表名.objects.values("col").distinct() <QuerySet [{col,x1},{col,x2}]>,对指定字段去重。返回记录中所有字段的值。返回值相当于列表套字典 order_by() models.类名.objects.order_by("-col") <QuerySet [obj obj objJ]>,对指定字段排序,如直接写col是升序,-col则是降序。 reverse() models.类名.objects.order_by("-col").reverse() <QuerySet [obj obj obj]>,前置条件必须有order_by(col),对其进行反转操作 exclude() models.表名.objects.exclude(col="xxx") <QuerySet [obj obj obj]>,除开col=xxx的记录,其他记录都拿 count() models.表名.objects.values("col").count() int,返回该字段记录的个数 exists() models.表名.objects.filter(col=“data”).exists() bool,返回该记录是否存在 first() models.表名.objects.first() obj,单条记录,取QuerySet中第一条记录 last() models.表名.objects.last() obj,单条记录,取QuerySet中最后一条记录 注意:获取all()后想配合其他的一些方法,可直接使用models.类型.object.其他方法()。这等同于all()

示例演示如下:

、
from app01 import models

values_queryset_dict = models.User.objects.values("username")
print(values_queryset_dict)
# <QuerySet [{'username': '云崖'}, {'username': '入云龙'}, {'username': '及时雨'}, {'username': '大刀'}, {'username': '智多星'}, {'username': '玉麒麟'}]>

values_list_queryset_tuple = models.User.objects.values_list("username")
print(values_list_queryset_tuple)
# <QuerySet [('云崖',), ('入云龙',), ('及时雨',), ('大刀',), ('智多星',), ('玉麒麟',)]>

distict_queryset = models.User.objects.values("age").distinct()
print(distict_queryset)
# <QuerySet [{'age': 18}, {'age': 21}, {'age': 22}, {'age': 23}]>

order_by_queryset = models.User.objects.order_by("-age")
print(order_by_queryset)
# <QuerySet [<User: 对象-入云龙>, <User: 对象-玉麒麟>, <User: 对象-大刀>, <User: 对象-及时雨>, <User: 对象-智多星>, <User: 对象-云崖>]>

reverse_queryset = models.User.objects.order_by("-age").reverse()
print(reverse_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-智多星>, <User: 对象-玉麒麟>, <User: 对象-大刀>, <User: 对象-入云龙>]>

exclude_queryset = models.User.objects.exclude(pk__gt=3)
print(exclude_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-玉麒麟>]>

num_pk = models.User.objects.values("pk").count()
print(num_pk)
# 6

bool_res = models.User.objects.filter(username="云崖").exists()
print(bool_res)
# True

first_obj = models.User.objects.first()
print(first_obj)
# 对象-云崖

last_obj = models.User.objects.last()
print(last_obj)
# 对象-大刀

多表关系

Django 中如果要实现多表查询,必须要建立外键,因为 Django 的多表查询是建立在外键关系基础之上的。

但是如果使用原生的 SQL 命令时我并不推荐使用外键做关联,因为这样会使得表与表之间的耦合度增加。

值得一提的是 Django 中多对多关系创建则只需要两张表即可,因为它会自动创建第三张表(当然也可以手动创建,有两种手动创建的方式,下面也会进行介绍)。

一对一

作者与作者详情是一对一

关键字: OneToOneField

注意事项如下:

1.如不指定关联字段,自动关联主键 id 字段

2.关联过后从表的关联字段会自动加上 _id 后缀

3.一对一关系建立在任何一方均可,但是建议建立在查询使用多的一方,如作者一方

class Author(models.Model):
    name = models.CharField(max_length=16, default="0")
    age = models.IntegerField()
    # 作者与作者详情一对一
    author_detail = models.OneToOneField(
        to="Authordetail", on_delete=models.CASCADE)

    def __str__(self) -> str:
        return "对象-%s" % self.name


class Authordetail(models.Model):
    phone = models.BigIntegerField()
    # 手机号,用BigIntegerField,

    def __str__(self) -> str:
        return "对象-%s" % self.phone

查看一对一关系(注意,外键上加了一个 UNIQUE 约束):

desc app01_author;

+------------------+-------------+------+-----+---------+----------------+
| Field            | Type        | Null | Key | Default | Extra          |
+------------------+-------------+------+-----+---------+----------------+
| id               | int(11)     | NO   | PRI | NULL    | auto_increment |
| age              | int(11)     | NO   |     | NULL    |                |
| author_detail_id | int(11)     | NO   | UNI | NULL    |                |
| name             | varchar(16) | NO   |     | NULL    |                |
+------------------+-------------+------+-----+---------+----------------+

一对多

书籍与出版社是一对多

关键字: ForeignKey

注意事项如下:

1.如不指定关联字段,自动关联主键 id 字段

2.关联过后从表的关联字段会自动加上 _id 后缀

3.一对多关系的 fk 应该建立在多的一方

class Publish(models.Model):
    name = models.CharField(max_length=16)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    def __str__(self) -> str:
        return "对象-%s" % self.name


class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    
    def __str__(self) -> str:
        return "对象-%s" % self.title

查看一对多关系:

decs app01_book;

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| title        | varchar(16)  | NO   |     | NULL    |                |
| price        | decimal(5,2) | NO   |     | NULL    |                |
| publish_date | date         | NO   |     | NULL    |                |
| publish_id   | int(11)      | NO   | MUL | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

多对多

书籍与作者是多对多

关键字: ManyToManyFiled

1. Django 使用 ManyToManyFiled 会自动创建第三张表,而该字段就相当于指向第三张表,并且第三张表默认关联其他两张表的 pk 字段

2.多对多关系建立在任何一方均可,但是建议建立在查询使用多的一方

3.多对多没有级联操作

class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    # 书籍和出版社是一对多/多对一
    authors = models.ManyToManyField("Author")
    # 书籍与作者是多对多关系

    def __str__(self) -> str:
        return "对象-%s" % self.title

查看多对多关系,即第三张表:

desc app01_book_authors;

+-----------+---------+------+-----+---------+----------------+
| Field     | Type    | Null | Key | Default | Extra          |
+-----------+---------+------+-----+---------+----------------+
| id        | int(11) | NO   | PRI | NULL    | auto_increment |
| book_id   | int(11) | NO   | MUL | NULL    |                |
| author_id | int(11) | NO   | MUL | NULL    |                |
+-----------+---------+------+-----+---------+----------------+

自关联

自关联是一个很特殊的关系,如评论表。

一个文章下可以有很多评论,这些评论又分为根评论和子评论,那么这个时候就可以使用自关联建立关系。

#评论表
class Comment(models.Model):
    #评论的内容字段
    content=models.CharField(max_length=255)
    #评论的发布时间
    push_time=models.DateTimeField(auto_now_add=True)
    #关联父评论的id,可以为空,一定要设置null=True
    pcomment = models.ForeignKey(to='self',null=True) 
    def __str__(self):
        return self.content

级联操作

一对一,一对多关系均可使用级联操作。

Django 中,默认会开启级联更新,我们可自行指定级联删除 on_delete 的操作方式。

级联删除选项 描述 models.CASCADE 删除主表数据时,从表关联数据也将进行删除 models.SET 删除主表数据时,与之关联的值设置为指定值,设置:models.SET(值),或者运行一个可执行对象(函数)。 models.SET_NUL 删除主表数据时,从表与之关联的值设置为null(前提FK字段需要设置为可空) models.SET_DEFAULT 删除主表数据时,从表与之关联的值设置为默认值(前提FK字段需要设置默认值) models.DO_NOTHING 删除主表数据时,如果从表中有与之关联的数据,引发错误IntegrityError models.PROTECT 删除主表数据时,如果从表中有与之关联的数据,引发错误ProtectedError

操作演示:

publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
    # 书籍和出版社是一对多/多对一

全部代码

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=16,default="0")
    age = models.IntegerField()
    # 作者与作者详情一对一
    author_detail = models.OneToOneField(to="Authordetail",on_delete=models.CASCADE)

    def __str__(self) -> str:
        return "对象-%s" % self.name


class Authordetail(models.Model):
    phone = models.BigIntegerField()
    # 手机号,用BigIntegerField,

    def __str__(self) -> str:
        return "对象-%s" % self.phone


class Publish(models.Model):
    name = models.CharField(max_length=16)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    def __str__(self) -> str:
        return "对象-%s" % self.name


class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
    # 书籍和出版社是一对多/多对一
    authors = models.ManyToManyField("Author")
    # 书籍与作者是多对多关系

    def __str__(self) -> str:
        return "对象-%s" % self.title

多表操作

一对多

新增记录

对于增加操作而言有两种

1.使用 fk_id 进行增加,添加另一张表的 id 号(因为外键会自动添加 _id 前缀,所以我们可以直接使用)

2.使用 fk 进行增加,添加另一张表的具体记录对象

第一种:
	models.表名.objects.create(
		col = xxx,
		col = xxx,
		外键_id = int,
	)
第二种:
	# 先获取对象
	obj = models.表名.objects.get(col=xxx)
	models.表名.objects.create(
		col = xxx,
		col = xxx,
		外键 = obj,
	)

示例操作:

from app01 import models

# 添加出版社
models.Publish.objects.create(
    # id自动为1
    name="北京出版社",
    addr="北京市海淀区",
    email="[email protected]",
)

models.Publish.objects.create(
    # id自动为2
    name="上海出版社",
    addr="上海市蒲东区",
    email="[email protected]",
)

# 第一种:直接使用fk_id,添加id号
models.Book.objects.create(
    title="Django入门",
    price=99.50,
    # auto_now_add,不用填
    publish_id=1,  # 填入出版设id号
)

# 第二种,使用fk,添加对象
# 2.1 先获取出版社对象
pub_obj = models.Publish.objects.get(pk=1)
    # 2.2 添加对象
    models.Book.objects.create(
    title="CSS攻略",
    price=81.50,
    # auto_now_add,不用填
    publish=pub_obj, # 填入出版社对象
)

修改记录

修改记录和增加记录一样,都是有两种

1.使用 fk_id 进行修改,改为另一张表的 id 号(因为外键会自动添加 _id 前缀,所以我们可以直接使用)

2.使用 fk 进行修改,改为另一张表的具体记录对象

第一种:
	models.表名.objects.filter(col=xxx).update(外键_id=int)
第二种:
	# 先获取对象
	obj = models.表名.objects.get(col=xxx)
	models.表名.objects.filter(col=xxx).update(外键=obj)

示例操作:

from app01 import models

# 第一种,直接使用fk_id  # 将第一本书改为上海出版社 # 注意,这里只能使用filter,因为QuerySet对象才具有update方法
models.Book.objects.filter(pk=1).update(publish_id=2,)

# 第二种,获取出版设对象,使用fk进行修改 # 将第二本书改为北京出版社
# 2.1获取出版设对象
pub_obj = models.Publish.objects.get(pk=2)
# 2.2使用fk进行修改
models.Book.objects.filter(pk=2).update(publish=pub_obj)  #  注意,这里只能使用filter,因为QuerySet对象才具有update方法

删除记录

如果删除一个出版社对象,与其关联的所有书籍都将会被级联删除。

models.Publish.objects.filter(pk=1).delete()

多对多

多对多的外键,相当于第三张表,必须要拿到第三张表才能进行操作。

这一点只要能理解,那么对多对多的操作就会十分便捷。

增加记录

增加记录也是两种操作,拿到第三张表后可以增加对象,也可以增加 id

注意:可以一次添加一个对象或 fk_id ,也可以添加多个

第一种:
   obj = models.表名.objects.get(col=xxx)
   obj.外键.add(id_1, id_2) # 可以增加一个,也可以增加多个
第二种:
	# 先获取对象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三张表
	obj = models.表名.objects.get(col=xxx)
	obj.外键.add(obj1, obj2) # 可以增加一个,也可以增加多个

# obj.外键 就是第三张表

示例操作:

from app01 import models

# 创建作者详细
a1 = models.Authordetail.objects.create(
	phone=12345678911
)

a2 = models.Authordetail.objects.create(
	phone=15196317676
)

# 创建作者
models.Author.objects.create(
    name="云崖",
    age=18,
    author_detail=a1,  # a1是创建的记录对象本身
)

models.Author.objects.create(
    name="杰克",
    age=18,
    author_detail=a2,  # a2是创建的记录对象本身
)

# 为书籍添加作者
# 方式1 先拿到具体对象,通过外键字段拿到第三张表,添加作者的id
# 拿到书籍,通过书籍外键字段拿到第三张表(必须是具体对象,不是QuerySet)
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.add(1, 2)  # 为第一部书添加两个作者

# 方式2 先拿到作者的对象,再拿到第三张表,添加作者对象
author_1 = models.Author.objects.get(name="云崖")
author_2 = models.Author.objects.get(name="杰克")

# 拿到书籍,通过书籍外键字段拿到第三张表(必须是具体对象,不是QuerySet)
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.add(author_1, author_2)  # 为第一部书添加两个作者

修改记录

修改记录也是拿到第三张表进行修改,可以修改对象,也可以修改 id

注意:可以一次设置一个对象或 id ,也可以添加多个,但是要使用 [] 进行包裹

第一种:
    obj = models.表名.objects.get(col=xxx)
    book_obj.authors.set([1]) # 注意[]包裹,可以设置一个,也可以设置多个
第二种:
	# 先获取对象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三张表
	obj = models.表名.objects.get(col=xxx)
	obj.表名.set([obj1, obj2]) # 注意[]包裹,可以设置一个,也可以设置多个
	
# obj.外键 就是第三张表

示例操作:

from app01 import models

# 用id进行设置
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.set([1])  # 拿到第三张表,放入作者的id进行设置。注意[]包裹,可以设置一个,也可以设置多个

# 用对象进行设置
author_obj = models.Author.objects.first()
# 先获取作者对象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.set([author_obj])  # 拿出第三张表,放入作者对象,注意[]包裹,可以设置一个,也可以设置多个

删除记录

直接拿到第三张表进行删除即可。可以删除对象,也可以删除 id

注意:可以一次性删除单个,也可以一次性删除多个

第一种:
	obj = models.表名.objects.get(col=xxx)
    obj.外键.remove(id_1,id_2) # 可以删除一个,也可以设置多个
第二种:
	# 先获取对象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三张表
	obj = models.表名.objects.get(col=xxx)
	obj.外键.remove(obj1, obj2) # 可以删除一个,也可以设置多个
	
# obj.外键 就是第三张表

示例操作:

from app01 import models

# 用id进行删除
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.remove(1) # 拿到第三张表,放入作者的id进行删除。可以删除一个,也可以删除多个

# 用对象进行删除
author_obj = models.Author.objects.last()
# 先获取作者对象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.remove(author_obj) # 拿出第三张表,放入作者对象,可以删除一个,也可以删除多个

清空记录

拿到第三张表,执行 clear() 则是清空操作。

book_obj = models.Book.objects.get(pk=1) # 拿到具体对象
book_obj.authors.clear() # 删除其所有作者

多表查询

数据准备

还是用多表操作中的结构,数据有一些不同。

书籍与出版社是一对多

书籍与作者是多对多

作者与作者详情是一对一

select * from app01_publish;  -- 出版社

+----+-----------------+-----------------------------+--------------------+
| id | name            | addr                        | email              |
+----+-----------------+-----------------------------+--------------------+
|  1 | 北京出版社      | 北京市海淀区                | [email protected]  |
|  2 | 上海出版社      | 上海浦东区                  | [email protected] |
|  3 | 西藏出版社      | 乌鲁木齐其其格乐区          | [email protected]   |
+----+-----------------+-----------------------------+--------------------+

select * from app01_author;  -- 作者

+----+--------------+-----+------------------+
| id | name         | age | author_detail_id |
+----+--------------+-----+------------------+
|  1 | 云崖         |  18 |                1 |
|  2 | 浪里白条     |  17 |                2 |
|  3 | 及时雨       |  21 |                3 |
|  4 | 玉麒麟       |  22 |                4 |
|  5 | 入云龙       |  21 |                5 |
+----+--------------+-----+------------------+

select * from app01_book;  -- 书籍

+----+-------------------+--------+--------------+------------+
| id | title             | price  | publish_date | publish_id |
+----+-------------------+--------+--------------+------------+
|  1 | Django精讲        |  99.23 | 2020-09-11   |          1 |
|  2 | HTML入门          |  78.34 | 2020-09-07   |          2 |
|  3 | CSS入门           | 128.00 | 2020-07-15   |          3 |
|  4 | Js精讲            | 152.00 | 2020-06-09   |          1 |
|  5 | Python入门        |  18.00 | 2020-08-12   |          1 |
|  6 | PHP全套           |  73.53 | 2020-09-15   |          2 |
|  7 | GO入门到精通      | 166.00 | 2020-07-14   |          3 |
|  8 | JAVA入门          | 123.00 | 2020-04-22   |          2 |
|  9 | C语言入门         |  19.00 | 2020-02-03   |          3 |
| 10 | FLASK源码分析     | 225.00 | 2019-11-06   |          1 |
+----+-------------------+--------+--------------+------------+


select * from app01_book_authors;  -- 作者书籍关系

+----+---------+-----------+
| id | book_id | author_id |
+----+---------+-----------+
|  1 |       1 |         1 |
|  2 |       2 |         1 |
|  3 |       2 |         2 |
|  4 |       2 |         3 |
|  5 |       2 |         5 |
|  6 |       3 |         4 |
|  7 |       3 |         5 |
|  8 |       4 |         2 |
|  9 |       5 |         2 |
| 10 |       6 |         2 |
| 11 |       7 |         1 |
| 12 |       7 |         2 |
| 13 |       7 |         3 |
| 14 |       8 |         4 |
| 15 |       9 |         1 |
| 16 |       9 |         5 |
| 17 |      10 |         5 |
+----+---------+-----------+

select * from app01_authordetail;  -- 作者详情

+----+-------------+
| id | phone       |
+----+-------------+
|  1 | 15163726474 |
|  2 | 17738234753 |
|  3 | 15327787382 |
|  4 | 13981080124 |
|  5 | 13273837482 |
+----+-------------+

正反向

正反向的概念就是看外键建立在那张表之上。

book(fk) ---> publish : 正向 publish ---> book(fk) : 反向

切记一句话:

正向查询用外键

反向查询用 表名_set

对象跨表

使用对象跨表语法如下:

正向:
	book_obj = models.书籍.objects.get(col=xxx)
	publish_obj = book_obj.fk_出版社		 # 这里publish_obj就是与book_obj相关的出版社了
	publish_obj.all()/.filter(col=xxx)/.get(col_xxx)  
反向:
	publish_obj = models.出版社.objects.get(col=xxx)
	book_obj = publish_obj.book_set		   # 这里book_obj就是与publish_obj相关的书籍了
	book_obj.all()/.filter(col=xxx)/.get(col_xxx)

查找 id 为1的出版社出版的所有书籍,反向

from app01 import models

publish = models.Publish.objects.get(pk=1)
book_queryset = publish.book_set
res = book_queryset.all()
print(res)

# <QuerySet [<Book: 对象-Django精讲>, <Book: 对象-Js精讲>, <Book: 对象-Python入门>, <Book: 对象-FLASK源码分析>]>

查找 id 为2的书籍作者,正向

from app01 import models

book = models.Book.objects.get(pk=2)
authors_queryset = book.authors
res = authors_queryset.all()
print(res)

双下跨表

双下划线也可以进行正向或者反向的跨表,并且支持跨多张表。

注意:双下划线能在 filter() 中使用,也能在 values() 中使用。拿对象用 filter() ,拿单一字段用 values()

查找 id 为1的出版社出版的所有书籍,反向,拿对象

from app01 import models

res_queryset = models.Book.objects.filter(publish__id=1)
print(res_queryset)

# <QuerySet [<Book: 对象-Django精讲>, <Book: 对象-Js精讲>, <Book: 对象-Python入门>, <Book: 对象-FLASK源码分析>]>

查找 id 为2的书籍作者姓名,正向,拿字段

from app01 import models

res_queryset = models.Book.objects.filter(id=2).values("authors__name")
print(res_queryset)

# <QuerySet [{'authors__name': '云崖'}, {'authors__name': '浪里白条'}, {'authors__name': '及时雨'}, {'authors__name': '入云龙'}]>

查找入云龙出的所有书籍,正向,拿对象

from app01 import models

res_queryset = models.Book.objects.filter(authors__name="入云龙")
print(res_queryset)

# <QuerySet [<Book: 对象-HTML入门>, <Book: 对象-CSS入门>, <Book: 对象-C语言入门>, <Book: 对象-FLASK源码分析>]>

查找入云龙出的所有书籍,拿对象,反向查(这个需要配合对象跨表才行)

from app01 import models

res_queryset = models.Author.objects.filter(name="入云龙").first().book_set.all()
print(res_queryset)

# <QuerySet [<Book: 对象-HTML入门>, <Book: 对象-CSS入门>, <Book: 对象-C语言入门>, <Book: 对象-FLASK源码分析>]>

跨多表,从 pk 为1的出版社中查到其所有作者的手机号,拿字段。

from app01 import models

res_queryset = models.Publish.objects.filter(pk=1).values("book__authors__author_detail__phone")
print(res_queryset)

# <QuerySet [{'book__authors__author_detail__phone': 15163726474}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 13273837482}]>

聚合查询

聚合函数

使用聚合函数前应进行导入,在 ORM 中,单独使用聚合查询的关键字是 aggregate()

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

聚合函数应该配合分组一起使用,而不是单独使用。

单独使用一般都是对单表进行操作。

注意:单独使用聚合函数后,返回的并非一个 QuerySet 对象,这代表 filter()/values() 等方法将无法继续使用。

查询书籍的平均价格:

from app01 import models

from django.db.models import Max,Min,Avg,Count,Sum
res = models.Book.objects.aggregate(Avg("price")) # 如不进行分组,则整张表为一组
print(res) # {'price__avg': 108.21}  # 单独使用聚合,返回的是字典

查询最贵的书籍,以及最便宜的书籍。

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
res = models.Book.objects.aggregate(Max("price"),Min("price"))
print(res)  # {'price__max': Decimal('225.00'), 'price__min': Decimal('18.00')}

分组查询

ORM 中,分组查询使用关键字 annotate()

切记以下几点:

1.只要对表使用了 annotate() ,则按照 pk 进行分组

2.如果分组前指定 values() ,则按照该字段进行分组,如 values("name").annotate()

3.分组函数中可以使用聚合函数,并且不同于使用 aggregate() 后的返回对象是一个字典,使用分组函数进行聚合查询后,返回依旧是一个 QuerySet

统计每本书的作者个数

from app01 import models

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

# authors_num 别名
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num')
print(res)  
# <QuerySet [{'title': 'Django精讲', 'authors_num': 1}, {'title': 'HTML入门', 'authors_num': 4}, {'title': 'CSS入门', 'authors_num': 2}, {'title': 'Js精讲', 'authors_num': 1}, {'title': 'Python入门', 'authors_num': 1}, {'title': 'PHP全套', 'authors_num': 1}, {'title': 'GO入门到精通', 'authors_num': 3}, {'title': 'JAVA入门', 'authors_num': 1}, {'title': 'C语言入门', 'authors_num': 2}, {'title': 'FLASK源码分析', 'authors_num': 1}]>

统计每个出版社中卖的最便宜的书籍

from app01 import models

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

# book_name 别名
res = models.Publish.objects.annotate(book_name=Min("book__price"),).values('name','book_name')
print(res)  

# <QuerySet [{'name': '北京出版社', 'book_name': Decimal('18.00')}, {'name': '上海出版社', 'book_name': Decimal('73.53')}, {'name': '西藏出版社', 'book_name': Decimal('19.00')}]>

统计不止一个作者的图书

from app01 import models

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

# authors_num 别名   只要返回的QuerySet对象,就可以使用filter
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num').filter(authors_num__gt=1)
print(res)  

# <QuerySet [{'title': 'HTML入门', 'authors_num': 4}, {'title': 'CSS入门', 'authors_num': 2}, {'title': 'GO入门到精通', 'authors_num': 3}, {'title': 'C语言入门', 'authors_num': 2}]>

查询每个作者出书的总价

from app01 import models

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

# sum_price 别名
res = models.Author.objects.annotate(sum_price=Sum("book__price"),).values('name','sum_price')
print(res)

# <QuerySet [{'name': '云崖', 'sum_price': Decimal('362.57')}, {'name': '浪里白条', 'sum_price': Decimal('487.87')}, {'name': '及时雨', 'sum_price': Decimal('244.34')}, {'name': '玉麒麟', 'sum_price': Decimal('251.00')}, {'name': '入云龙', 'sum_price': Decimal('450.34')}]>

F&Q过滤

F查询

F 查询能够拿到字段的数据。使用前应该先进行导入

form django.db.moduls import F

注意:如果要操纵的字段是字符类型,要对其进行拼接操作还需导入 Concat 进行拼接

将所有书籍的价格提升50元

from app01 import models

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

res = models.Book.objects.update(price=F("price")+50)
print(res)  # 10

在每本书名字后面加上爆款二字(注意:操纵字符类型,需要用到 ConcatValue 函数)

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
# 必须配合下面的两个进行使用
from django.db.models.functions import Concat
from django.db.models import Value

res = models.Book.objects.update(title=Concat(F("title"), Value("爆款")))
print(res)  # 10

Q查询

filter() 中,只要多字段筛选中用逗号进行分割都是 AND 链接,如何进行 ORNOT 呢?此时就要用到 Q ,还是要先进行导入该功能。

form django.db.moduls import Q
符号 描述 filter(Q(col=xxx),Q(col=xxx)) AND关系,只要在filter()中用逗号分割,就是 AND 关系 filter(Q(col=xxx)!Q(col=xxx)) OR关系,用|号进行链接 filter(~Q(col=xxx)!Q(col=xxx)) NO关系,用~号进行连接

查询书籍表中入云龙或云崖出版的书籍

from app01 import models

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

res = models.Book.objects.filter(Q(authors__name='云崖')|Q(authors__name="入云龙"))
print(res)
# <QuerySet [<Book: 对象-Django精讲爆款>, <Book: 对象-HTML入门爆款>, <Book: 对象-GO入门到精通爆款>, <Book: 对象-C语言入门爆款>, <Book: 对象-HTML入门爆款>, <Book: 对象-CSS入门爆款>, <Book: 对象-C语言入门爆款>, <Book: 对象-FLASK源码分析爆款>]>

查询书籍表中不是云崖和入云龙出版的书籍

from app01 import models

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


res = models.Book.objects.filter(~Q(authors__name='云崖'),Q(authors__name="入云龙"))
print(res)
# <QuerySet [<Book: 对象-CSS入门爆款>, <Book: 对象-FLASK源码分析爆款>]>

Q进阶

如果我们的 Q 查询中,能去让用户来输入字符串指定字段进行查询,那么就非常厉害了。

其实这也是能够实现。

from app01 import models

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

q = Q() # 实例化出一个对象
q.connector = "and"  # 指定q的关系

# 两个Q
q.children.append(("price__lt","120")) 
q.children.append(("price__gt","50"))

res = models.Book.objects.filter(q)
# 相当于: models.Book.objects.filter(Q(price__lt=120),Q(price__gt=50))

print(res)
# <QuerySet [<Book: 对象-Python入门爆款>, <Book: 对象-C语言入门爆款>]>

查询优化

使用单表或多表时,可以使用以下方法进行查询优化。

only

only 取出的记录对象中只有指定的字段,没有其他字段。

因此查询速度会非常快。

默认会拿出所有字段。

from app01 import models

obj = models.Book.objects.all().first()

print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

使用 only 只会拿出指定字段,但是如果要拿指定字段以外的字段。则会再次进行查询

from app01 import models

obj = models.Book.objects.only("title").first()

print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1; 
SELECT `app01_book`.`id`, `app01_book`.`price` FROM `app01_book` WHERE `app01_book`.`id` = 1;

defer

deferonly 正好相反,拿除了指定字段以外的其他字段。

但是如果要拿指定字段的字段。则会再次进行查询

from app01 import models

obj = models.Book.objects.defer("title").first()


print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` WHERE `app01_book`.`id` = 1;

select_related

原本的跨表,尤其是使用对象跨表,都会查询两次。

但是 select_related 指定连表,则只会查询一次。

注意! select_related 中指定的连表,只能是外键名。并且不能是多对多关系

原本跨表,两次查询语句。

from app01 import models

obj = models.Book.objects.all().first()
obj_fk = obj.publish
print(obj)
print(obj_fk)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` = 1;

如果是使用 select_related ,则只需要查询一次。

from app01 import models

obj = models.Book.objects.select_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)


SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id`, `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_book` INNER JOIN `app01_publish` ON (`app01_book`.`publish_id` = `app01_publish`.`id`) ORDER BY `app01_book`.`id` ASC LIMIT 1;

prefetch_related

prefetch_related 是子查询。它会默认走两次 SQL 语句,相比于 select_related ,它的查询次数虽然多了一次,但是量级少了很多,不用拼两张表全部内容。

注意! prefetch_related 中指定的连表,只能是外键名。并且不能是多对多关系

from app01 import models

obj = models.Book.objects.prefetch_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` IN (1);  # 注意in,只查这一条

如果要使用两张表中许多数据,则使用 select_related 拼出整表。

如果连表需要的数据不多,可使用 prefetch_related 子查询。

原生SQL

如果你觉得 ORM 有一些查询搞不定,就可以使用原生 SQL 语句。

官方文档: https://docs.djangoproject.com/zh-hans/3.1/topics/db/sql/

connection

这和 pymysql 的使用基本一致,但是并不提供返回 dict 类型的数据。所以我们需要自定义一个函数来进行封装。

from app01 import models
from django.db import connection, connections

def dictfetchall(cursor):
    "Return all rows from a cursor as a dict"
    columns = [col[0] for col in cursor.description]
    return [
    dict(zip(columns, row))
    for row in cursor.fetchall()
]

print(help(connection.cursor))

# 1.1 获取游标对象
cursor = connection.cursor()  # 默认连接default数据库。
cursor.execute(
    """
    SELECT * FROM app01_book INNER JOIN app01_publish
    on app01_book.publish_id = app01_publish.id
    where app01_book.id = 1;
    """
,()) #第二参数,字符串拼接。与pymysql使用相同,防止sql注入。

# 1.2 返回封装结果
row = dictfetchall(cursor)
print(row)

# [{'id': 1, 'title': 'Django精讲', 'price': Decimal('99.23'), 'publish_date': datetime.date(2020, 9, 11), 'publish_id': 1, 'name': '北京出版社', 'addr': '北京市海淀区', 'email': '[email protected]'}]

raw

raw 是借用一个模型类,返回结果将返回至模型类中。

返回结果是一个 RawQuerySet 对象,这种用法必须将主键取出

from app01 import models

res_obj = models.Book.objects.raw("select id,name from app01_author",params=[]) # 必须取出其他表的主键
# params = 格式化。防止sql注入

for i in res_obj:
	print(i.name)  # 虽然返回的Book实例化,但是其中包含了作者的字段。可以通过属性点出来

# 云崖
# 浪里白条
# 及时雨
# 玉麒麟
# 入云龙

你可以用 raw()translations 参数将查询语句中的字段映射至模型中的字段。这是一个字典,将查询语句中的字段名映射至模型中的字段名。说白了就是 as 一个别名。

例如,上面的查询也能这样写:

from app01 import models
name_map = {'pk': 'aid', 'name': 'a_name',}  # 不要使用id,使用pk。它知道是id  

res_obj = models.Book.objects.raw("select * from app01_author", params=[],translations=name_map)
# params = 格式化。防止sql注入

for i in res_obj:
	print(i.a_name) 

# 云崖
# 浪里白条
# 及时雨
# 玉麒麟
# 入云龙

extra

原生与 ORM 结合,这个可用于写子查询。

我没怎么用过这个,很少使用。

def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
    # 构造额外的查询条件或者映射,如:子查询

    Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

开启事务

Django 中开启事务非常简单,因为又 with() 上下文管理器的情况,所以我们只需要在没出错的时候提交,出错后回滚即可。

from django.db import transaction

 
def func_views(request):
    try:
        with transaction.atomic():      
			# 操作1
			# 操作2
			# 如果没发生异常自动提交事务
            # raise DatabaseError 数据库异常,说明sql语句有问题    
    except DatabaseError:     # 自动回滚,不需要任何操作
            pass

详解QuerySet

惰性求值

QuerySet 的一大特性就是惰性求值,即你不使用数据时是不会进行数据库查询。

from app01 import models
res_queryset = models.Book.objects.all()
# 无打印SQL

缓存机制

QuerySet 具有缓存机制,下次再使用同一变量时,不会走数据库查询而是使用缓存。

from app01 import models
res_queryset = models.Book.objects.filter(pk=1)

for i in res_queryset:
	print(i)

for i in res_queryset:
	print(i)

# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲
# 对象-Django精讲

避免脏数据

因为 QuerySet 的缓存机制,所以使得可能出现脏数据。

即数据库中修改了某个数据,但是 QuerySet 缓存中依旧是旧数据。

避免这种问题的解决方案有两种:

 1.不要重复使用同一变量。每次使用都应该重新赋值(虽然增加查询次数,但是保证数据准确)
 2.使用迭代器方法,不可重复用
from app01 import models
res_queryset = models.Book.objects.filter(pk=1)

for i in res_queryset.iterator(): # 迭代器
	print(i)

for i in res_queryset:  # 缓存没有,重新获取
	print(i)

# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲
# (0.003) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲

常用字段及参数

mysql对照

Django MySQL AutoField integer AUTO_INCREMENT BigAutoField bigint AUTO_INCREMENT BinaryField longblob BooleanField bool CharField varchar(%(max_length)s) CommaSeparatedIntegerField varchar(%(max_length)s) DateField date DecimalField numeric(%(max_digits)s, %(decimal_places)s) DurationField bigint FileField varchar(%(max_length)s) FilePathField varchar(%(max_length)s) IntegerField integer BigIntegerField bigint IPAddressField char(15) GenericIPAddressField char(39) NullBooleanField bool OneToOneField integer PositiveIntegerField nteger UNSIGNED SlugField varchar(%(max_length)s) SmallIntegerField smallint TextField longtext TimeField time UUIDField char(32)

数值类型

Django字段 描述 是否有注意事项 AutoField int自增列,必须填入参数 primary_key=True 有,见描述 BigAutoField bigint自增列,必须填入参数 primary_key=True 有,见描述 SmallIntegerField 小整数 -32768 ~ 32767 PositiveSmallIntegerField 正小整数 0 ~ 32767 IntegerField 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField 正整数 0 ~ 2147483647 BigIntegerField 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 BooleanField 布尔值类型 NullBooleanField 可以为空的布尔值 FloatField 浮点型 DecimalField 10进制小数 有,见说明 BinaryField 二进制类型

说明补充:

DecimalField(Field)
- 10进制小数
- 参数:
	max_digits,小数总长度
	decimal_places,小数位长度

其他补充:自定义无符号整数类型

class UnsignedIntegerField(models.IntegerField): # 必须继承
	def db_type(self, connection):
		return 'integer UNSIGNED' # 返回真实存放的类型

字符类型

Django字段 描述 是否有注意事项 CharField 必须提供max_length参数, max_length表示字符长度 有,见描述 EmailField 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 有,见说明 URLField 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField 字符串类型,格式必须为逗号分割的数字 UUIDField 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 有,见说明 FileField 字符串,路径保存在数据库,文件上传到指定目录 有,见说明 ImageField 字符串,路径保存在数据库,文件上传到指定目录 有,见说明

说明补充:

GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
    protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
    unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"

FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹

FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
    upload_to = ""      上传文件的保存路径
    storage = None      存储组件,默认django.core.files.storage.FileSystemStorage


ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
    upload_to = ""      上传文件的保存路径
    storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
    width_field=None,   上传图片的高度保存的数据库字段名(字符串)
    height_field=None   上传图片的宽度保存的数据库字段名(字符串)

时间类型

Django字段 描述 是否有注意事项 DateTimeField 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 有,见说明 DateField 日期格式 YYYY-MM-DD 有,见说明 TimeField 时间格式 HH:MM[:ss[.uuuuuu]] DurationField 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

说明补充:

时间类型的字段都有两个参数:
auto_now=False  # 当记录更新时是否自动更新当前时间
auto_now_add=False # 当记录创建时是否自动更新当前时间

条件参数

条件参数 描述 null 数据库中字段是否可以为空,接受布尔值 db_column 数据库中字段的列名,接受字符串 default 数据库中字段的默认值,接受时间,数值,字符串 primary_key 数据库中字段是否为主键,接受布尔值(一张表最多一个主键) db_index 数据库中字段是否可以建立索引,接受布尔值 unique 数据库中字段是否可以建立唯一索引,接受布尔值 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引,接受布尔值 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引,接受布尔值 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引,接受布尔值

choices

choices 是一个非常好用的参数。它允许你数据库中存一个任意类型的值,但是需要使用时则是使用的它的描述。

如下:

gender = models.BooleanField(choices=((0,"male"),(1,"female")),default=0)
# 实际只存0和1,但是我们取的时候会取male或者female

取出方法:

get_col_display()

后端示例:
	obj = models.User.objects.get(pk=1)
	gender = obj.get_gender_display()
	print(gender)  # male
	
前端示例:
	{{obj.get_gender_display}}  <!-- 前端不用加括号,自己调用 -->

元信息

class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)
        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"

            # 联合索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 联合唯一索引
            unique_together = (("driver", "restaurant"),)

            # admin中显示的表名称
            verbose_name

            # verbose_name加s
            verbose_name_plural

多表关系参数

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要进行关联的表名
        to_field=None,              # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
                                        - models.CASCADE,删除关联数据,与之关联也删除
                                        - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                                        - models.PROTECT,删除关联数据,引发错误ProtectedError
                                        - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                                        - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                                        - models.SET,删除关联数据,
                                                      a. 与之关联的值设置为指定值,设置:models.SET(值)
                                                      b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

                                                        def func():
                                                            return 10

                                                        class MyModel(models.Model):
                                                            user = models.ForeignKey(
                                                                to="User",
                                                                to_field="id"
                                                                on_delete=models.SET(func),)
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        db_constraint=True          # 是否在数据库中创建外键约束
        parent_link=False           # 在Admin中是否显示关联数据


    OneToOneField(ForeignKey)
        to,                         # 要进行关联的表名
        to_field=None               # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为

                                    ###### 对于一对一 ######
                                    # 1. 一对一其实就是 一对多 + 唯一索引
                                    # 2.当两个类之间有继承关系时,默认会创建一个一对一字段
                                    # 如下会在A表中额外增加一个c_ptr_id列且唯一:
                                            class C(models.Model):
                                                nid = models.AutoField(primary_key=True)
                                                part = models.CharField(max_length=12)

                                            class A(C):
                                                id = models.AutoField(primary_key=True)
                                                code = models.CharField(max_length=1)

    ManyToManyField(RelatedField)
        to,                         # 要进行关联的表名
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                    # 做如下操作时,不同的symmetrical会有不同的可选字段
                                        models.BB.objects.filter(...)

                                        # 可选字段有:code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)

                                        # 可选字段有: bb, code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)

        through=None,               # 自定义第三张表时,使用字段用于指定关系表
        through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                        from django.db import models

                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)

                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )

                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在数据库中创建外键约束
        db_table=None,              # 默认创建第三张表时,数据库中表的名称字段以及参数

M2M创建

M2M 的创建方式有三种,分别是全自动,半自动,全手动。

全自动使用最方便,但是扩展性最差。

半自动介于全自动与全手动之间。

全手动使用最麻烦,但是扩展性最好。

全自动

自动创建的第三张表,即为全自动。

最上面多表关系中的多对多,使用的便是全自动创建。

全自动创建操纵及其方便,如 add/set/remove/clear

半自动

手动创建第三张表,并指定此表中那两个字段是其他两张表的关联关系。

可使用 __ 跨表,正反向查询。

但是不可使用如 add/set/remove/clear

在实际生产中,推荐使用半自动。它的扩展性最强

class Book(models.Model):
	name = models.CharField(max_length=32)
	authors = models.ManyToManyField(
		to="Author", # 与作者表创建关系。
		through="M2M_BookAuthor", # 使用自己创建的表
		through_fields=('book','author'), # 这两个字段是关系  注意!那张表上创建多对多,就将字段放在前面
	)
	
class Author(models.Model):
	name = models.CharField(max_length=32)
	# book = models.ManyToManyField(
		# to="Book",
		# through="M2M_BookAuthor",
		# through_fields=('author','book'), Author创建,所以author放在前面
	
	# )
	
class M2M_BookAuthor(models.Model):
	book = models.ForeignKey(to="Book")
	author = models.ForeignKey(to="Author")
	# 两个fk,自动添加_id后缀。
	
    class Meta:
    	unique_together = (("author", "book"),)
    	# 联合唯一索引

全手动

不可使用 add/set/remove/clear ,以及 __ 跨表,正反向查询。

所有数据均手动录入。

class Book(models.Model):
	name = models.CharField(max_length=32)

	
class Author(models.Model):
	name = models.CharField(max_length=32)
	
class M2M_BookAuthor(models.Model):
	book = models.ForeignKey(to="Book")
	author = models.ForeignKey(to="Author")
	# 两个fk,自动添加_id后缀。
	
    class Meta:
    	unique_together = (("author", "book"),)
    	# 联合唯一索引

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK