11-模型层

Django自带的sqlite3数据对日期格式不敏感,处理的时候容易出错。

Django 在创建项目时自动在应用下创建了tests.py,这个py文件可以作为测试文件;也可以在应用下手动创建一个py测试文件。

无论哪种方式,都需要提前书写以下代码:

# 测试环境准备 去manage.py 中拷贝前4行代码 然后自己写两行
import os
import sys


def main():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "book.settings")
    # 写这两行
    import django
    django.setup()
    # 这这个diamond块的下面就可以测试django里面的单个py文件了

Django中,通过test命令可以查找并运行所有TestCase的子类

1 运行所有的测试用例

python manage.py test

2 运行某个app下面的所有的测试用例

python manage.py test app

3 运行某个app下面的tests.py文件

python manage.py test app.tests

4 运行某个app下面的tests.py文件中指定的class类ModeTest

python manage.py test  app.tests.ModeTest

5 执行 ModeTest 类下的某个测试方法

python manage.py test app.tests.ModeTest.methodname

6 使用 -p(或--pattern)参数模糊匹配测试文件

python manage.py test -p test*.py

数据准备

from django.db import models
from django.utils import timezone  # 导入模块


class UserInfo(models.Model):
    """
    max_length:长度
    verbose_name:相当于commit,注释
    default:默认值
    null: 是否允许为空 null=True blank=True 就可以为空
    """
    username = models.CharField(max_length=32, verbose_name="用户名")
    password = models.CharField(max_length=32, verbose_name="密码")
    age = models.IntegerField(verbose_name="年龄", null=False, default=3)
    # 在Django中,通常使用 django.utils.timezone.now() 来获取当前日期时间,它会返回一个带有时区信息的对象。
    register_time = models.DateTimeField(default=timezone.now, verbose_name='注册时间')

数据库的配置,在settings中配置好即可

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_django',
        'HOST': 'localhost',
        'USER': 'root',
        'PASSWORD': 'xm',
        'CHARSET': 'utf8'
    }
}

该数据库之前已经创建过了,展示一下数据

mysql> select * from app1_userinfo;
+----+------------------------+----------+-----+----------------------------+
| id | username               | password | age | register_time              |
+----+------------------------+----------+-----+----------------------------+
|  5 | 大乔                   | 666      |   3 | 2024-03-04 09:16:40.239485 |
|  6 | 阿珂                   | 666      |   4 | 2024-03-04 09:16:40.239485 |
|  7 | 庄周                   | 666      |   5 | 2024-03-04 09:16:40.239485 |
|  8 | 海月月                 | 666      |   6 | 2024-03-04 09:16:40.239485 |
|  9 | 小满                   | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 10 | 123                    | 12       |   3 | 2024-03-04 09:16:40.239485 |
| 11 | 大白                   | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 12 | 大                     | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 13 | chuxueonline@gmail.com | 13       |   3 | 2024-03-04 09:16:40.239485 |
| 14 | cancerwake             | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 15 | 上官婉儿               | 782      |   7 | 2024-03-04 09:16:40.239485 |
| 16 | 哆啦A梦                | 123      |   4 | 2024-03-04 09:16:40.239485 |
+----+------------------------+----------+-----+----------------------------+
12 rows in set (0.01 sec)

mysql> describe app1_userinfo;
+---------------+-------------+------+-----+---------+----------------+
| Field         | Type        | Null | Key | Default | Extra          |
+---------------+-------------+------+-----+---------+----------------+
| id            | bigint(20)  | NO   | PRI | NULL    | auto_increment |
| username      | varchar(32) | NO   |     | NULL    |                |
| password      | varchar(32) | NO   |     | NULL    |                |
| age           | int(11)     | NO   |     | NULL    |                |
| register_time | datetime(6) | NO   |     | NULL    |                |
+---------------+-------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

操作数据

特殊说明,下面演示的全部代码都是在test.py main入口下面演示的(即不会复制全部代码,只复制关键语句)

增加数据

UserInfo.objects.create(username='上官婉儿', password=782, age=7)

删除数据

# 方式1 找到后直接删除
UserInfo.objects.filter(username='大').delete()

# 方式2 赋值给一个变量,然后通过变量.delete()  更推荐直接删除
useless = UserInfo.objects.filter(id=13)
useless.delete()

修改数据

# 方式1 查询后直接修改
UserInfo.objects.filter(username='大白').update(username='小白')

# 方式2 通过get查询后赋值给变量,然后通过变量.字段=新值
# 然后通过变量.save()保存修改才能修改成功
data = UserInfo.objects.get(username='123')
data.username = '万年公主'
data.save()

全部数据all

items = UserInfo.objects.all()
print(len(items))  # 10

按指定条件过滤filter和get

获取到数据之后,都可以通过.字段名取到对应的值,需要注意的是filter()得到的是一个

items = UserInfo.objects.filter(id=16)
print(type(items))  #  <class 'django.db.models.query.QuerySet'>

items = UserInfo.objects.get(id=16)
print(items.username)

找到之后也可以使用.first或者.last获取唯一的数据(前提是只有一个数据)

username = UserInfo.objects.filter(id=16).first()
print(username.username)  # 哆啦A梦

username = UserInfo.objects.filter(id=15).last()
print(username.username)  # 上官婉儿

获取数据值

items = UserInfo.objects.all().values()
print(type(items))  # <class 'django.db.models.query.QuerySet'>
# 通过循环遍历values得到的是字典
items = UserInfo.objects.all().values()
for item in items:
    print(item, type(item))  # <class 'dict'>
items = UserInfo.objects.all().values_list()
for item in items:
    print(item, type(item))  # <class 'tuple'>

去重distinct

去重
from django.test import TestCase

if __name__ == '__main__':
    import os

    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
    import django
    django.setup()
    from app1.models import UserInfo

    useless = UserInfo.objects.values('age').distinct()
    # 被删除的数据
    for item in useless:
        print(item)

    # {'age': 3}
    # {'age': 4}
    # {'age': 5}
    # {'age': 6}
    # {'age': 7}

排序order_by

默认是从小到大排序
from django.test import TestCase


if __name__ == '__main__':
    import os
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
    import django
    django.setup()
    from app1.models import UserInfo
    from tabulate import tabulate

    table_data = []
    data = UserInfo.objects.order_by('age').values()
    for line in data:
        username = line.get('username')
        age = line.get('age')
        table_data.append([username, age])

    headers = ['username', 'age']
    print(tabulate(tabular_data=table_data, headers=headers, tablefmt='grid'))

    # +------------+-------+
    # | username   |   age |
    # +============+=======+
    # | 大乔       |     3 |
    # +------------+-------+
    # | 小满       |     3 |
    # +------------+-------+
    # | 万年公主   |     3 |
    # +------------+-------+
    # | 小白       |     3 |
    # +------------+-------+
    # | cancerwake |     3 |
    # +------------+-------+
    # | 阿珂       |     4 |
    # +------------+-------+
    # | 哆啦A梦    |     4 |
    # +------------+-------+
    # | 庄周       |     5 |
    # +------------+-------+
    # | 海月月     |     6 |
    # +------------+-------+
    # | 上官婉儿   |     7 |
    # +------------+-------+
如果要降序,给排序的字段前面加上一个 - 号即可
from django.test import TestCase


if __name__ == '__main__':
    import os
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
    import django
    django.setup()
    from app1.models import UserInfo
    from tabulate import tabulate

    table_data = []
    data = UserInfo.objects.order_by('-age').values()
    for line in data:
        username = line.get('username')
        age = line.get('age')
        table_data.append([username, age])

    headers = ['username', 'age']
    print(tabulate(tabular_data=table_data, headers=headers, tablefmt='grid'))

    # +------------+-------+
    # | username   |   age |
    # +============+=======+
    # | 上官婉儿   |     7 |
    # +------------+-------+
    # | 海月月     |     6 |
    # +------------+-------+
    # | 庄周       |     5 |
    # +------------+-------+
    # | 阿珂       |     4 |
    # +------------+-------+
    # | 哆啦A梦    |     4 |
    # +------------+-------+
    # | 大乔       |     3 |
    # +------------+-------+
    # | 小满       |     3 |
    # +------------+-------+
    # | 万年公主   |     3 |
    # +------------+-------+
    # | 小白       |     3 |
    # +------------+-------+
    # | cancerwake |     3 |
    # +------------+-------+
如果要反转数据 加上一个reverse即可,效果同添加-号
from django.test import TestCase


if __name__ == '__main__':
    import os
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
    import django
    django.setup()
    from app1.models import UserInfo
    from tabulate import tabulate

    table_data = []
    data = UserInfo.objects.order_by('age').reverse().values()
    for line in data:
        username = line.get('username')
        age = line.get('age')
        table_data.append([username, age])

    headers = ['username', 'age']
    print(tabulate(tabular_data=table_data, headers=headers, tablefmt='grid'))

    # +------------+-------+
    # | username   |   age |
    # +============+=======+
    # | 上官婉儿   |     7 |
    # +------------+-------+
    # | 海月月     |     6 |
    # +------------+-------+
    # | 庄周       |     5 |
    # +------------+-------+
    # | 阿珂       |     4 |
    # +------------+-------+
    # | 哆啦A梦    |     4 |
    # +------------+-------+
    # | 大乔       |     3 |
    # +------------+-------+
    # | 小满       |     3 |
    # +------------+-------+
    # | 万年公主   |     3 |
    # +------------+-------+
    # | 小白       |     3 |
    # +------------+-------+
    # | cancerwake |     3 |
    # +------------+-------+

统计个数count

data = UserInfo.objects.count()
print(data)  # 10

排除某个结果exclude

在排除结果之前,我们先看看默认的数据库

mysql> select * from app1_userinfo;
+----+--------------+----------+-----+----------------------------+
| id | username     | password | age | register_time              |
+----+--------------+----------+-----+----------------------------+
|  5 | 大乔         | 666      |   3 | 2024-03-04 09:16:40.239485 |
|  6 | 阿珂         | 666      |   4 | 2024-03-04 09:16:40.239485 |
|  7 | 庄周         | 666      |   5 | 2024-03-04 09:16:40.239485 |
|  8 | 海月月       | 666      |   6 | 2024-03-04 09:16:40.239485 |
|  9 | 小满         | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 10 | 万年公主     | 12       |   3 | 2024-03-04 09:16:40.239485 |
| 11 | 小白         | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 14 | cancerwake   | 123      |   3 | 2024-03-04 09:16:40.239485 |
| 15 | 上官婉儿     | 782      |   7 | 2024-03-04 09:16:40.239485 |
| 16 | 哆啦A梦      | 123      |   4 | 2024-03-04 09:16:40.239485 |
+----+--------------+----------+-----+----------------------------+
10 rows in set (0.01 sec)
先使用filter选出age等于3的结果,然后排除小白
from django.test import TestCase
import wcwidth


if __name__ == '__main__':
    import os
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
    import django
    django.setup()
    from app1.models import UserInfo
    from tabulate import tabulate

    table_data = []
    data = UserInfo.objects.filter(age='3').exclude(username='小白').values()
    for line in data:
        username = line.get('username')
        age = line.get('age')
        table_data.append([username, age])

    headers = ['username', 'age']
    print(tabulate(tabular_data=table_data, headers=headers, tablefmt='grid'))
    
    # +------------+-------+
    # | username   |   age |
    # +============+=======+
    # | 大乔       |     3 |
    # +------------+-------+
    # | 小满       |     3 |
    # +------------+-------+
    # | 万年公主   |     3 |
    # +------------+-------+
    # | cancerwake |     3 |
    # +------------+-------+

是否存在exists

返回的是布尔值,很少用到

exists = UserInfo.objects.filter(username='貂蝉').exists()
print(exists)  # False
简单总结
# 以下方法的前提均为 models.模型表名.objects 之后的方法

# 【0】新增数据
.create(字段名=字段值)
# 【1】查询全部数据
.all()
# 【2】带有筛选条件的过滤
.filter(筛选字段名=筛选字段值)
# 【3】获取筛选结果的第一条数据
.filter(筛选字段名=筛选字段值).first()
# 【4】获取筛选结果的最后一条数据
.filter(筛选字段名=筛选字段值).last()
# 【4.1】获取筛选后的结果进行修改
.filter(筛选字段名=筛选字段值).update(字段名=字段值)
# 【4.2】获取筛选后的结果进行删除
.filter(筛选字段名=筛选字段值).delete()
# 【5】根据指定条件获取数据对象,条件不存在或者数据大于2则会报错
.get(筛选字段名=筛选字段值)
# 【6】获取单个字段名所对应的数据
.values(字段名)
# 【7】获取多个字段名所对应的数据
.value_list(字段名1,字段名2)
# 【8】去重,拿到指定字段的数据后,对筛选出的数据进行去重
.values(字段名1,字段名2).distinct()
# 【9】排序
# 正序
.order_by(字段名)
# 倒序
.order_by(-字段名)
# 【10】反转的已经经过排序过的数据
.order_by(字段名).reverse()
# 【11】统计当前数据对象的个数
.count()
# 【12】排除指定条件的数据
.exclude(字段名=字段值)
# 【13】判断符合当前条件的数据是否存在
.filter(筛选字段名=筛选字段值).exists()

# 补充:查看当前ORM语句的SQL查询语句
# 注意可以使用此方法的必须是 QuerySet 对象
.query

双下划线查询

精髓就是字段名__条件=值

filter里是不能写逻辑运算符的,而是给定了一个特定的方法去提供逻辑查询,那就是双下划线。

返回值 = models.User.objects.filter(字段__contains = '条件')
方法 功能
字段__gt 大于
字段__lt 小于
字段__gte 大于等于
字段__lte 小于等于
字段__in 成员运算、在什么里
字段__range 范围查询
字段__contains 模糊查询,区分大小写
字段__icontains 模糊查询,忽略大小写
字段__startswith 匹配开头
字段__endswith 匹配结尾
字段__regex 正则表达式
字段__year 按照年份筛选数据
字段__month 按照月份筛选数据
字段__day 按照天筛选数据
# 年龄在22 3 4 岁之间的
obj = models.User.objects.filter(age__in=[22, 3, 4])
print(obj)  # <QuerySet [<User: 对象:大乔>, <User: 对象:小乔>]>
# 年龄在4岁到22岁之间的  即包含4和22
obj = models.User.objects.filter(age__range=[4, 22])
print(obj)  # <QuerySet [<User: 对象:大乔>, <User: 对象:小乔>]>

# 名字里面包含小的  就是MySQL里面的like 模糊匹配 默认区分大小写
obj = models.User.objects.filter(username__contains='i')
print(obj)  # <QuerySet [<User: 对象:Iki>, <User: 对象:Iki>]>

# 如果要区分大小写 如果要不区分大小写 在前面加一个i就行了
obj = models.User.objects.filter(username__icontains="i")
print(obj) # <QuerySet [<User: 对象:amIgo>, <User: 对象:Iki>, <User: 对象:amIgo>, <User: 对象:Iki>]>

# 以什么开头
obj = models.User.objects.filter(username__startswith='小')
print(obj)  # <QuerySet [<User: 对象:小满>, <User: 对象:小满>, <User: 对象:小乔>]>

# 以什么结尾
obj = models.User.objects.filter(username__endswith='月')
print(obj)  # <QuerySet [<User: 对象:海月>]>

# 博客园的日期统计就用到了年份和月份筛选

# 年份筛选数据
obj = models.User.objects.filter(register__year='2012')
print(obj)  # <QuerySet [<User: 对象:老夫子>]>

# 月份来筛选
obj = models.User.objects.filter(register__month='12')
print(obj)  # <QuerySet [<User: 对象:小满>, <User: 对象:老夫子>, <User: 对象:小满>]>

多对多

假定下面这些概念、字段与关系:

  • 作者模型:一个作者有姓名和年龄。
  • 作者详细模型:把作者的详情放到详情表,手机号,家庭住址信息。
  • 作者详情模型 和 作者模型之间是一对一的关系(one-to-one)。
  • 出版社模型:出版社有名称,所在城市以及email。
  • 书籍模型: 书籍有书名和价格、出版日期。
  • 一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many)。
  • 一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
  • 书跟作者是多对多关系,利用Django 的建表语句,可以新生成一张“关系表”---> book2author。

ORM中的外键创建和mysql几乎一样,以创建图书表,出版社表,作者表和作者详情表。

两张表 关系 方法 外键位置
书与出版社 一对多关系 ForeignKey(to='出版社表') 一对多关系也是建在多的一方,建在书的表里
书与作者 多对多关系 ManyToManyField(to='作者表') 多对多关系,可以不用自己创建第三张表
作者与作者详情 一对一关系 OneToOneField=(to='作者详情表') 一对一关系,建在查询频率较高的表中,建在作者表里

三个关键字里面的参数,to用于指定跟哪张表有关系,自动关联主键。to_field\to_fields,也可以自己指定关联字段。

ManyToManyField不会在表中创建实际的字段,而是告诉 Django ORM 自动创建第三张关系表。

ForeignKey、OneToOneField会在字段的后面自动添加 _id 后缀,如果你在定义模型类的时候自己添加了该后缀那么迁移的时候还会添加,所以不要自己加下划线id后缀。

创建模型

from textwrap import dedent
from django.db import models


# 出版社详细
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32, verbose_name='出版社名称')
    city = models.CharField(max_length=32, verbose_name='出版社地址')
    # Email是特定的个数
    email = models.EmailField()

    class Meta:
        # 这里这样定义之后,迁移数据库的时候就会按照我们指定的表名了
        db_table = 'Publish'  # 表名为 'Publish'
        verbose_name_plural = '出版社'  # 在Django管理界面中显示为 '出版社'

    def __str__(self):
        return dedent(
            f"""
            nid: {self.nid}
            name: {self.name}
            city: {self.city}
            email: {self.email}
            """
        )


# 作者详细表
class AuthorDetails(models.Model):
    nid = models.AutoField(primary_key=True)
    # 注意 这里不能设置 auto_now_add=True 因为这里的数据是从前端传过来的
    birthday = models.DateTimeField(verbose_name='生日')
    phone = models.CharField(max_length=11, verbose_name='手机号')
    gender_choice = [
        (1, '男'),
        (2, '女')
    ]
    gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choice)
    addr = models.CharField(max_length=100, verbose_name='作者地址')

    class Meta:
        db_table = 'AuthorDetails'
        verbose_name_plural = '作者详细表'

    def __str__(self):
        return dedent(f"""
            nid: {self.nid}
            birthday: {self.birthday}
            phone: {self.phone}
            gender: {self.gender}
            addr: {self.addr}
            """
        )


# 作者表
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    # 年龄,使用int类型的小整数就可以了
    age = models.IntegerField()
    # 由于作者与作者详细表是一对一的关系,所以选择在作者表中这样建立外键
    # 注意这里只写 author_detail 就可以了, _id 程序会自增的
    # 注意这里的on_delete一定要加,因为是一对一
    author_detail = models.OneToOneField(to='AuthorDetails', to_field='nid', on_delete=models.CASCADE)

    class Meta:
        db_table = 'Author'
        verbose_name_plural = '作者表'


# 书籍
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    # 出版日期:日期格式 这里一样不能设置 auto_now_add=True
    pub_date = models.DateTimeField()
    # 价格,最大五位数,保留小数点两位
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 与出版社表的字段 publish_id
    # 注意自己写的时候只写publish就可以了,django会自动补上
    # 注意:on_delete必须加上,不过这里建议设置为null 当然不是级联删除
    # null=True表示可以为空值
    # 一对多
    publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.SET_NULL, null=True)

    # 书跟作者是多对多的关系,理论上需要新建一张关系表,分别将其与作者表关联起来
    authors =models.ManyToManyField(to='Author')

    class Meta:
        db_table = 'Book'
        verbose_name_plural = '书籍表'

    def __str__(self):
        return dedent(
            f"""
            nid: {self.nid}
            title: {self.title}
            pub_date: {self.pub_date}
            price: {self.price}
            publish: {self.publish}
            authors: {self.authors}
            """
        )

    """
        👇
        create table book2author(
            nid int primary key auto_increment,
            book_id int, 
            author_id int,
            
            foreign key (book_id) references Book(nid) on update cascade on delete set null,
            foreign key (author_id) references Author(nid) on update cascade on delete set null
        );
    """


执行命令

python manage.py makemigrations
python manage.py migrate
点我查看表结构
mysql> desc Publish;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| nid   | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(32)  | NO   |     | NULL    |                |
| city  | varchar(32)  | NO   |     | NULL    |                |
| email | varchar(254) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)

mysql> desc AuthorDetails;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| nid      | int(11)      | NO   | PRI | NULL    | auto_increment |
| birthday | datetime(6)  | NO   |     | NULL    |                |
| phone    | varchar(11)  | NO   |     | NULL    |                |
| gender   | smallint(6)  | NO   |     | NULL    |                |
| addr     | varchar(100) | NO   |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

mysql> desc Author;
+------------------+-------------+------+-----+---------+----------------+
| Field            | Type        | Null | Key | Default | Extra          |
+------------------+-------------+------+-----+---------+----------------+
| nid              | int(11)     | NO   | PRI | NULL    | auto_increment |
| name             | varchar(32) | NO   |     | NULL    |                |
| age              | int(11)     | NO   |     | NULL    |                |
| author_detail_id | int(11)     | NO   | UNI | NULL    |                |
+------------------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> desc Book;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| nid        | int(11)      | NO   | PRI | NULL    | auto_increment |
| title      | varchar(32)  | NO   |     | NULL    |                |
| pub_date   | datetime(6)  | NO   |     | NULL    |                |
| price      | decimal(5,2) | NO   |     | NULL    |                |
| publish_id | int(11)      | YES  | MUL | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

录入数据

models.Publish.objects.create(name='上海出版社', city='上海', email='shanghai@email.com')
models.Publish.objects.create(name='北京出版社', city='北京', email='123@qq.com')
models.Publish.objects.create(name='湖南出版社', city='湖南', email='hunan@qq.com')

models.Book.objects.create(title='JavaScript', price=212.88, pub_date='2023-3-25', publish_id=1)
models.Book.objects.create(title='Typescript', price=112.58, pub_date='2024-3-5', publish_id=2)
models.Book.objects.create(title='Python', price=88.82, pub_date='2022-12-12', publish_id=3)
models.Book.objects.create(title='Dart', price=150, pub_date='2023-11-04', publish_id=1)
models.Book.objects.create(title='Golang', price=200.12, pub_date='2024-1-11', publish_id=3)
models.Book.objects.create(title='Julia', price=178.9, pub_date='2024-2-21', publish_id=1)

models.AuthorDetails.objects.create(birthday="2000-01-14", phone='13312129999', gender=1,  addr='上海浦东')
models.AuthorDetails.objects.create(birthday="1993-12-14", phone='16923248772', gender=2, addr='云南大理')
models.AuthorDetails.objects.create(birthday="1995-05-14", phone='13888888888', gender=2, addr='北京朝阳')
models.AuthorDetails.objects.create(birthday="1997-02-23", phone='15987567232', gender=2, addr='陕西西安')
models.AuthorDetails.objects.create(birthday="2003-04-22", phone='13678912344', gender=1, addr='四川成都')
models.AuthorDetails.objects.create(birthday="1998-09-13", phone='18721352234', gender=2, addr='广东广州')

models.Author.objects.create(name='老夫子', age=22, author_detail_id=1)
models.Author.objects.create(name='小满', age=17, author_detail_id=2)
models.Author.objects.create(name='王昭君', age=18, author_detail_id=3)
models.Author.objects.create(name='大乔', age=22, author_detail_id=4)
models.Author.objects.create(name='兰陵王', age=22, author_detail_id=5)
models.Author.objects.create(name='阿珂', age=22, author_detail_id=6)

一对多外键

# 一对多外键增删改查

# 增加
# 方法1 虚拟字段
publish_obj = models.Publish.objects.filter(nid=1).first()
models.Book.objects.create(title='Ruby', price=188.99, pub_date='2006-06-06', publish=publish_obj)

# 方法2 直接增加
models.Book.objects.create(title='C++', price=88.99, pub_date='1999-09-12', publish_id=3)


# 修改
# 方式1 虚拟字段
models.Book.objects.filter(pk=5).update(publish_id=2)
publish_obj = models.Publish.objects.filter(pk=1).first()
# 方式2 直接修改
models.Book.objects.filter(pk=5).update(publish=publish_obj)

# 删 由于我设置的是 set null 所以数据没有被删除,而是成了空字段
models.Publish.objects.filter(pk=2).delete()
mysql> select * from publish;
+-----+-----------------+--------+--------------------+
| nid | name            | city   | email              |
+-----+-----------------+--------+--------------------+
|   1 | 上海出版社      | 上海   | shanghai@email.com |
|   3 | 湖南出版社      | 湖南   | hunan@qq.com       |
+-----+-----------------+--------+--------------------+
2 rows in set (0.00 sec)

mysql> select * from book;
+-----+------------+----------------------------+--------+------------+
| nid | title      | pub_date                   | price  | publish_id |
+-----+------------+----------------------------+--------+------------+
|   1 | JavaScript | 2023-03-24 16:00:00.000000 | 212.88 |          1 |
|   2 | Typescript | 2024-03-04 16:00:00.000000 | 112.58 |       NULL |
|   3 | Python     | 2022-12-11 16:00:00.000000 |  88.82 |          3 |
|   4 | Dart       | 2023-11-03 16:00:00.000000 | 150.00 |          1 |
|   5 | Golang     | 2024-01-10 16:00:00.000000 | 200.12 |          1 |
|   6 | Julia      | 2024-02-20 16:00:00.000000 | 178.90 |          1 |
|   7 | Ruby       | 2006-06-05 16:00:00.000000 | 188.99 |          1 |
|   8 | C++        | 1999-09-11 16:00:00.000000 |  88.99 |          3 |
+-----+------------+----------------------------+--------+------------+
8 rows in set (0.00 sec)

多对多外键

# 多对多增删改查
book_obj = models.Book.objects.filter(title='Python').first()
laofuzi = models.Author.objects.filter(nid=1).first()
ake = models.Author.objects.filter(nid=6).first()
book_obj.authors.add(laofuzi, ake)  # 书籍name为Python的书籍绑定一个主键为1和6的作者
"""
add 给第三张关系表添加数据,阔内即可传数字也可以传对象 并且都支持多个
"""

# 查询主键为3的书籍的所有作者的名字
bookVal = models.Book.objects.filter(nid=3).first()
rets = bookVal.authors.all().values('name')
print(rets)  # <QuerySet [{'name': '老夫子'}, {'name': '阿珂'}]>

# 修改
book_obj = models.Book.objects.filter(nid=3).first()
book_obj.authors.set([3, 4]) # 括号内必须给一个可迭代对象
book_obj.authors.set([4])  # 括号内必须给一个可迭代对象

author_obj = models.Author.objects.filter(pk=4).first()
author_obj1 = models.Author.objects.filter(pk=5).first()
book_obj.authors.set([author_obj, author_obj1])  # 括号内必须给一个可迭代对象
"""
括号内必须传一个可迭代对象,该对象内既可以数字也可以对象,并且支持多个
"""

# 解除多对多关系,注意first要加
book_obj = models.Book.objects.filter(nid=4).first()
# 注意这里的 4 代表author_id
book_obj.authors.remove(4)
# 在第三张关系表中清空某个书籍与作者的绑定关系
book_obj.authors.clear()
"""
clear 括号内不要加任何参数
"""

正反向的概念

正向查询按字段;反向查询按表名小写

# 正向: 外键字段在我手上那么,我查你就是正向
# 反向: 外键字段如果不在手上,我查你就是反向
# book >>> 外键字段在书那儿(正向) >>> publish
# publish >>> 外键字段在书那儿(反向) >>> book
# 一对一和多对多正反向的判断也是如此

"""
正向查询按字段
反向查询按表名小写
    表名小写_set
    ...
"""

关于.all()

正向什么时候需要加.all(),当你的结果可能有多个的时候就需要加.all();如果是一个则直接拿到数据对象。

基于对象

反向查询的时候,当你的查询结果可以有多个的时候 就必须加_set.all();当你的结果只有一个的时候 不需要加_set.all()

注意:在书写orm语句的时候跟写sql语句一样的,不要企图一次性将orm语句写完 如果比较复杂 就写一点看一点

子查询(基于对象的跨表查询)

# 1 查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
# 书查出版社 正向
res = book_obj.publish
print(res)
# nid: 1
# name: 上海出版社
# city: 上海
# email: shanghai@email.com

# 2 查询书籍主键为3的作者(多对多)
book_obj = models.Book.objects.filter(pk=3).first()
# 书查作者 正向
author = book_obj.authors
print(author)  # book.Author.None
autorAll = book_obj.authors.all()  # <QuerySet [<Author: Author object (4)>, <Author: Author object (5)>]>
for obj in autorAll:
    print(obj.name, obj.author_detail.phone) 
    # 大乔 15987567232
    # 兰陵王 13678912344
    
# 3 查询作者 阿珂信息
author_obj = models.Author.objects.filter(name='阿珂').first()
res = author_obj.author_detail
print(res)
# nid: 6
# birthday: 1998-09-12 16:00:00+00:00
# phone: 18721352234
# gender: 2
# addr: 广东广州

# 4 查询出版社是上海出版社的书
publish_obj = models.Publish.objects.filter(name='上海出版社').first()
# 出版社查书 反向
bookSet = publish_obj.book_set
bookAll = publish_obj.book_set.all().values('title', 'price')
print(bookSet)  # book.Book.None
print(bookAll)  # QuerySet 能查询到

# 5 查询作者是 大乔 写过的书
author_obj = models.Author.objects.filter(name='大乔').first()
# 作者查书  反查
bookSet = author_obj.book_set
bookAll = author_obj.book_set.all()
print(bookSet)  # book.Book.None
print(bookAll)
# <QuerySet [<Book:
#             nid: 3
#             title: Python
#             pub_date: 2022-12-11 16:00:00+00:00
#             price: 88.82
#             publish:
# nid: 3
# name: 湖南出版社
# city: 湖南
# email: hunan@qq.com
# 
#             authors: book.Author.None
# >]>

# 6 查询手机号是 13888888888 的作者信息
author_detail_obj = models.AuthorDetails.objects.filter(phone='13888888888').first()
print(author_detail_obj)
# nid: 3
# birthday: 1995-05-13 16:00:00+00:00
# phone: 13888888888
# gender: 2
# addr: 北京朝阳
res = author_detail_obj.author.name
print(res)  # 王昭君

联表查询(基于双下划线的跨表查询)

# 1 查询小满的手机号
# res = models.Author.objects.filter(name='小满').values('author_detail__phone')
# print(res)  # <QuerySet [{'author_detail__phone': '16923248772'}]>

# 反向
res = models.AuthorDetails.objects.filter(author__name='小满')  # 拿作者姓名是 小满 的作者详情
print(res)
# <QuerySet [<AuthorDetails:
# nid: 2
# birthday: 1993-12-13 16:00:00+00:00
# phone: 16923248772
# gender: 2
# addr: 云南大理
# >]>
res = models.AuthorDetails.objects.filter(author__name='小满').values('phone', 'author__name')
print(res)  # <QuerySet [{'phone': '16923248772', 'author__name': '小满'}]>

# 2 查询书籍主键为1的出版社名称和书的名称
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': 'JavaScript', 'publish__name': '上海出版社'}]>
# 反向
res = models.Publish.objects.filter(book__nid=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '上海出版社', 'book__title': 'JavaScript'}]>

# 3 查询书籍主键为3的作者姓名
res = models.Book.objects.filter(pk=3).values('authors__name')
print(res)  # <QuerySet [{'authors__name': '大乔'}, {'authors__name': '兰陵王'}]>
# 反向
res = models.Author.objects.filter(book__nid=3).values('name')
print(res)  # <QuerySet [{'name': '大乔'}, {'name': '兰陵王'}]>

# 查询主键是2的作者的手机号和姓名
res = models.Author.objects.filter(pk=2).values('name', 'author_detail__phone')
print(res)  # <QuerySet [{'name': '小满', 'author_detail__phone': '16923248772'}]>

自己的模型找自己,不需要添加__,自己的模型找其它的模型,需要添加__

进阶练习(连续跨表)

# 练习:查询 湖南出版社 关联的所有书籍的名字,以及作者的姓名
# 正向查询
res = models.Book.objects.filter(publish__name='湖南出版社').values_list('title', 'authors__name')
print(res)
# <QuerySet [('Python', '大乔'), ('Python', '兰陵王'), ('C++', None)]>  这里的None 是因为,on_delete我设置的是set null

# 反向查询
res = models.Publish.objects.filter(name='湖南出版社').values_list('book__title', 'book__authors__name')
print(res)
# <QuerySet [('Python', '大乔'), ('Python', '兰陵王'), ('C++', None)]> 这里的None 是因为,on_delete我设置的是set null

# 练习:手机号以 136 开头的作者的出版过的所有书籍名称,以及出版社名称,姓名也一起查询出来
res = models.Book.objects.filter(authors__author_detail__phone__regex="136").values_list('authors__name', 'title', 'publish__name')
print(res)  # <QuerySet [('兰陵王', 'Python', '湖南出版社')]>

res = models.Author.objects.filter(author_detail__phone__startswith='136').values('name', 'book__title', 'book__publish__name')
print(res)  # <QuerySet [{'name': '兰陵王', 'book__title': 'Python', 'book__publish__name': '湖南出版社'}]>

通过 ManyToManyField 自动创建第三张表(纯手动)了解即可

优点:第三张表可以任意拓展字段

缺点:orm 查询不方便

Django 在自动创建的多对多关系的中间表的命名方案是将相关模型的名称按字母顺序连接起来,然后附加一个下划线(_)和 "id"

下面案例中Django自动创建的第三张表在数据库中的名字为book_author1book

from django.db import models

# 书籍模型
class Book1(models.Model):
    title = models.CharField(max_length=32, verbose_name='书名')

# 作者模型
class Author1(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')

# 通过 ORM 自带的 自动创建第三张表
# 书籍和作者的关联模型
class Author1Book(models.Model):
    # 外键关联到 Author1 模型,当 Author1 对象删除时,相关的关联关系也会删除
    author = models.ForeignKey(to='Author1', on_delete=models.CASCADE)
    
    # 外键关联到 Book1 模型,当 Book1 对象删除时,相关的关联关系也会删除
    book = models.ForeignKey(to='Book1', on_delete=models.CASCADE)

    class Meta:
        # 在Django管理界面中显示为 '书籍和作者外键关联表'
        verbose_name_plural = '书籍和作者外键关联表'
        
        # 设置唯一性约束,确保一个作者只能关联一本书
        unique_together = ('author', 'book')
模型 数据库表名
Author1 book_author1
Book1 book_book1
Author1Book book_author1book

设置 ManyToManyField 并指定自行创建的第三张表(半自动)推荐使用

优点:结合自动和手动优点

缺点:多对多字段不支持增删改查:add(), remove(), set(), clear()

# 书籍模型
class Book2(models.Model):
    title = models.CharField(max_length=32, verbose_name='书名')


# 自己创建第三张表,并通过 指定关联
# 作者模型,通过自定义的第三张表 Author2Book 关联 Book2 模型
class Author2(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    books = models.ManyToManyField(to='Book2', through='Author2Book', through_fields=('author', 'book'))
    # through 是表名称
    # through_fields 接受一个2元组 ('field1', 'field2)
    # field1 是定义ManyToManyField的模型外键的名(author)
    # field2 是关联目标模型(book)的外键名


# 作者和书籍的关联中间表,用于手动控制多对多关系
class Author2Book(models.Model):
    # 外键关联到 Author2 模型,当 Author2 对象删除时,相关的关联关系也会删除
    author = models.ForeignKey(to='Author2', on_delete=models.CASCADE)

    # 外键关联到 Book2 模型,当 Book2 对象删除时,相关的关联关系也会删除
    book = models.ForeignKey(to='Book2', on_delete=models.CASCADE)

报错 on_delete 参数

TypeError: __init__() missing 1 required positional argument: 'on_delete'。

django 升级到 2.O 以后,表之间的关联,必现写上 on_delete 参数,否则会报异常。

解决方案:定义外键的时候需要加上 on_delete

即:contract = models.ForeignKey(Contract, on_delete = models.CASCADE)

自定义字段(了解即可)

from django.db import models

# Create your models here.
#Django中没有对应的char类型字段,但是我们可以自己创建
class FixCharField(models.Field):
    '''
    自定义的char类型的字段类
    '''
    def __init__(self,max_length,*args,**kwargs):
        self.max_length=max_length
        super().__init__(max_length=max_length,*args,**kwargs)

    def db_type(self, connection):
        '''
        限定生成的数据库表字段类型char,长度为max_length指定的值
        :param connection:
        :return:
        '''
        return 'char(%s)'%self.max_length
#应用上面自定义的char类型
class Class(models.Model):
    id=models.AutoField(primary_key=True)
    title=models.CharField(max_length=32)
    class_name=FixCharField(max_length=16)
    gender_choice=((1,'男'),(2,'女'),(3,'保密'))
    gender=models.SmallIntegerField(choices=gender_choice,default=3)
# 重新整理了一份
from django.db import models


class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户姓名')
    age = models.SmallIntegerField()
    password = models.CharField(max_length=32, verbose_name='用户密码')
    register = models.DateField(verbose_name='用户注册时间')

    def __str__(self):
        return f"对象:{self.username}"


class TestBook(models.Model):
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 如果指定了auto_now_add=True  字段添加的时候可以不用写
    publish_date = models.DateField(auto_now_add=True, verbose_name='出版日期')

    # 书籍和出版社是一对多
    publish = models.ForeignKey(to='TestPublish', on_delete=models.CASCADE)
    # 多对多  书籍和作者是多对多
    # 会自动创建第三张表  如果使用 ManyToManyField
    authors = models.ManyToManyField(to='TestAuthor')


class TestPublish(models.Model):
    name = models.CharField(max_length=32, verbose_name='出版社名称')
    addr = models.CharField(max_length=255, verbose_name='出版社地址')
    email = models.EmailField(verbose_name='出版社联系邮箱')

    # Email这个字段不是给models看的 而是给我们后续要学习到的校验组件看的

    def __str__(self):
        return f"对象:{self.name}"


class TestAuthor(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    age = models.SmallIntegerField(verbose_name='作者年龄')

    # 一对一  作者和作者详情是一对一
    # 注意 外键 以及 一对一 都需要设置 on_delete 才能正常迁移数据库成功
    author_detail = models.OneToOneField(to='TestDetail', on_delete=models.CASCADE)


class TestDetail(models.Model):
    phone = models.BigIntegerField(verbose_name='作者电话号码')  # 电话号码使用BigIntegerField或者CharField 更保险一点
    addr = models.CharField(max_length=255, verbose_name='作者地址')

auto_now和auto_now_add

auto_now: 每次操作数据的时候,该字段会自动将当前事件根棍

auto_now_add:在创建字段的时候会自动将当前事件记录下来,后续只要不人为修改,那么就一直不变。【推荐】比较常用,比如用户的注册时间。

pk即主键

pk会自动查找到当前表的主键字段 指代的就是当前表的主键字段用了pk之后 你就不需要指代当前表的主键字段到底叫什么了

get方法(不推荐)

直接返回的就是当前对象, 但是,该方法不推荐使用,因为一旦数据不存在,会直接报错。

而filter则不会,如果filter数据不存在,则返回一个空 可以用布尔去判断进一步处理

obj = models.User.objects.get(pk=6)
print(obj)  # User object (6)  这里的object就是类实例化的对象

save

obj = models.User.objects.filter(pk=7).first()
obj.username = "老夫子"
obj.save()  # 记得保存 不然不生效

xx.objects必知必会13条


all()  # 获取所有
filter() # 带有条件的过滤查询
get()  # 直接拿对象 但对象不存在就报错
first() # 拿queryset里面的第一个元素
last() # 拿queryset里面的最后一个元素
values() #  获取数据的指定字段 类似于 select name, age from ...  返回结果 列表套字典
values_list() # 同上 结果是列表套元组
distinct()  # 去重
order_by()  # 排序 默认是升序 如果要降序字段加一个减号就行了
reverse()   # 反转 反转的前提是已经排过序了
count()  # 统计当前表全部记录的总个数
exclude()  # 把某字段排除再外
exists()  #  判断是否存在  返回的是布尔值 基本用不到 因为数据本身就自带布尔值

values

obj = models.User.objects.values('username', 'password')
print(obj) # <QuerySet [{'username': '小满', 'password': '123456'}, {'username': '老夫子', 'password': '123456'}, {'username': '阿珂', 'password': '123456'}]>

obj = models.User.objects.values('username', 'password').first()
print(obj)  # {'username': '小满', 'password': '123456'}
print(type(obj))  # <class 'dict'>

values_list

obj = models.User.objects.values_list('username', 'password').first()
print(obj)  # ('小满', '123456')
print(type(obj))  # <class 'tuple'>

query

查看内部封装的sql语句,只有queryset对象才支持,也就是这个方法如果不是queryset对象,无法使用

obj = models.User.objects.values_list('username', 'password')
print(obj.query) # SELECT `testdemo_user`.`username`, `testdemo_user`.`password` FROM `testdemo_user`

print('完成')

distinct

去重,一定要是一模一样的数据如果带有主键那么肯定不一样 你在往后的查询中一定不要忽略主键

obj = models.User.objects.all()
print(obj)  # <QuerySet [<User: 对象:小满>, <User: 对象:老夫子>, <User: 对象:阿珂>, <User: 对象:小满>]>
obj = models.User.objects.values('username', 'password').distinct()
print(obj)  # <QuerySet [{'username': '小满', 'password': '123456'}, {'username': '老夫子', 'password': '123456'}, {'username': '阿珂', 'password': '123456'}]>

order_by

obj = models.User.objects.order_by('-age')
obj = models.User.objects.order_by('age')

reverse

obj = models.User.objects.all()
print(obj)
obj = models.User.objects.all().reverse()
print(obj)
# 上面结果一样
obj = models.User.objects.order_by('-age').reverse()
print(obj)
obj = models.User.objects.order_by('age').reverse()
print(obj)
# 排序了 结果不一样

count

count = models.User.objects.count()
print(count) # 7

exclude

obj = models.User.objects.exclude(username='小满')
print(obj) # <QuerySet [<User: 对象:老夫子>, <User: 对象:阿珂>, <User: 对象:大乔>, <User: 对象:海月>, <User: 对象:小乔>]>

exists

result = models.User.objects.filter(pk=22).exists()
print(result)  # False

一对多外键增删改查

# 增
# 方式1 直接写实际字段id
models.TestBook.objects.create(title='三国演义', price=123.23, publish_id=1)
# 方式2 虚拟字段 放对象
# 这里先把对象查询出来
publish_obj = models.TestPublish.objects.filter(pk=2).first()
# #                                      然后这里直接放对象就可以了,会自动找到对应的主键值插入到数据库里面
models.TestBook.objects.create(title='水浒传', price=882.24, publish=publish_obj)

# 删  级联更新级联删除的 模型表里面设置的是on_delete=models.CASCADE
models.TestPublish.objects.filter(pk=1).delete()  # 级联删除

# 改
# 方式1 一样直接写入实际字段id
models.TestBook.objects.filter(pk=5).update(publish_id=1)
# 方式2 一样根据数据对象去更新
publish_obj = models.TestPublish.objects.filter(pk=2).first()
models.TestBook.objects.filter(pk=6).update(publish=publish_obj)

多对多的增删改查

多对多的增删改查就是在操作第三张表,说的再直白一点,多对多的增删改查就是第三张关系表的增删改查

models.TestBook.objects.create(title='山海经', price=123.23, publish_id=1)
models.TestBook.objects.create(title='论语', price=345.23, publish_id=2)
models.TestBook.objects.create(title='活着', price=666.23, publish_id=1)
models.TestBook.objects.create(title='海边的卡夫卡', price=888, publish_id=2)

# 如何给书籍添加作者
# 先创建一个书籍对象
book_obj = models.TestBook.objects.filter(pk=5).first()
print(book_obj.authors)  # 这么写就类似于已经到了第三张关系表了
# testdemo.TestAuthor.None
book_obj.authors.add(2)  # 这句话的意思就是给书籍主键为5的书籍绑定作者主键为2的作者
# add  理解为去第三张表里面添加一个关系
book_obj.authors.add(1, 2, 3)  # 因为书籍和作者是多对多的关系,这句话的意思就是给这本书籍绑定3个作者

# 同样支持放对象
author_obj = models.TestAuthor.objects.filter(pk=1).first()
author_obj1 = models.TestAuthor.objects.filter(pk=2).first()
author_obj2 = models.TestAuthor.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
# 同样支持写多个
book_obj.authors.add(author_obj1, author_obj2)
"""
    add 给第三张关系表添加数据
        括号内即支持传数字 也支持传对象 并且支持多个
"""

# 删
book_obj.authors.remove(2)
# 一样支持传多个
book_obj.authors.remove(2, 3)
# 也可以根据对象去删除
author_obj1 = models.TestAuthor.objects.filter(pk=1).first()
book_obj.authors.remove(author_obj1)

"""
remove
    括号内即支持传数字 也支持传对象 并且支持多个
"""
book_obj = models.TestBook.objects.filter(pk=7).first()
# 修改 通过set  括号内必须给一个可迭代对象
# 会影响主键 原理就是把之前的删掉 然后新增进去
book_obj.authors.set([2])
book_obj.authors.set([1, 2, 3])
# 同样支持放对象
author_obj1 = models.TestAuthor.objects.filter(pk=2).first()
author_obj2 = models.TestAuthor.objects.filter(pk=3).first()
book_obj.authors.set([author_obj2, author_obj1])

"""
set
    括号内必须传一个可迭代对象(元组或字典),该对象内即支持传数字 也支持传对象 并且支持多个
"""

# 清空
# 在第三张表中清空某个书籍与作者的绑定关系 比如这本书永久下架了
book_obj.authors.clear()

"""
clear
    括号内 什么关系都不要放,它是直接去第三张表里面,把当前对象(案例中的这边书)所有的绑定关系都清空掉
"""

正反向

# 正向
# 反向
外键字段在我手上,我查你就是正向
外键字段不在我受伤,我查你就是反向

book ===> 外键字段在书那儿(正向) ===> publish
publish ===> 外键字段在书那儿(反向) === book

一对一和多对多的正反向判断也是如此

"""
正向查询按字段
反向查询按表名小写
			_set()
			...
"""

多表查询

子查询(变成了基于对象的跨表查询)

# 查询书籍为5对应的出版社
# 1. 书籍对象
book_obj = models.TestBook.objects.filter(pk=5).first()
# 2. 通过书籍对象去查
publish_obj = book_obj.publish
print(publish_obj)
print(publish_obj.name)
print(publish_obj.addr)

# 查询书籍主键5对应的作者
print(book_obj.authors.all())  # <QuerySet [<TestAuthor: TestAuthor object (2)>]>

# 查询作者大乔的电话号码
author_obj = models.TestAuthor.objects.filter(name='大乔').first()
res = author_obj.author_detail
print(res)  # TestDetail object (2)
print(res.phone)  # 120
print(res.addr)  # 山东

"""
在写orm语句的时候 跟写sql语句是一样的
不要企图一次性写完,如果比较复杂,就写一点看一点

正向什么时候需要加.all()
    当你拿到的结果可能是多个的时候,就要加.all()
    如果是一个则直接拿到数据对象
    book_obj.publish
    book_obj.authors.all()
    author_obj.author_detail
"""

# 查询出版社 是东方出版社的书
# 要查询什么 就先拿到查询第一个条件的对象
publish_obj = models.TestPublish.objects.filter(name='东方出版社').first()
# 出版社查书 反向 表名小写
res = publish_obj.testbook
print(res) # AttributeError: 'TestPublish' object has no attribute 'testbook'
res = publish_obj.testbook_set
print(res) # testdemo.TestBook.None
print(res.all())  # QuerySet 数量3
for item in res.all():
    print(item.title)

# 查询作者是大乔写过的书
author_obj = models.TestAuthor.objects.filter(name='大乔').first()
res = author_obj.testbook_set
print(res.all())  # <QuerySet [<TestBook: TestBook object (5)>]>

# 查询手机号是110的作者姓名
detail_obj = models.TestDetail.objects.filter(phone=110).first()
res = detail_obj.testauthor.name
print(res)
"""
什么时候要加.set_all()
基于对象    
    反向查询的时候
        当你的查询结果可以有多个的时候,就必须加_set.all()
        当你的结果只有一个的时候,不需要加_set.all()
        // 一般情况下 1对1的反向查询是不需要加_set.all()的
"""

联表查询(变成了基于双下划线的跨表查询)

双下划线可以一行代码拿到结果

# 查询作者大乔的手机号和姓名
res = models.TestAuthor.objects.filter(name='大乔').values('author_detail__phone', 'name')
print(res)  # <QuerySet [{'author_detail__phone': 120, 'name': '大乔'}]>
# 反向
res = models.TestDetail.objects.filter(testauthor__name='大乔').values('phone', 'testauthor__name')
print(res)  # <QuerySet [{'phone': 120, 'testauthor__name': '大乔'}]>
# 查询书籍主键为5的书籍名称和出版社名称
res = models.TestBook.objects.filter(pk=5).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': '水浒传', 'publish__name': '东方出版社'}]>


# 查询书籍为5的作者姓名
res = models.TestBook.objects.filter(pk=5).values('authors__name')
print(res)  # <QuerySet [{'authors__name': '大乔'}]>

# 反向
res = models.TestAuthor.objects.filter(testbook__id=5).values('name')
print(res)  # <QuerySet [{'name': '大乔'}]>

# 查询书籍主键是5的作者的手机号
# 三张表 author book detail
res = models.TestBook.objects.filter(pk=5).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 120}]>
posted @ 2024-03-23 00:49  小满三岁啦  阅读(2)  评论(0编辑  收藏  举报