5.Django 数据库多表查询

1. 测试环境搭建

1.1 创建项目

image-20220307192312533

1.2 解决问题
0. 解决templates 路径拼接问题
1. 注释csrf中间键
1.3 数据库配置
* 连接以及存在的MySQL数据库
0. Navicat 中创建 一个 OMR .
1. settings 配置文件中第75行中 设置 mysql数据的连接信息
2. 导入pymysql模块, 设置pymysql 连接 mysql.

image-20220307193444021

DATABASES = {
    'default': {
        # 0. 连接mysql数据库
        'ENGINE': 'django.db.backends.mysql',
        # 1. 连接的数据库中的库
        'NAME': 'ORM',
        # 2. IP
        'HOST': '127.0.0.1',
        # 3. 端口
        'POST': '3306',
        # 4. 用户名
        'USER': 'root',
        # 5. 用户密码
        'PASSWORD': '123',
        # 6. 数据的编码格式
        'CHARSET': 'utf8',
    }
}
# app01 应用下的__init__.py
import pymysql
pymysql.install_as_MySQLdb()
1.4 Pycharm连接数据库

image-20220307195142494

先测试成功在连接.

image-20220307195331297

1.5 测试脚本
将下面代码复制到app01 应用下的tests.py 文件中.
from django.test import TestCase

import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
    import django

    django.setup()

    # 在这个代码会下面就可以测试django里面的单个py文件
    # 所有代码必须等待环境准备完毕之后才能书写
    from app01 import models  # 这行代码是不允许在配置文件外面

2. 单表操作

2.1 创建表单
在app01下的model.py创建User表。
models.DateField()   日期字段 年月日
register_time = models.DateTimeField()   # 日期字段 年月日 时分秒

时间字段参数:
auto_now: 每次操作数据的时候,该字段会自动将当前的时间更新,操作数据的时间.
auto_now_add:创建数据的时候会自动将创建的时间记录下来,不认为修改就不会改变.
# 0. 用户表
class User(models.Model):
    # 1. id字段 自动增加
    # 2. 用户名
    name = models.CharField(max_length=32, verbose_name='用户名')
    # 3. 年龄
    age = models.IntegerField()
    # 4. 注册时间 不给register_time字段设置值的时候会自动获取当前时间
    register_time = models.DateTimeField(auto_now_add=True)
2.2 数据库迁移
在终端输入:

python manage.py makemigrations
python manage.py migrate

image-20220307222746258

2.3 增加数据
在测试文件tests.py 中写
    # 方式1:
    res1 = models.User.objects.create(name='kid', age=18)

    # res1 返回值就是对象本身。

image-20220307223759555

    # 方式2
    import datetime
    ctime = datetime.datetime.now()
    user_obj = models.User(name='qz', age=18, register_time=ctime)
    user_obj.save()
2.4 删除数据
一般使用主键作为查询依据。
    # 方式1 直接按id字段查找
    res2 = models.User.objects.filter(id=1).delete()
    # 返回值 res3  是当前影响的行数
    # 方式1 pk 查询
    res3 = models.User.objects.filter(pk=2).delete()
pk会自动查找当前表的主键字段,不需要指代当前表的主键字段到底叫什么 uid pid sid.
    # 方式2
    user_obj = models.User.objects.filter(pk=3).first()
    user_obj.delete()
2.5 修改值
    # 方式1
    models.User.objects.filter(pk=4).update(name='qaz')
    # 方式2
    user_obj = models.User.objects.filter(pk=4)
    user_obj.update(name='qqq')
    # get取值
    user_obj = models.User.objects.get(pk=4)
    print(user_obj)
    user_obj.name = 'xxxx'
    user_onj.save()
filter    取不存在的值为空,不会报错
get       取不存在的值会报错 用户匹配查询不存在。
		 app.models.DoesNotExist: User matching query does not exist.

3. 展示SQL语句

3.1 方法查看
QuerySet对象内部封装的sql语句。
querset对象.query
# 方式1 只有QuerySet对象才能query查看内部sql语句

res1 = models.User.objects.all()
print(res1, type(res1))
# <QuerySet [<User: User object>]> <class 'django.db.models.query.QuerySet'>

print(res1.query)
# SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user`
3.2 配置文件
将下面代码复制到settings.py配置文件中。随便找个位置放。
所有的ORM语句都能自动触发打印sql语句。
# 方式二 
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

4. objects 方法

0. objects是Manager类型的对象,是Model和数据库进行查询的接口.
1. all()          查询所有数据, 结果是queryset对象,列表套套字典.
2. get_queryset() 获取所有queryset对象数据,结果是queryset对象,列表套套元组.
* 结果一样的 
3. filter()       过滤条件查询,结果是queryset对象,列表套数据对象.
4. get()          直接拿数据对象,但是条件不存在直接报错.
5. first()        表中第一条数据,结果是一个queryset对象.
6. last()         表中最后一个数据,结果是一个queryset对象.
7. values()       指定获取的字段,返回该字段所有的值,结果是列表套字典. 
8. valuce_list()  指定获取的字段,返回该字段所有的值,是列表套元组.
9. distinct()     去重
10. order_by()    排序
11. reverse()     倒序
12. count()       统计
13. exclude()     排除
14. excsts()      判断值是否存在
# 在models.py中User类中添加__str__ 方法
    def __str__(self):
        # 打印User实例化对象时展示 name age register_time 的信息
        return '%s %s %s' % (self.name, self.age, self.register_time)
为表添加几条数据用于测试.

image-20220307232201511

4.1 objects对象
objects是Manager类型的对象.
    user_obj = models.User.objects
    print(user_obj, type(user_obj))
    # app01.User.objects <class 'django.db.models.manager.Manager'>
    # 不查询表不执行SQl语句
4.2 获取数据对象
all() get_queryset()
查询所有数据, 结果是列表套queryset对象.
    user_obj = models.User.objects.all()
    print(user_obj, type(user_obj))
    # <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, ...'> <class 'django.db.models.query.QuerySet'>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` LIMIT 21; args=()
    user_obj = models.User.objects.get_queryset()
    print(user_obj, type(user_obj))
    # <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, ...'> <class 'django.db.models.query.QuerySet'>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` LIMIT 21; args=()
4.3 过滤条件
filter()       过滤条件查询,结果是queryset.
get()          直接拿数据对象,但是条件不存在直接报错.
    user_obj = models.User.objects.filter()
    print(user_obj, type(user_obj))
    # <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, ...'> <class 'django.db.models.query.QuerySet'>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` LIMIT 21; args=()
    user_obj = models.User.objects.filter(pk=5)
    print(user_obj, type(user_obj))
    # <QuerySet [<User: ww 20 2022-03-27 23:21:04+00:00>]> <class 'django.db.models.query.QuerySet'>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`id` = 5 LIMIT 21; args=(5,)
    user_obj = models.User.objects.get(pk=5)
    print(user_obj, type(user_obj))
	# ww 20 2022-03-27 23:21:04+00:00 <class 'app01.models.User'>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`id` = 5; args=(5,)
4.4 头尾的值
first()        表单中第一条数据,元素queryset对象的值, 结果是表的数据对象.
last()         表单中最后一个元素, 结果是表的数据对象.
    user_obj = models.User.objects.first()
    print(user_obj, type(user_obj))
    # kid 18 2022-03-07 23:18:44+00:00 <class 'app01.models.User'>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` ORDER BY `app01_user`.`id` ASC LIMIT 1; args=()
    user_obj = models.User.objects.last()
    print(user_obj, type(user_obj))
    # qq 22 2022-03-07 23:21:29+00:00 <class 'app01.models.User'>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` ORDER BY `app01_user`.`id` DESC LIMIT 1; args=()
4.5 获取值
values():指定获取的字段,返回该字段所有的值,结果是列表套字典. 
values_list():指定获取的字段,返回该字段所有的值,是列表套元组.
* 不指定字段获取所有字段的数据
    user_obj = models.User.objects.values('name')
    print(user_obj, type(user_obj))
    # <QuerySet [{'name': 'kid'}, {'name': 'qq'}, {'name': 'ww'}, {'name': 'qq'}]> <class 'django.db.models.query.QuerySet'>
(0.001) SELECT `app01_user`.`name` FROM `app01_user` LIMIT 21; args=()
    user_obj = models.User.objects.values_list('name', 'age')
    print(user_obj, type(user_obj))
    # <QuerySet [('kid', 18), ('qq', 19), ('ww', 20), ('qq', 22)]> <class 'django.db.models.query.QuerySet'>
(0.001) SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` LIMIT 21; args=()
* 不指定字段获取所有字段的数据
    user_obj = models.User.objects.values()
    print(user_obj)
    # <QuerySet [{'id': 3, 'name': 'kid', 'age': 18, 'register_time': datetime.datetime(2022, 3, 7, 23, 18, 44, tzinfo=<UTC>)}, ...]>
    user_obj = models.User.objects.values_list()
    print(user_obj)
    # <QuerySet [(3, 'kid', 18, datetime.datetime(2022, 3, 7, 23, 18, 44, tzinfo=<UTC>)), (4, 'qq', 19, datetime.datetime(2022, 3, 7, 23, 19, 44, tzinfo=<UTC>)), ...]>
# 上面两个的代码的SQL语句一样的.
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` LIMIT 21; args=()
4.6 去重
先准备下相同的数据,id不要设置成一样的,这张表的id是唯一的.
id name  age register_time
7   kid   18  2021-08-16 00:00:00.000000
8   kid   18  2021-08-16 00:00:00.000000

image-20220308005524469

去重一定要是一模一样的数据,
如果带有主键那么肯定是不一样的,在查询中注意主键。
    user_obj = models.User.objects.values().distinct()  # values不添加添加默认获取所有值
    print(user_obj, type(user_obj))
    # <QuerySet [{'id': 3, 'name': 'kid', 'age': 18, 'register_time': datetime.datetime(2022, 3, 7, 23, 18, 44, tzinfo=<UTC>)}, ...}]> <class 'django.db.models.query.QuerySe
(0.001) SELECT DISTINCT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` LIMIT 21; args=()
    user_obj = models.User.objects.values('name').distinct()  
    print(user_obj, type(user_obj))
    # <QuerySet [{'name': 'kid'}, {'name': 'qq'}, {'name': 'ww'}, {'name': 'zz'}]> <class 'django.db.models.query.QuerySet'>
(0.000) SELECT DISTINCT `app01_user`.`name` FROM `app01_user` LIMIT 21; args=()
    user_obj = models.User.objects.values('name', 'age').distinct()  
    print(user_obj, type(user_obj))
    # <QuerySet [{'name': 'kid', 'age': 18}, {'name': 'qq', 'age': 19}, {'name': 'ww', 'age': 20}, {'name': 'qq', 'age': 22}, {'name': 'zz', 'age': 18}]> <class 'django.db.models.query.QuerySet'>
(0.000) SELECT DISTINCT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` LIMIT 21; args=()
如果id不是唯一的可以这样测试下.
models.User.objects.distinct()
4.7 排序
.order_by() 指定排序的字典 不指定就以主键
默认为升序
降序 在字段前面加上-负号

数字按从小到大排序
字符按子母表排序
    # 升序
    user_obj = models.User.objects.order_by('age')
    print(user_obj, type(user_obj))
<QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, 
            <User: zz 18 2021-08-16 00:00:00+00:00>, 
            <User: zz 18 2021-08-16 00:00:00+00:00>, 
            <User: qq 19 2022-03-07 23:19:44+00:00>,
            <User: ww 20 2022-03-27 23:21:04+00:00>, 
            <User: qq 22 2022-03-07 23:21:29+00:00>]>
<class 'django.db.models.query.QuerySet'>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` ORDER BY `app01_user`.`age` ASC LIMIT 21; args=()
    # 降序 在字段前面加上-负号
    user_obj = models.User.objects.order_by('-age')
    print(user_obj, type(user_obj))
<QuerySet [<User: qq 22 2022-03-07 23:21:29+00:00>, 
    <User: ww 20 2022-03-27 23:21:04+00:00>, 
    <User: qq 19 2022-03-07 23:19:44+00:00>,
    <User: kid 18 2022-03-07 23:18:44+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>, 
    <User: zz 18 2021-08-16 00:00:00+00:00>]> 
<class 'django.db.models.query.QuerySet'>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` ORDER BY `app01_user`.`age` DESC LIMIT 21; args=()
4.7 倒序
.order_by().reverse()数据必须是排过序的。
    # 先排序在倒序
    user_obj = models.User.objects.order_by('age').reverse()
    print(user_obj, type(user_obj))
<QuerySet [<User: qq 22 2022-03-07 23:21:29+00:00>,
    <User: ww 20 2022-03-27 23:21:04+00:00>,
    <User: qq 19 2022-03-07 23:19:44+00:00>, 
    <User: kid 18 2022-03-07 23:18:44+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>, 
    <User: zz 18 2021-08-16 00:00:00+00:00>]> 
<class 'django.db.models.query.QuerySet'>
4.8 统计
count()  统计当前数据的个数。
    user_obj = models.User.objects.count()
    print(user_obj)
    # 6 
(0.000) SELECT COUNT(*) AS `__count` FROM `app01_user`; args=()
4.9 排除
.exclude()  排除指定的条件
    user_obj = models.User.objects.exclude(name='kid')
    print(user_obj)
    # <QuerySet [<User: qq 19 2022-03-07 23:19:44+00:00>, 
    <User: ww 20 2022-03-27 23:21:04+00:00>, 
    <User: qq 22 2022-03-07 23:21:29+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE NOT (`app01_user`.`name` = 'kid') LIMIT 21; args=('kid',)
4.10 判断值是否存在
.exists()返回值为布尔值。
    user_obj = models.User.objects.filter(name='kid').exists()
    print(user_obj)
    # True
(0.000) SELECT (1) AS `a` FROM `app01_user` WHERE `app01_user`.`name` = 'kid' LIMIT 1; args=('kid',)

5. 字段方法

5.1 双向方法
搭配字段使用 字段__xx
1. __gt             大于
2. ——lt             小于
3. __gte            大于等于
4. __lte            小于等于
5. __in[x1, x2]     查询多个值x1 和x2
6. ——range[x1, x9]  查询x1 - x9 之间所有值
7. __contains=' '   模糊查询,区分大小写
8. __icontains=' '  模糊查询,不区分大小写
9. __starswith=' '  以什么开头
10. __endwith=' '   以什么结尾
11. __day=          
12. __month=        
13. __year=         
14. __week_day=     星期几
5.2 练习

image-20220310130013938

 1. 查询年龄大于20岁的数据
 	user_obj = models.User.objects.filter(age__gt=20)
    print(user_obj)
    # <QuerySet [<User: qq 22 2022-03-07 23:21:29+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`age` > 20 LIMIT 21; args=(20,)
 2. 查询年龄小于20岁的数据
	user_obj = models.User.objects.filter(age__lt=20)
	print(user_obj)
    <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>,
    <User: qq 19 2022-03-07 23:19:44+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`age` < 20 LIMIT 21; args=(20,)
 3. 查询年龄在1820岁的数据
	user_obj = models.User.objects.filter(age__in=[18, 20])
    print(user_obj)
    <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>,
    <User: ww 20 2022-03-27 23:21:04+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`age` IN (18, 20) LIMIT 21; args=(18, 20)
 4. 查询18岁到22岁的数据
	user_obj = models.User.objects.filter(age__range=[18, 22])
    print(user_obj)
    <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, 
    <User: qq 19 2022-03-07 23:19:44+00:00>, 
    <User: ww 20 2022-03-27 23:21:04+00:00>,
    <User: qq 22 2022-03-07 23:21:29+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>,
    <User: zz 18 2021-08-16 00:00:00+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`age` BETWEEN 18 AND 22 LIMIT 21; args=(18, 22) 
 5. 查询名字中有k的数据
    user_obj = models.User.objects.filter(name__contains='k')
    print(user_obj)
    # <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>]>
(0.008) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`name` LIKE BINARY '%k%' LIMIT 21; args=('%k%',)
 6. 查询名字中有k的数据(忽略大小写)
    user_obj = models.User.objects.filter(name__icontains='K')
    print(user_obj)
	# <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`name` LIKE '%K%' LIMIT 21; args=('%K%',)
 7. 查询注册时间是2022年的数据
    user_obj = models.User.objects.filter(register_time__year=2022)
    print(user_obj)
    # 数据是字符串类型
    user_obj = models.User.objects.filter(register_time__year='2022')
    print(user_obj)
    <QuerySet [<User: kid 18 2022-03-07 23:18:44+00:00>, 
    <User: qq 19 2022-03-07 23:19:44+00:00>, 
    <User: ww 20 2022-03-27 23:21:04+00:00>,
    <User: qq 22 2022-03-07 23:21:29+00:00>]>
(0.000) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`register_time` BETWEEN '2022-01-01 00:00:00' AND '2022-12-31 23:59:59.999999' LIMIT 21; args=('2022-01-01 00:00:00', '2022-12-31 23:59:59.999999')
 8. 查询注册时间是3月的数据
    user_obj = models.User.objects.filter(register_time__month=3)
    print(user_obj)
    <QuerySet [<User: kid 18 2022-03-07 00:00:00>, 
    <User: qq 19 2022-03-07 00:00:00>,
    <User: ww 20 2022-03-27 00:00:00>, 
    <User: qq 22 2022-03-07 00:00:00>]>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE EXTRACT(MONTH FROM `app01_user`.`register_time`) = 3 LIMIT 21; args=(3,)
如果查不出来直接在setting.py文件中设置:USE_TZ = False

image-20220308201015413

 9. 查询注册时间是27号的数据
    user_obj = models.User.objects.filter(register_time__day=27)
    print(user_obj)
	# <QuerySet [<User: ww 20 2022-03-27 00:00:00>]>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE EXTRACT(DAY FROM `app01_user`.`register_time`) = 27 LIMIT 21; args=(27,)
 10.查询注册时间是星期天的数据
星期天是1  星期一是2
    user_obj = models.User.objects.filter(register_time__week_day=1)
    print(user_obj)
    # <QuerySet [<User: ww 20 2022-03-27 00:00:00>]>
(0.001) SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE DAYOFWEEK(`app01_user`.`register_time`) = 1 LIMIT 21; args=(1,)

6. 多表操作之增删改

* 一对多,一对一 和普通一张表的增删改是一样的
* 多对多,通过虚拟字段使用 .add() .set([]) .remove(). clear() 这几个方法
6.1 表数据
1.书籍表:
id  title      price   publish_date
...  ...       ...     ...

2.出版社表:
id     name        addr  email
1    上海出版社    上海  123@qq.com
2    北京出版社    北京  456@qq.com
.....


3.作者表
id  name  age   author_datail_id
1   kid   18      1
2   qq	  19      2
3   qaq	  20      3
.....

4.作者详情表:
id phone a  ddr
1   110	    北京
2   112     上海		
3   119     深圳
.....
6.2 建立基础表
# 1. 书籍表
class Book(models.Model):
    # 1. id 字段 自动添加
    # 2. 书名字段
    title = models.CharField(max_length=32, verbose_name='书名')
    # 3. 价格 共八位 小数占两位
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    # 4. 出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='出版时间')
    

# 2. 出版社表
class Publish(models.Model):
    # 1. id 字段 自动添加
    # 2. 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字')
    # 3. 地址
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    # 4. 邮箱
    email = models.EmailField(verbose_name='邮箱')


# 3. 作者表
class Author(models.Model):
    # 1. id 字段 自动添加
    # 2. 作者名字
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 3. 作者姓名
    age = models.IntegerField(verbose_name='作者年龄')


# 4. 作者详情表
class AuthorDetail(models.Model):
    # 1. id 字段 自动添加
    # 2. 手机
    phone = models.BigIntegerField()
    # 地址
    addr = models.CharField(max_length=32, verbose_name='作者地址')
6.3 外键字段
* 创建的字段会自动加_id
# 1. 书籍表
class Book(models.Model):
    # 1. id 字段 自动添加
    # 2. 书名字段
    title = models.CharField(max_length=32, verbose_name='书名')
    # 3. 价格 共八位 小数占两位
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    # 4. 出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='出版时间')
    # 5. 外键字段
    # 5.1 出版社一对多书籍表, 外键建立在多的一方 
    publish = models.ForeignKey(to='Publish')
    # 5.2 作者表多对多书籍表, 虚拟字典在查询频率高的一方
    author = models.ManyToManyField(to='Author')


# 2. 出版社表
class Publish(models.Model):
    # 1. id 字段 自动添加
    # 2. 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字')
    # 3. 地址
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    # 4. 邮箱
    email = models.EmailField(verbose_name='邮箱')


# 3. 作者表
class Author(models.Model):
    # 1. id 字段 自动添加
    # 2. 作者名字
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 3. 作者姓名
    age = models.IntegerField(verbose_name='作者年龄')
    # 4. 外键 作者表一对一作者详情表
    author_detail_id = models.OneToOneField(to='AuthorDetail')


# 4. 作者详情表
class AuthorDetail(models.Model):
    # 1. id 字段 自动添加
    # 2. 手机
    phone = models.BigIntegerField()
    # 3. 地址
    addr = models.CharField(max_length=32, verbose_name='作者地址')

image-20220308222756031

为每个表设置__str__ 方法.
# 1. 书籍表
class Book(models.Model):
    # 1. id 字段 自动添加
    # 2. 书名字段
    title = models.CharField(max_length=32, verbose_name='书名')
    # 3. 价格 共八位 小数占两位
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    # 4. 出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='出版时间')
    # 5. 外键字段
    # 5.1 出版社一对多书籍表, 外键建立在多的一方
    publish = models.ForeignKey(to='Publish')
    # 5.2 作者表多对多书籍表, 虚拟字典在查询频率高的一方
    author = models.ManyToManyField(to='Author')

    def __str__(self):
        # 打印Book实例化对象时展示所有字段的信息
        return '%s %s %s %s %s %s' % (self.id, self.title, self.price, self.publish_date, self.publish, self.author)


# 2. 出版社表
class Publish(models.Model):
    # 1. id 字段 自动添加
    # 2. 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字')
    # 3. 地址
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    # 4. 邮箱
    email = models.EmailField(verbose_name='邮箱')

    def __str__(self):
        # 打印Publish表实例化对象时展示所有字段的信息
        return '%s %s %s %s' % (self.id, self.name, self.addr, self.email)


# 3. 作者表
class Author(models.Model):
    # 1. id 字段 自动添加
    # 2. 作者名字
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 3. 作者姓名
    age = models.IntegerField(verbose_name='作者年龄')
    # 4. 外键 作者表一对一作者详情表
    author_detail = models.OneToOneField(to='AuthorDetail')

    def __str__(self):
        # 打印Author表实例化对象时展示所有字段的信息
        return '%s %s %s %s' % (self.id, self.name, self.age, self.author_detail)


# 4. 作者详情表
class AuthorDetail(models.Model):
    # 1. id 字段 自动添加
    # 2. 手机
    phone = models.BigIntegerField()
    # 3. 地址
    addr = models.CharField(max_length=32, verbose_name='作者地址')

    def __str__(self):
        # 打印AuthorDetail表实例化对象时展示所有字段的信息
        return '%s %s %s' % (self.id, self.phone, self.addr)
6.4 数据库迁移
python manage.py makemigrations
python manage.py migrate

image-20220308220027992

6.5 录数据
在tests.py  中录数据
1. 作者详情表
4.作者详情表:
id phone    addr
1   110	    北京
2   112     上海		
3   119     深圳
	models.AuthorDetail.objects.create(phone=110, addr='北京')
	models.AuthorDetail.objects.create(phone=112, addr='上海')
	models.AuthorDetail.objects.create(phone=119, addr='深圳')

image-20220308221852159

2.作者表
3.作者表
id  name  age   author_detail_id
1   kid   18      1
2   qq	  19      2
3   qaq	  20      3
    models.Author.objects.create(name='kid', age=18, author_detail_id=1)
    models.Author.objects.create(name='qq', age=19, author_detail_id=2)
    models.Author.objects.create(name='qaq', age=20, author_detail_id=3)

image-20220308222936430

3. 出版社表
2.出版社表:
id     name        addr  email
1    上海出版社    上海  123@qq.com
2    北京出版社    北京  456@qq.com
    models.Publish.objects.create(name='上海出版社', addr='上海', email='123@qq.com')
    models.Publish.objects.create(name='北京出版社', addr='北京', email='456@qq.com')

image-20220308223414790

4.数据表
1.书籍表:
id  title             price   publish_deta        publish_id
1   开局签到荒古圣体   123.33  创建表的时候自动添加    1
2   洪荒之至尊通天     123.33                        1
3   重生之都市仙尊     123.33                        2
4   穿越诸天万界       123.33                        2
    models.Book.objects.create(title='开局签到荒古圣体', price=123.33, publish_id=1)
    models.Book.objects.create(title='洪荒之至尊通天', price=123.33, publish_id=1)
    models.Book.objects.create(title='重生之都市仙尊', price=123.33, publish_id=2)
    models.Book.objects.create(title='穿越诸天万界', price=123.33, publish_id=2)

image-20220308224435733

6.6 一对多外键操作
一对一  一对多的操作和普通的表操作是一样的。
 objects.各种方法
 objects.create(字段=)
 object.各种方法.delete()
 objects.各种方法.update()

objects.filter查的结果是一个queryset对象 是一个列表套字典 ,字典中是一个数据对象.
objects.filter.first()将这个数据对象取出来.
外键字段不带_id 它的值可以是一个对象, 也可以是一个表的之间值.
外键字段带_id= 表的主键值, publish不带_id 它的值不能就是表的主键值.
1. 增
添加书籍数据  并设置外键 绑定出版社.

image-20220308231108560

    # 方式1: 书籍表  对应出版社的外键绑定出版社的id
	models.Book.objects.create(title='现在开始梁山我说了算', price=555.05, publish_id=1 )

image-20220308231154356

    # 方式二: 书籍表  对应出版社的外键绑定出版社的数据值
    # 1. 获取出版社的数据对象
    publish_obj = models.Publish.objects.filter(pk=1).first()
    print(publish_obj)  # 1 上海出版社 上海 123@qq.com

    # 2. 为书籍表添加数据                                              publish 外键字段
    models.Book.objects.create(title='带着仓库到大明', price='666.06', publish=publish_obj)
publish=publish_obj publish不带_id 它的值可以是一个对象, 也可以是一个表的之间值.
publish_id = 表的主键值, publish不带_id 它的值不能就是表的主键值.

image-20220308232434623

2. 删
* 默认是级联更新和级联删除的 
删除 书籍表的数据
   # 方式一: 查询直接删除
   models.Book.objects.filter(pk=6).delete()

image-20220308232911051

	# 方式二: 获取QuerySet对象在删除
    book_obj = models.Book.objects.filter(pk=5)
	book_obj.delete()

image-20220308233032710

3. 修改
方式1 将书籍表id为1的出版社信息改为 北京出版社的   
方式2 在改回上海出版社 
    # 方式1 查询后直接修改
    models.Book.objects.filter(pk=1).update(publish=2)

image-20220308234134922

    # 1. 先获取出版社的数据对象
    publish_obj = models.Publish.objects.filter(pk=1).first()
    # 2. 为数据表修改数据
    models.Book.objects.filter(pk=1).update(publish=publish_obj)

image-20220308234712857

6.7 多对多外键操作
多对多的操作就是对第三张虚拟表进行操作。

image-20220308235406909

1. 增
.add()给第三张关系表添加数据
虚拟字段.add()
括号内既可以传入数字也可以传入对象,并且都支持多个。
数字则是被关联表的id之间, 对象是被关联表的数据对象.
    # 方式1:  id为1的书籍绑定一个主键为1的作者
    # 先获取数据表的数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 为数据对象的虚拟字段 author 添加 author_id
    book_obj.author.add(1)

image-20220309000044054

    # id为2的书籍绑定一个主键分别为 1,2 的作者
    # 先获取数据表的数据对象
    book_obj = models.Book.objects.filter(pk=2).first()
    # 为数据对象的虚拟字段 author 添加 author_id
    book_obj.author.add(1, 2)

image-20220309000239970

    # id为3的书籍绑定一个主键分别为 1,2 的作者
    # 先获取书籍表的数据对象
    book_obj = models.Book.objects.filter(pk=3).first()

    # 获取id为1的作者数据对象
    author_obj_1 = models.Author.objects.filter(pk=1).first()
    # 获取id为2的作者数据对象
    author_obj_2 = models.Author.objects.filter(pk=2).first()

    # 为书籍表对象绑定值
    book_obj.author.add(author_obj_1, author_obj_2)

image-20220309000934421

2. 删
 .remove()给第三张关系表删除数据。
 虚拟字段.remove()
 括号内既可以传入数字也可以传入对象,并且都支持多个。
 数字则是被关联表的id之间, 对象是被关联表的数据对象.
    # id为1的书籍绑 删除 主键分1的作者
    # 方式1
    # 先获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first().author.remove(1)
    # remove移除绑定关系
    book_obj.author.remove(1)
book_obj = models.Book.objects.filter(pk=1).first().author.remove(1)
可以一步完成推荐写两步.

image-20220309002107552

    # 方式2:
	# 先获取数据对象
    book_obj = models.Book.objects.filter(pk=2).first()

    # 获取id为1的作者数据对象
    author_obj_1 = models.Author.objects.filter(pk=1).first()

    # 获取id为2的作者数据对象
    author_obj_2 = models.Author.objects.filter(pk=2).first()

    book_obj.author.remove(author_obj_1, author_obj_2)

image-20220309002720519

3. 修改
.set([])括号内必须传可迭代对象。
虚拟字段.add.set([])
该对象内既可以是数字也可以是对象并且都支持多个。
数字则是被关联表的id之间, 对象是被关联表的数据对象.
先删除在新增。id值会变。
    # 方式1:  id为3的书籍 修改为值绑定一个主键为 1 的作者
    # 先获取数据对象
    book_obj = models.Book.objects.filter(pk=3).first()
    # 修改值
    book_obj.author.set([1])

image-20220309003510092

    # 方式2:
	    # 先获取数据对象
    book_obj = models.Book.objects.filter(pk=3).first()

    # 获取id为1的作者数据对象
    author_obj_1 = models.Author.objects.filter(pk=1).first()

    # 获取id为2的作者数据对象
    author_obj_2 = models.Author.objects.filter(pk=2).first()

    book_obj.author.set([author_obj_1, author_obj_2])

image-20220309003819645

4. 清空
在第三张表中清空某个书籍与作者的绑定关系。
.clear()括号内不要加任何参数。
虚拟字段.clear()
    # 清楚第三张表的所有数据
    book_obj = models.Book.objects.filter(pk=6).first()
    book_obj.author.clear()

7. 跨表查询

7.1 正反向的概念
正向查询: 通过外键去查询外键所关联的表.
反向查询:  被一个外键关联的表,去查询绑定绑定这个外键的表.


例:出版社表一对多书籍表,外键字段建多的一方书籍表中.
通过 书籍表 --> 查找 出版社,就是正向。
通过 出版社 --> 查找 书籍表 就是反向向。
一对一,多对多也是同样判断的。

正向查询 数据对象按.外键字段
反向查询 数据对象.表名小写_set
查询结果是数据对象.
6.2 子查询
子查询 --> 基于数据对象跨表查询。只能正向查询,不支持反向查询,

ORM子查询方法:
	数据对象.外键字段 获取被关联的数据对象  数据对象.字段  获取值
	数据对象.虚拟字段 获取被关联的数据对象  数据对象.字段  获取值
在书写ORM语句的时候跟写sql语句是一样的,如果语句复杂可以分段写。

* 要分清楚查询的结果是一个值还是多个值.
是一个则值直接数据对象。
结果有多个值的时候就需要加上.all()来获取值, 不加获取不到数据.
1. 查询书籍主键为1的出版社名称

image-20220309134935981

    #  1. 先获取id为1的 书籍对象
    book_obj = models.Book.objects.filter(pk=1).first()
    print(book_obj)
* 关联信息直接被查出来.app01.xxx.None 表示后面没有关联的表了
* app01.xxx.None 只会出现在外键关联的表中. 一对一被关联表也不会出现.
1 开局签到荒古圣体 123.33 2022-03-08 22:43:50 1 上海出版社 上海 123@qq.com app01.Author.None
	# 2. 通过外键字段获取被外键关联的数据
    print(book_obj.publish)  # 1 上海出版社 上海 123@qq.com

    # 3. 对象点属性的方式取值
    print(book_obj.publish.name)
 2. 查询书籍主键为3的作者名字

image-20220309141621165

    #  1. 先获取id为3的 书籍对象
    book_obj = models.Book.objects.filter(pk=3).first()

    # 2. 通过虚拟字段获取被关联表的数据 (不显示第三张表的数据)
    print(book_obj.author)  # 2 app01.Author.None  获取不到数据
    print(book_obj.author.all())  
    # <QuerySet [<Author: 1 kid 18 1 110 北京>, <Author: 2 qq 19 2 112 上海>]>

    # 3.获取数据对象的名字
    data_obj = book_obj.author.all()
    for obj in data_obj:
        print(obj.name)  # kid qq
 3. 拿到作者kid的电话号码

image-20220309143318919

    #  1. 先获取作者kid的数据对象
    author_obj = models.Author.objects.filter(name='kid').first()
    print(author_obj)  # 1 kid 18 1 110 北京  一对一中不会有 app01.xxx.None 

    # 2. 通过外键字段获取作者详情表的数据对象
    print(author_obj.author_detail)  # 1 110 北京
    
    # 3.数据对象点属性的方式取值
    print(author_obj.author_detail.phone)  # 110
7.3 联表查询
联表查询 --> 基于数据对象.表名_set跨表查询。
(表名指定的表中 外键需要绑定这个数据对象的某个字段)


ORM反向查询:
	数据对象.表名_set        结果是单个值
	数据对象.表名_set.all()  结果是多个值
	* 一对一就直接.表名 不需要_set
在书写ORM语句的时候跟写sql语句是一样的,如果语句复杂可以分段写。

* 要分清楚查询的结果是一个值还是多个值.
是一个则值直接数据对象。
结果有多个值的时候就需要加上_set.all()来获取值, 不加获取不到数据.
1. 查询北京出版社出版的书

image-20220309144852967

    #  1. 先获取出版社的数据对象
    publish_obj = models.Publish.objects.filter(name='北京出版社').first()
    print(publish_obj)  # 2 北京出版社 北京 456@qq.com

    # 2.使用_set.all() 反向查询
    print(publish_obj.book_set)  # app01.Book.None 获取不到数据
    print(publish_obj.book_set.all())
<QuerySet [<Book: 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None>, 
<Book: 4 穿越诸天万界 123.33 2022-03-08 22:44:11.728670 2 北京出版社 北京 456@qq.com app01.Author.None>]>
    # 3. 获取数据
    book_obj = publish_obj.book_set.all()
    for obj in book_obj:
        print(obj.title)  # 重生之都市仙尊 穿越诸天万界
 2. 查询作者kid写的书

image-20220309151359036

    #  1. 先获取作者的数据对象
    author_obj = models.Author.objects.filter(name='kid').first()
    print(author_obj)  # 1 kid 18 1 110 北京

    # 2. 直接查书籍表的数据会展示 外键字段连接的数据
    book_msg = models.Book.objects.filter(title='重生之都市仙尊').first()
    print(book_msg)
    # 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None

    # 3. _set反向查询  会将外键字典的值一起展示出来
    print(author_obj.book_set)  # app01.Book.None
    print(author_obj.book_set.all())
    # <QuerySet [<Book: 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None>]>

    # 4.获取值 两种方式
    print(author_obj.book_set.all().first().title)

    book_obj = author_obj.book_set.all()
    for obj in book_obj:
        print(obj.title)  # 重生之都市仙尊
 3.查询手机号是110的作者名字。
* 一对一就直接.表名 不需要_set

image-20220309153442815

    # 1. 先获取作者详情的数据对象
    authordetail_obj = models.AuthorDetail.objects.filter(phone=110).first()
    print(authordetail_obj)  # 1 110 北京

    # 2. 反向查询 一对一就直接.表名 不需要_set
    print(authordetail_obj.author)  # 1 kid 18 1 110 北京

    # 3. 获取数据
    print(authordetail_obj.author.name)  # kid

    # 一对一就直接.表名._set 直接报错
    print(authordetail_obj.author_set)
    # AttributeError:“AuthorDetail”对象没有属性“author_set”
子查询和联表查询的QuerySet对象中是列表套数据对象 通过点取值
<QuerySet [<表名:数据对象>,....]
双下方法查询的QuerySet对象中是列表套字典, 字典的取值方式取值
<QuerySet [{'字段': 110}]

8. 双下方式查询

跨表的时候可以使用双下方式便捷查询.

直接通过 queryset对象.values() 获取指定字段的值 

正向查询
    queryset对象.values('外键__字段') 
反向查询
    queryset对象.values('表名__字段')

获取的数据格式是queryset对象列表套字典,字典的键是values中指定的参数,值是匹配出来的值.

queryset对象列表中的字典键可以重复出现.
8.1 正向查询
 1.查询kid的手机号

image-20220309161351200

    # 1. 子查询方式  数据对象.外键字典
    author_obj = models.Author.objects.filter(name='kid').first()
    print(author_obj)  # 1 kid 18 1 110 北京
    print(author_obj.author_detail)  # 1 110 北京
    print(author_obj.author_detail.phone)  # 110
    # 2. __方式查询
    phone_obj = models.Author.objects.filter(name='kid').values('author_detail__phone')
    print(phone_obj)  # <QuerySet [{'author_detail__phone': 110}]>
    print(phone_obj.first())  # {'author_detail__phone': 110}
 2.查询书籍主键为1的书名和出版社名字

image-20220309161741300

    # 1. 子查询方式
    book_obj = models.Book.objects.filter(pk=1).first()
    print(book_obj)  # 1 开局签到荒古圣体 123.33 2022-03-08 22:43:50.303320 1 上海出版社 上海 123@qq.com app01.Author.None
    print(book_obj.title)  # 开局签到荒古圣体
    print(book_obj.publish.name)  # 上海出版社
    # __方式查询
    book_obj = models.Book.objects.filter(pk=1).values('title', 'publish__name')
    print(book_obj)  # <QuerySet [{'title': '开局签到荒古圣体', 'publish__name': '上海出版社'}]>
    print(book_obj.first())  # {'title': '开局签到荒古圣体', 'publish__name': '上海出版社'}
 3.查询书籍主键为3的作者姓名

image-20220309194330717

    # 子查询方式
    book_obj = models.Book.objects.filter(pk=3).first()
    print(book_obj)
    # 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None
    print(book_obj.author.all())  
    # <QuerySet [<Author: 1 kid 18 1 110 北京>, <Author: 2 qq 19 2 112 上海>]>
    author_obj = book_obj.author.all()
    for obj in author_obj:
        print(obj.name)  # kid  qq
    # __方式查询
    book_obj = models.Book.objects.filter(pk=3).values('author__name')
    print(book_obj)  # <QuerySet [{'author__name': 'kid'}, {'author__name': 'qq'}]>
    for obj in book_obj:
        print(obj.get('author__name'))  # kid qq
 4.查询书籍主键是3的作者的手机号

image-20220309194415610

    # 子查询方式
    book_obj = models.Book.objects.filter(pk=3).first()
    print(book_obj.author.all())  # <QuerySet [<Author: 1 kid 18 1 110 北京>, <Author: 2 qq 19 2 112 上海>]>
    author_obj = book_obj.author.all()
    for obj in author_obj:
        print(obj)  # -                   1 kid 18 1 110 北京   2 qq 19 2 112 上海
        print(obj.author_detail)  # -     1 110 北京            2 112 上海
        print(obj.author_detail.phone)  # 110                   112
    # __方式查询
    phone_obj = models.Book.objects.filter(pk=3).values('author__author_detail__phone')
    print(phone_obj)  # <QuerySet [{'author__author_detail__phone': 110}, {'author__author_detail__phone': 112}]>
    for obj in phone_obj:
        print(obj.get('author__author_detail__phone'))  # 110 112
8.2 反向查询
1.查询手机号110的作者姓名

image-20220309181634326

    # 反向查询 一对一不需要_set
    author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
    print(author_detail_obj)  # 1 110 北京
    print(author_detail_obj.author)  # 1 kid 18 1 110 北京
    print(author_detail_obj.author.name)  # kid
	# __方式查询
    author_detail_obj = models.AuthorDetail.objects.filter(phone=110).values('author__name', 'phone')
    print(author_detail_obj)  # <QuerySet [{'author__name': 'kid', 'phone': 110}]>
    print(author_detail_obj.first())  # {'author__name': 'kid', 'phone': 110}
2.查询上海出版社出版的书

image-20220309184239858

    # 反向查询 _set 方式
    publish_obj = models.Publish.objects.filter(name='上海出版社').first()
    print(publish_obj)  # 1 上海出版社 上海 123@qq.com
    print(publish_obj.book_set.all())
    # <QuerySet [<Book: 1 开局签到荒古圣体 123.33 2022-03-08 22:43:50.303320 1 上海出版社 上海 123@qq.com app01.Author.None>, <Book: 2 洪荒之至尊通天 123.33 2022-03-08 22:44:11.715622 1 上海出版社 上海 123@qq.com app01.Author.None>]>

    book_obj = publish_obj.book_set.all()
    for obj in book_obj:
        print(obj.title)  # 开局签到荒古圣体 洪荒之至尊通天

    # __方式查询
    publish_obj = models.Publish.objects.filter(name='上海出版社').values('book__title')
    print(publish_obj)  # <QuerySet [{'book__title': '开局签到荒古圣体'}, {'book__title': '洪荒之至尊通天'}]>
    print(publish_obj.first())  # {'book__title': '开局签到荒古圣体'}
 3.查询kid写的书

image-20220309190036010

    # 反向查询
    book_obj = models.Author.objects.filter(name='kid').first()
    print(book_obj)  # 1 kid 18 1 110 北京
    print(book_obj.book_set.all())
    # <QuerySet [<Book: 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None>]>
    
    print(book_obj.book_set.all().first())
    # 3 重生之都市仙尊 123.33 2022-03-08 22:44:11.726676 2 北京出版社 北京 456@qq.com app01.Author.None
    print(book_obj.book_set.all().first().title)
    # 重生之都市仙尊
    # __方式查询
    book_obj = models.Author.objects.filter(name='kid').values('book__title')
    print(book_obj)  # <QuerySet [{'book__title': '重生之都市仙尊'}]>
    print(book_obj.first())  # {'book__title': '重生之都市仙尊'}

9. 聚合查询

聚合查询通常都是配合分组一起使用的。
需要导入导入模块,只要更数据相关的模块基本上都在django.dbmodels或者django.db中。
from django.db.models import Max, Min, Sum,Count, Avg

聚合查询直接使用需要在aggregate方法中使用.
objects.aggregate(聚合函数()) 

聚合查询的结果是一个字典
键的名字是聚合函数中写的参数+聚合函数的名称
{'参数__聚合函数': xx}
9.1 直接使用
* 先手动修改一个价格

image-20220309195321180

1.获取图书的平均价格
    from django.db.models import Max, Min, Sum, Count, Avg
    price__avg = models.Book.objects.aggregate(Avg('price'))
    print(price__avg)  
    # {'price__avg': 250.0}
2.最贵的书
    from django.db.models import Max, Min, Sum, Count, Avg
    price__max = models.Book.objects.aggregate(Max('price'))
    print(price__max)  
    # {'price__max': Decimal('400.00')}
3.最便宜的书
    from django.db.models import Max, Min, Sum, Count, Avg
    price__min = models.Book.objects.aggregate(Min('price'))
    print(price__min)  
    # {'price__min': Decimal('100.00')}
4.所有书籍的价格
    from django.db.models import Max, Min, Sum, Count, Avg
    price__sum = models.Book.objects.aggregate(Sum('price'))
    print(price__sum)
    # {'price__sum': Decimal('1000.00')}
5.数图的数量
写pk自动找主键
    from django.db.models import Max, Min, Sum, Count, Avg
    price__count = models.Book.objects.aggregate(Count('pk'))
    print(price__count)
    # {'pk__count': 4}
9.2 分组查询
MySQL分组查询特点:
分组之后,开了严格模式组内其他字段都无法直接获取了,只能获取分组的依据.
ORM所有字段都可以获取.
ORM  分组关键字 annotate() 括号内写聚合函数
annotate('别名'=聚合函数(操作的字段)).values('别名')
操作的字段可以 表名__字段 跨表正反向查询.

.values(写需要展示的字段, select 要展示的字段)
xxx表.objects.annotate(),就以这个xxx表的主键作为分组的依据.

在跨表操作的时候会自动拼接表.
一般都会为聚合函数的结果起一个别名.在配合values('别名')使用.

聚合函数操作的字段是一个表的主键的话可以只写表名,
xxx表__id 省略__id 就写xxx表
 1.统计每一本书的作者个数
 以book_id最为依据,去统计 author_id 的数量

image-20220309210100416

    # Count('author') 实际为Count('author__id')  可以省略不写
    book_obj = models.Book.objects.annotate(Count_num=Count('author')).values('title', 'Count_num')
    print(book_obj)
<QuerySet [{'title': '开局签到荒古圣体', 'Count_num': 0}, {'title': '洪荒之至尊通天', 'Count_num': 0}, {'title': '重生之都市仙尊', 'Count_num': 2}, {'title': '穿越诸天万界', 'Count_num': 0}]>
(0.000) SELECT `app01_book`.`title`, COUNT(`app01_book_author`.`author_id`) AS `Count_num` FROM `app01_book` LEFT OUTER JOIN `app01_book_author` ON (`app01_book`.`id` = `app01_book_author`.`book_id`) GROUP BY `app01_book`.`id` ORDER BY NULL LIMIT 21; args=()
 2.统计每个出版社卖的最便宜的书的价格
 以出版社的id为依据,求最低的价格

image-20220309210928680

	publish_obj = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
    print(publish_obj)
    # <QuerySet [{'name': '上海出版社', 'min_price': Decimal('100.00')}, {'name': '北京出版社', 'min_price': Decimal('300.00')}]>
(0.000) SELECT `app01_publish`.`name`, MIN(`app01_book`.`price`) AS `price__min` FROM `app01_publish` LEFT OUTER JOIN `app01_book` ON (`app01_publish`.`id` = `app01_book`.`publish_id`) GROUP BY `app01_publish`.`id` ORDER BY NULL LIMIT 21; args=()
3. 统计书籍作者不为1的数量
先按图书表的主键分组,在统计每本书的作者数量,最后过滤一个作者的图书
	from django.db.models import Max, Min, Sum, Count, Avg
    book_obj = models.Book.objects.annotate(author_count=Count('author__id')).filter(author_count__gt=1).values('title', 'author_count')
    print(book_obj)  
    # <QuerySet [{'title': '重生之都市仙尊', 'author_count': 2}]>
只要ORM语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法。
(0.000) SELECT `app01_book`.`title`, COUNT(`app01_book_author`.`author_id`) AS `author_count` FROM `app01_book` LEFT OUTER JOIN `app01_book_author` ON (`app01_book`.`id` = `app01_book_author`.`book_id`) GROUP BY `app01_book`.`id` HAVING COUNT(`app01_book_author`.`author_id`) > 1 ORDER BY NULL LIMIT 21; args=(1,)	
 4.查询每个作者出的书的总价格
 先按作者id分组,在统计所有书籍的价格

image-20220309212928851

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

    author_obj = models.Author.objects.annotate(count_sum=Sum('book__price')).values('name', 'count_sum')
    print(author_obj)
    # <QuerySet [{'name': 'kid', 'count_sum': Decimal('300.00')}, {'name': 'qq', 'count_sum': Decimal('300.00')}, {'name': 'qaq', 'count_sum': None}]>
(0.000) SELECT `app01_author`.`name`, SUM(`app01_book`.`price`) AS `count_sum` FROM `app01_author` LEFT OUTER JOIN `app01_book_author` ON (`app01_author`.`id` = `app01_book_author`.`author_id`) LEFT OUTER JOIN `app01_book` ON (`app01_book_author`.`book_id` = `app01_book`.`id`) GROUP BY `app01_author`.`id` ORDER BY NULL LIMIT 21; args=()

10. F查询

F 查询可以取到表中某个字段对应的值,当作筛选条件,还支持一般的数学运算.
导入模块 from django.db.models import F
先为书表添加字段
库存的字段  stock
卖出的字段  market
class Book(models.Model):
······
    # 库存的字段
    stock = models.IntegerField(default=1000)
    # 卖出的字段
    market = models.IntegerField(default=1000)
    
    def __str__(self):
        # 打印Book实例化对象时展示所有字段的信息
        return '%s %s %s %s %s %s %s %s' % (
            self.id, self.title, self.price, self.publish_date, self.publish, self.author, self.stock, self.market)
执行数据库迁移命令:
python  manage.py makemigrations
python manage.py migrate
 再修改数据(数据不够用自己多添加几个)

image-20220309224643407

1.查询卖出数大于库存数的书籍名称
    from django.db.models import F
    # -                                    卖出 大于 库存
    book_obj = models.Book.objects.filter(market__gt=F('stock'))
    print(book_obj.values('title', 'market', 'stock'))
    < QuerySet[{'title': '洪荒之至尊通天', 'market': 2000, 'stock': 1}, {'title': '重生之都市仙尊', 'market': 1000, 'stock': 800}, {
        'title': '穿越诸天万界', 'market': 1500, 'stock': 500}] >
2.将所有书籍的价格提升50
    from django.db.models import F
    # 将所有书籍的价格提升50块
    models.Book.objects.update(price=F('price')+50)

image-20220309230127399

3.将所有书的名称后面加上 劲爆 两个字 需要Concat函数  Value函数
Concat用于字符串的拼接操作,参数位置决定了拼接是在头部拼接还是尾部拼接,Value里面是拼接的值
from django.db.models.functions import Concat
from django.db.models import Value 
    from django.db.models import F
    from django.db.models import Value
    from django.db.models.functions import Concat

    models.Book.objects.update(title=Concat(F('title'), Value('劲爆')))

image-20220309230853175

    # 单行修改
    from django.db.models import F
    from django.db.models import Value
    from django.db.models.functions import Concat

    book_obj = models.Book.objects.filter(pk=1)
    # 不跨表的话F查询可以不用(title=Concat('title'), Value('xx')))
    book_obj.update(title=Concat(F('title'), Value('xx')))
    # 不 需要.save()

image-20220309235711399

# 如果要修改char字段不能使用 + 
    models.Book.objects.update(title=F('title')+'劲爆')  

image-20220309231341694

删除后缀还不知道,先手改吧.

11. Q查询

from django.db.models import Q
在filter()括号中默认是and关系。
导入Q模块之后可以使用|(or 运输算法)  ~(not 运算符)
 1.查看卖出数大于100或者价格小于200的书籍
    from django.db.models import Q

    book_obj = models.Book.objects.filter(Q(stock__gt=100) | Q(price__lt=200))
    print(book_obj.values('title', 'stock', 'price'))
<QuerySet [{'title': '开局签到荒古圣体xx', 'stock': 1000, 'price': Decimal('150.00')}, {'title': '重生之都市仙尊', 'stock': 800, 'price': Decimal('350.00')}, {'title': '穿越诸天万界', 'stock': 500, 'price': Decimal('450.00')}]>
写成一个模板使用.
    from django.db.models import Q

    q = Q()
    print(q)  # (AND: )
    q.connector = 'or'  # 修改为or
    print(q)  # (or: )

    q.children.append(('stock__gt', 100))
    print(q)  # (or: ('stock__gt', 100))
    q.children.append(('price__lt', 200))
    print(q)  # (or: ('stock__gt', 100), ('price__lt', 200))

    book_obj = models.Book.objects.filter(q).values('title', 'stock', 'price')
    print(book_obj)
QuerySet [{'title': '开局签到荒古圣体xx', 'stock': 1000, 'price': Decimal('150.00')}, {'title': '重生之都市仙尊', 'stock': 800, 'price': Decimal('350.00')}, {'title': '穿越诸天万界', 'stock': 500, 'price': Decimal('450.00')}]>

12. Django中开启事务

事务四个特性:
ACID
原子性:不可分割的最新单位。
一致性:更原子性是相辅相成的。
隔离性:事务之间互相不干扰。
持久性:事务一旦确认永久生效。

事务的回滚:rellback
事务的确认:commit

image-20220310004342614

# 开启事务
from django.db import transaction
try:
    with transaction.atomic():
    # sql 语句
    # 在with代码块内书写的所有orm操作都是属于同一个事务
    pass
except Exception as e:
	print(e)

13. ORM 字段&参数

13.1 内置字段
verbose_name 字段注释
AutoField 自增
	主键字段 必须设置 primary_key = True
null  字段可以为 
IntegerField     int整型
BigInegerField   bigint
DecimalField           十进制字段 
	max_digits=8       8
	decimal_places=2   小数部分2
CharField 字符 --> 对应数据库中的varchar
	max_length 长度 必须设置
EmailFiled  --> varchar(254) 邮件
DateFiled       日期格式
	date
DateTimeFiled
	auto_now:    auto_now=True每次修改数据的时候都会自动跟新当前时间。
	auto_now_add: auto_now_add=True之在创建数据的时候记录创建的时候,后续不会自动更改。
BooleanField   布尔值
该字段存布尔值(False/True) 数据库中存0/1
TextField   文本类型  
	存大段文本内容,没有字数限制
FileField
	upload = '/date一个路径'
	该字段传一个文件对象,会自动将文件保存到/date目录下然后将文件路径保存到数据库中
ForeignKey(unique=True) 等价于 OneToOneField()
一对多   一对一的区别就在与unique=True
使用ForeignKey(unique=True)创建一对一是可以的但是不推荐这样去使用.
to 设置要关联的表
to_field 设置要关联的字段 不写默认为主键字段,一般不写.

on_delere 当删除表中的数据时,当前表与起关联的行的行为。
django2.x以上版本需要自己指定外键字段的级联更新级联删除。
13.2 自定义字段
# app 应用下的 models.py 
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        super().__init__(max_length= max_length, *args, **kwargs)
    def db_type(self, connection):
        # 返回字段和约束
        return 'char(%s)' % self.max_length
    # tests.py中使用
    my_char = models.MyCharField(max_length=32, verbose_name='自定义的字段')

image-20220310012253527

image-20220310012405823

13.2 choices参数
存储一个可枚举的字段。
xx字段 = models.InergerField(choice=一个列表或元组)
字段存储的是Integer类型,但是可以通过数字获取对应的真正信息.
((,), (,)) 其中元素的值相对应
保证字段类型跟列举出来的元组的第一个数据类型一致即可

使用choices 参数的字段取值的对应信息 搭配固定的语法 get_字段名_display()
如果没有对应关系,直接展示字段的值。
# 5. 用户表2
class UserTwo(models.Model):
    # 1. id字段自动增加
    # 2. 用户名
    name = models.CharField(max_length=32, verbose_name='用户名')
    # 3. 年龄
    age = models.IntegerField(verbose_name='年龄')
    # 4. 性别列举信息
    gender_tuple = (
        (1, '男'),
        (2, '女'),
        (3, '保密')
    )

    # 5. 性别字段
    gender = models.IntegerField(choices=gender_tuple, verbose_name='性别')

    # 6. 成绩列表  有对应关系   没有对应关系
    score_tuple = (('A', '优秀'), ('B', None))
    # 7. 成绩字段
    score = models.IntegerField(choices=score_tuple, verbose_name='成绩')
执行数据库迁移命令。

image-20220312224136424

	# app01应用下tests.py 
	# 增加数据
    models.User2.objects.create(name='kid1', age=18, gender=1, score='A')
    models.User2.objects.create(name='kid2', age=19, gender=2, score='B')
    # 存一个不存在的                                   5不存在对应的数据
    models.User2.objects.create(name='kid1', age=18, gender=5, score='A')
	# app01下 tests.py 中查看值
	user2_obj = models.User2.objects.all()
    for obj in user2_obj:
        print(obj.get_gender_display(), obj.get_score_display())
 优秀
 None
5 优秀 
 5没有对应的值,就直接显示5

14. 多对多创建方式

14.1 全自动
orm自动创建第三张表
class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ManyToManyField(to='Author')
    
class Author(models.Model):
    name = models.CharField(max_length=32)
优点:自动,支持orm操作第三表的方法,add() remove() clear() ···正方向查询···
缺点:第三张拓展性差,没法添加额外的字段。
14.2 纯手动
几乎不会去使用.
class Book(models.Model):
    name = models.CharField(max_length=32)

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

    # 手动创建第三张表
class BookToAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
优点:第三表完全可以自己拓展。
缺点:add() remove() clear() ···正方向查询···不能用。
14.3 半自动
0. 写法与全自动一样,不需要ORM自动创建第三张表,而是自己创建.
1. through指定第三张表的名字,through_fields指定关联的字端.经过这一步之后可以使用正方向查询.
2. through_fields的第一个参数,写当前表的表名,会自动加_id.
* 第三张表的字段是普通的字段,直接操作第三张.字段 无法到另一张表中去.
# 6.书籍表
class Book(models.Model):
    # 6.1 id 字段自动增加
    # 6.2 书名
    title = models.CharField(max_length=32)
    # 6.3 虚拟字段                   多对多 作者表
    author = models.ManyToManyField(to='Author',
                                    # 指定第三张表
                                    through='BookToAuthor',
                                    # 表中关联的字段  _id自动添加
                                    through_fields=('book', 'author')
                                    )


# 7.作者表
class Author(models.Model):
    # 7.1 id字段自动添加
    # 7.2 姓名
    name = models.CharField(max_length=32)


# 8 第三张表 分别一对多书籍表与作者表
class BookToAuthor(models.Model):
    # 8.1 id字段自动增加
    # 8.2 图数的id   _id自动加的
    book = models.ForeignKey(to='Book')
    # 8.3 作者id    _id自动增加
    author = models.ForeignKey(to='Author')
    # 8.4 绑定时间  半自动的意义 可以拓展 其他字段
    binding_time = models.DateTimeField(auto_now_add=True)  # 写入数据的时间自动获取当前时间

image-20220313024124846

    # app01应用下 tests.py
    # 为书籍表添加数据
    models.Book.objects.create(title='Python')

    # 为作者添加数据
    models.Author.objects.create(name='kid')

    # 为第三张表添加数据
    models.BookToAuthor.objects.create(book_id=1, author_id=1)

image-20220313024617264

可是使用 正方向查询,不能使用:add() remove() clear() ···
拓展性高
为每个表添加__str__ 方法在打印对象时触发.
# 6.书籍表
class Book(models.Model):
    # 6.1 id 字段自动增加
    # 6.2 书名
    title = models.CharField(max_length=32)
    # 6.3 外键字段                   多对多 作者表
    author = models.ManyToManyField(to='Author',
                                    # 指定第三张表
                                    through='BookToAuthor',
                                    # 表在关联的字段
                                    through_fields=('book', 'author')
                                    )

    # 6.4 打印对象时自动触发
    def __str__(self):
        return f'{self.pk} {self.title} {self.author}'

    # 7.作者表


class Author(models.Model):
    # 7.1 id字段自动添加
    # 7.2 姓名
    name = models.CharField(max_length=32)

    # 7.3 打印对象时自动触发
    def __str__(self):
        return f'{self.pk} {self.name}'


# 8 第三张表 分别一对多书籍表与作者表
class BookToAuthor(models.Model):
    # 8.1 id字段自动增加
    # 8.2 图数的id   _id自动加的
    book = models.ForeignKey(to='Book')
    # 8.3 作者id    _id自动增加
    author = models.ForeignKey(to='Author')
    # 8.4 绑定时间  半自动的意义 可以拓展 其他字段
    binding_time = models.DateTimeField(auto_now_add=True)  # 写入数据的时间自动获取当前时间

    # 8.4 打印对象时自动触发
    def __str__(self):
        return f'{self.pk} {self.book_id} {self.author_id} {self.binding_time}'

image-20220313033138010

    # 正向查询
    # 获取书籍数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    print(book_obj.author.all())  # <QuerySet [<Author: 1 kid>]>

    # 反向到 第三张表
    print(book_obj.booktoauthor_set.all())
    # <QuerySet [<BookToAuthor: 1 1 1 2022-03-13 02:45:20.064068>]>

    # 反向查询
    # 获取作者数据对象
    author_obj = models.Author.objects.filter(pk=1).first()
    print(author_obj.book_set.all())  # <QuerySet [<Book: 1 Python app01.Author.None>]>

    # 反向到第三张表
    print(author_obj.booktoauthor_set.all())
    # <QuerySet [<BookToAuthor: 1 1 1 2022-03-13 02:45:20.064068>]>
    # 获取第三张的queryset对象
    book_author_obj = models.BookToAuthor.objects.filter(pk=1)
    print(book_author_obj)
    # queryset对象.values('外键__字段')
    print(book_author_obj.values('book_id__pk'))

    # 数据对象
    print(book_author_obj.first())  # 1 1 1 2022-03-13 02:45:20.064068
    # 数据对象.字段  获取外键指定的数据对象 book_id现在获取的是一个值,那它就不是外键,是一个普通的字段
    print(book_author_obj.first().book_id)  # 1

15. 数据库的查询优化

研究对象查询数据的时候,还需不需要走数据库去查询.
优化: 走的少,或者不走,数据库.
将应用下的models.py  book类的__str__ 方法注释.

image-20220310022053422

15.1 惰性查询
orm语句的特点:
惰性查询:
	仅仅只是书写orm语句,后面根本没有使用到该语句查询出来的参数,
	那么orm会自动识别到,不用就直接不执行。
    # 在设置中打开打开LOGGING现在SQL语句
    boolk_obj = models.Book.objects.all()   
    # 没有没有使用res 直接不执行,终端都不打印sql语句。

image-20220310012733803

    book_obj = models.Book.objects.all()
    print(book_obj)
    # 使用数据 才执行。终端都打印sql语句。

image-20220310012821858

15.2 only
all()  拿表中所有的数据
only() 拿指定字段的数据
    book_obj = models.Book.objects.all()
    print(book_obj)
    for obj in book_obj:
        print(obj.title)
        
    for obj in book_obj:
        print(obj.price)

image-20220310020949464

    book_obj = models.Book.objects.all().values('title')
    print(book_obj)
    for obj in book_obj:
        print(obj)

image-20220310021323167

    book_obj = models.Book.objects.only('title')

    print(book_obj)
    for obj in book_obj:
        print(obj.title)

image-20220310021820466

    book_obj = models.Book.objects.only('title')
    print(book_obj)

image-20220310021952701

15.3 defer
defer与only相反
defer查询指定字段外的字段.
    book_obj = models.Book.objects.defer('title')
    for obj in book_obj:
        print(obj.title)
查询没有被查出来的字段需要重新走数据库

image-20220310014903059

    for obj in book_obj:
        print(obj.price)
而查询查出来的的字段则不需要走数据库。

image-20220310015029326

15.4 select_related
联表查询
select_related内部直接先将关联表与被关联表连接起来,(拼表操作)
select_related()括号内只能放 一对多  一对一 外键字段 

拼接后的表中所有数据全部封装给查询出来的对象。

这个时候对象无论点击关联表的数据还是被关联表的数据都无需在走数据库查询。
给了多对多的虚拟字段会报错
django.core.exceptions.FieldError:select_related 中给出的字段名称无效
    # book 表拼接 通过外键 拼接 出版社表
    book_publish_obj = models.Book.objects.select_related('publish')
	for obj in book_publish_obj:
        print(obj.title, obj.price)

image-20220310015743880

15.5 prefetch_related
子查询
将子查询出来的所有结果封装到对象中.
    # book 表拼接 通过外键 拼接 出版社表
    book_publish_obj = models.Book.objects.prefetch_related('publish')
    for obj in book_publish_obj:
        print(obj.title, obj.price)

image-20220310020140346

select_related()只能为一对一  一对多 使用
多对多使用,拼接虚拟表没有数据.(Book类中的__str__方法打开)

image-20220310135155086

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
在jango中,数据库表结构的设计是通过定义模型(Models)来实现的。模型是一个Python类,它继承自django.db.models.Model,并且通过定义类的属性来描述表的字段。下面是一个例子: ```python from django.db import models class Publisher(models.Model): pid = models.AutoField(primary_key=True) name = models.CharField(max_length=32, unique=True) # 其他字段... class Book(models.Model): title = models.CharField(max_length=32) pub = models.ForeignKey("Publisher", on_delete=models.CASCADE) # 其他字段... ``` 这个例子中,我们定义了两个模型,一个是Publisher(出版社)模型,一个是Book(图书)模型。Publisher模型包含了一个pid字段作为出版社的id,设置为主键,类型为自增的整数,以及一个name字段作为出版社的名称,限制字符长度为32,并设置唯一属性。Book模型包含了一个title字段作为书名,限制字符长度为32,并且还有一个外键字段pub,关联到Publisher模型的id字段,级联设置为级联删除。你可以根据需要添加其他的字段。 另外,还有一个例子是关于学生和班级的表结构设计: ```python class Class(models.Model): cid = models.AutoField(primary_key=True) cname = models.CharField(max_length=32, unique=True) # 其他字段... class Student(models.Model): sid = models.AutoField(primary_key=True) s_name = models.CharField(max_length=32, null=False) gender = models.CharField(max_length=2, default='男') class_id = models.ForeignKey("Class", on_delete=models.CASCADE) # 其他字段... ``` 这个例子中,我们定义了一个Class(班级)模型和一个Student(学生)模型。Class模型包含一个cid字段作为班级的id,设置为主键,类型为自增的整数,以及一个cname字段作为班级的名称,限制字符长度为32,并设置唯一属性。Student模型包含一个sid字段作为学生的id,设置为主键,类型为自增的整数,一个s_name字段作为学生的名称,限制字符长度为32,非空约束,一个gender字段作为学生的性别,限制字符长度为2,默认设置为男,还有一个外键字段class_id,关联到Class模型的id字段,级联设置为级联删除。 通过以上的例子,你可以参考Django的模型定义来设计数据库表结构。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值