Django 模型层


参考文档:https://www.cnblogs.com/zjyao/p/17360266.html#单表的增删改查
https://zhuanlan.zhihu.com/p/151373067
https://zhuanlan.zhihu.com/p/151376312
https://zhuanlan.zhihu.com/p/151376532
https://zhuanlan.zhihu.com/p/151765221
https://zhuanlan.zhihu.com/p/151765854
https://zhuanlan.zhihu.com/p/151766255

1.Django模型层之ORM介绍

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。
如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库
针对应用程序的数据操作,直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下:

  • sql语句的执行效率问题:应用开发程序员需要耗费一大部分精力去优化sql语句
  • 数据库迁移问题:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行

2.ORM模型

在django的ORM框架中,继承自django.db.models.Model的类称之为模型类,或简称模型。

​一个模型是关于你的数据,唯一的、决定性的信息源、它包含存储数据的基本字段和方法。

​通常,每个模型都映射到一个数据库表。模型中的属性对应数据库表的字段

​如下所示:原生SQL与ORM的模型对应关系

模型层图书项目示例

MVT图解

项目准备

1.创建项目

django-admin startproject bookmanager

2.创建应用

python manager.py startapp book

3.安装应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 添加子应用
    'book.apps.BookConfig',
    # 简写
    # 'book',
]

4.配置数据库

# 删除\注释掉原来的DATABASES配置项,新增下述配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
        'NAME': 'db1',          # 要连接的数据库
        'USER': 'root',         # 链接数据库的用户名
        'PASSWORD': '',         # 链接数据库的密码                  
        'HOST': '127.0.0.1',    # mysql服务监听的ip  
        'PORT': 3306,           # mysql服务监听的端口  
        'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行 
                                #(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个
                                #http请求放水(然后自定义事务),可以用non_atomic_requests修饰器 
        'OPTIONS': {
            "init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB
            "charset": "utf8",
            "unix_socket": "",
        }
    }
}
# mysqlclient安装与兼容性问题
# Django默认加载的MySQLclient是MySQLdb模块,实践使用中一般更倾向于使用pymysql模块

pip3 install pymysql

# 在项目目录下的__init__.py中添加

import pymysql
pymysql.install_as_MySQLdb()

# 但是有的人使用却会报错,
raise ImproperlyConfigured('mysqlclient 1.4.0 or newer is required; you have %s.' % Database.__version__)
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required; you have 0.10.1.

# 发生上面的错误,是因为django版本>=2.2 ,pymysql的mysqlclient版本是0.9.3,版本过低,所以出现了上面的情况,解决办法如下
# 在项目目录下的__init__.py中添加

import pymysql
pymysql.version_info = (1, 4, 13, "final", 0)
pymysql.install_as_MySQLdb()  # 使用pymysql代替mysqldb连接数据库

5.配置日志

# 如果想打印orm转换过程中的sql,需要在settings中进行配置日志:
# 注意,settings.py中必须DEBUG=True,同时loggers的level是DEBUG,否则从控制台看不到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',
        },
    }
}

6.项目中配置urls.py
路由分发配置

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('', include('book.urls')),

7.应用中配置urls.py

  • 应用中创建 urls.py
  • 正则 : 路径中包含booklist/,就调用视图中对应的bookList函数
from django.urls import path
from .views import bookList

urlpatterns = [
    path("booklist", bookList),
]

8.应用中的视图

from django.http import HttpResponse

def bookList(request):
    return HttpResponse("hello,booklist")

9.开启服务器,测试项目

 # 进入项目文件中, 开启项目对应的服务器
 python manage.py runserver

 # 浏览器中输入网址
 http://127.0.0.1:8000/booklist/

定义模型类

  • 模型类被定义在"应用/models.py"文件中。
  • 模型类必须继承自Model类,位于包django.db.models中。

接下来首先以"图书-人物"管理为例进行演示。

1.定义

在models.py 文件中定义模型类。

from django.db import models
# Create your models here.
# 准备书籍列表信息的模型类
class BookInfo(models.Model):
    # 创建字段,字段类型...
    name = models.CharField(max_length=20, verbose_name='名称')
    pub_date = models.DateField(verbose_name='发布日期', null=True)
    readcount = models.IntegerField(default=0, verbose_name='阅读量')
    commentcount = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'bookinfo'  # 指明数据库表名
        verbose_name = '图书'  # 在admin站点中显示的名称

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.name


# 准备人物列表信息的模型类
class PeopleInfo(models.Model):
    GENDER_CHOICES = (
        (0, 'male'),
        (1, 'female')
    )
    name = models.CharField(max_length=20, verbose_name='名称')
    gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
    description = models.CharField(max_length=200, null=True, verbose_name='描述信息')
    book = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')  # 外键
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'peopleinfo'
        verbose_name = '人物信息'

    def __str__(self):
        return self.name

数据库表名

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

关于主键

django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。

默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。

字段/属性命名限制

Fields字段被指定为模型类的类属性,是模型最重要的部分,也是模型唯一必须要有的部分,是用来定义数据库字段的
Django 对字段的命名设置了一些限制:

  • 注意字段名不要选择与模型API冲突的名字,如clean、save或delete等
  • 字段名不能是Python保留字,因为这将导致Python语法错误。
  • 由于Django 查询语法的工作方式,所以字段名称中连续的下划线不能超过两个。 例如:
class Example(models.Model):
    foo__bar = models.IntegerField() # 'foo__bar'有两个下划线
  • 出于类似的原因,字段名不能以下划线结尾。

字段类型

django提供了几十个内置的字段类型,详见: https://docs.djangoproject.com/en/3.0/ref/models/fields/#model-field-types
如果django的内置类型无法满足需求,也可以自定义: https://docs.djangoproject.com/en/3.0/howto/custom-model-fields/

字段类型 说明
AutoField int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列
BooleanField 布尔字段,值为True或False
NullBooleanField 支持Null、True、False三种值
CharField 字符类型,必须提供max_length参数, max_length表示字符长度
TextField 大文本字段,一般超过4000个字符时使用
IntegerField 整数类型,范围在 -2147483648 to 2147483647
DecimalField 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数
FloatField 浮点数
DateField 日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例
TimeField 时间,参数同DateField
DateTimeField 日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例
FileField 上传文件字段
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片

字段选项

选项 说明
null 如果为True,Django将在数据库中将空值存储为NULL。默认值为False
blank 如果为True,则该字段允许为空。默认值为False
db_column 字段的名称,如果未指定,则使用属性的名称
db_index 若值为True, 则在表中会为此字段创建索引,默认值是False
default default 字段的默认值。这可以是值或可调用对象。如果可调用,则每次创建新对象时都将调用它
primary_key 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用
unique 如果为True, 这个字段在表中必须有唯一值,默认值是False
choices 用于指定一个二元组,如果给定了此选项,则admin界面默认表单小部件将是一个选择框,而不是标准文本字段,并且将选择限制为给定的选项

null是数据库范畴的概念,blank是表单验证相关。如果字段的blank=True,则表单验证将允许输入空值。如果字段为空=假,则该字段是必需的

外键

在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:

  • CASCADE级联,删除主表数据时连通一起删除外键表中数据
  • PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
  • SET_NULL设置为NULL,仅在该字段null=True允许为null时可用
  • SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用
  • SET()设置为特定值或者调用特定方法
  • DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

模型字段的增删改

在表生成之后,如果需要增加、删除、修改表中字段,需要这么做

# 一:增加字段
#1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值
publish = models.CharField(max_length=12,default='人民出版社',null=True)
#1.2、重新执行那两条数据库迁移命令


# 二:删除字段
#2.1 直接注释掉字段
#2.2 重新执行那两条数据库迁移命令

# 三:修改字段
#2.1 将模型类中字段修改
#2.2 重新执行那两条数据库迁移命令

2.数据库迁移

# 生成迁移文件
python manage.py makemigrations
# 同步到数据库中
python manage.py migrate

# 注意:
# 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行
# 2、数据库迁移记录的文件存放于应用下的migrations文件夹里
# 3、了解:如果要给迁移一个有意义的名称而不是生成的名称,则可以使用makemigrations --name选项:
python manage.py makemigrations --name xx app01 # 会在migrations目录下生成迁移文件0001_xx.py

python manage.py sqlmigrate app01 0001_xx  # 展示迁移的sql语句
python manage.py showmigrations # 项目的迁移及其状态。

3.添加测试数据

insert into bookinfo(name, pub_date, readcount,commentcount, is_delete) values
('射雕英雄传', '1980-5-1', 12, 34, 0),
('天龙八部', '1986-7-24', 36, 40, 0),
('笑傲江湖', '1995-12-24', 20, 80, 0),
('雪山飞狐', '1987-11-11', 58, 24, 0);
insert into peopleinfo(name, gender, book_id, description, is_delete)  values
    ('郭靖', 1, 1, '降龙十八掌', 0),
    ('黄蓉', 0, 1, '打狗棍法', 0),
    ('黄药师', 1, 1, '弹指神通', 0),
    ('欧阳锋', 1, 1, '蛤蟆功', 0),
    ('梅超风', 0, 1, '九阴白骨爪', 0),
    ('乔峰', 1, 2, '降龙十八掌', 0),
    ('段誉', 1, 2, '六脉神剑', 0),
    ('虚竹', 1, 2, '天山六阳掌', 0),
    ('王语嫣', 0, 2, '神仙姐姐', 0),
    ('令狐冲', 1, 3, '独孤九剑', 0),
    ('任盈盈', 0, 3, '弹琴', 0),
    ('岳不群', 1, 3, '华山剑法', 0),
    ('东方不败', 0, 3, '葵花宝典', 0),
    ('胡斐', 1, 4, '胡家刀法', 0),
    ('苗若兰', 0, 4, '黄衣', 0),
    ('程灵素', 0, 4, '医术', 0),
    ('袁紫衣', 0, 4, '六合拳', 0);

ORM基本使用

建立表关系

涉及到字段ForeignKey、ManyToManyField、OneToOneField的使用
一对一
一对多
多对多

单表

环境准备

1.先创建一个应用,然后定义数据模型
app01/models.py

from django.db import models

# Create your models here.


class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()  # 年月日
    """
    DateField
    DateTimeField
        两个重要参数 
        auto_now:每次操作数据的时候 该字段会自动将当前时间更新
        auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
    """
    def __str__(self):
        return '对象:%s' % self.name

2.执行数据迁移

3.测试脚本
当你只是想测试django中的某一个py文件内容 那么你可以不用书写前后端交互的形式而是直接写一个测试脚本即可
脚本代码无论是写在应用下的tests.py还是自己单独开设py文件都可以,本文是在app01/test.py

import os


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
    import django
    django.setup()
    # 所有的代码都必须等待环境准备完毕之后才能书写
    from app01 import models

4.后续测试单表的增删改查就在app01/test.py中了,单独执行test.py即可

增删改查

import os


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
    import django
    django.setup()
    # 所有的代码都必须等待环境准备完毕之后才能书写
    from app01 import models
    
    # 增加
    # 第一种方式
    models.User.objects.create(username='kevin', password='123', age=20)

    # 第二种方式
    # cls = models.User  # <class 'app01.models.User'>
    res = models.User(username='tank', password='123', age=23)  # <class 'app01.models.User'>
    res.save()
    
    # 修改
    # 第一种方式
    models.User.objects.filter(pk=1).update(username='jerry', password='123')

    # 第二种方式
    user_obj = models.User.objects.filter(pk=3).first()
    user_obj.username = 'tom'
    user_obj.password = '888'
    user_obj.age = 16
    user_obj.save()

    # 删除
    # 第一种方式
    models.User.objects.filter(pk=2).delete()

    # 第二种方式
    user_obj = models.User.objects.filter(pk=1).first()
    user_obj.delete()

    # 查询
    # QuerySet对象,类似与列表套对象的形式,[obj1,obj2]
    res = models.User.objects.filter(pk=3)  # <QuerySet [<User: User object>]>
    print(res)   # QuerySet对象,
    print(res[0])  # 具体的对象 # User object
    print(res[0].username)  # 对象具体的属性  # tom
    print(res[0].password)
    print(res[0].age)
    print(models.User.objects.filter(age=16))  # <QuerySet [<User: User object>, <User: User object>]>

    # User object,用户对象,就是这个用户的一条记录信息
    res1 = models.User.objects.filter(pk=3).first()  # User object
    print(res1)
    print(res1.username)

    print(models.User.objects.filter(username='kevin', password='666').first())  # User object
    print(models.User.objects.filter(username='kevin').filter(password='666').first())  # 与上面是等价的

    # 查询所有
    res2 = models.User.objects.all()
    print(res2)  # <QuerySet [<User: User object>, <User: User object>, <User: User object>, <User: User object>]>
    for obj in res2:
        print(obj.username)


    # 查询一条记录的两种方法
    one1 = models.User.objects.filter(pk=10).first()  # 当查询记录不存在的时候返回None
    print(one1)  # User object  # None

    one2 = models.User.objects.get(pk=10)  # 查询记录不存在的时候,报错
    print(one2)  # User object  # 报错

    try:
        one2 = models.User.objects.get(pk=3)
    except Exception:
        print('数据查询不存在')

    # 查看原生SQL语句
    # 1.query属性  注意:返回的结果只有是QuerySet对象的时候,才有query属性,才能看sql语句
    res = models.User.objects.values_list('username', 'password', 'age') 
    print(res.query)
    # SELECT "app01_user"."username", "app01_user"."password", "app01_user"."age" FROM "app01_user"
        

    # res = models.User.objects.create(username='kevin', password='123', age=20)  # 插入成功的这条记录对象
    # print(res.query) # 会报错

    # 2.在settings中配置LOGGING日志

常见的查询方法

models.py文件:
    def __str__(self):  # str魔法,打印时直接运行该方法
        return self.username


1.all()       查询所有数据
2.filter()     带有过滤条件的查询
3.get()        直接拿数据对象 但是条件不存在直接报错


4.first()      拿queryset里面第一个元素
5.last()       拿queryset里面最后一个元素

res = models.User.objects.all().first()
print(res)
res = models.User.objects.all().last()
print(res)


6.values value_list

# values:指定查询的字段,返回的是列表套字典
# value_list:指定查询的字段,返回的是列表套元组

# select username, password from user
res = models.User.objects.values('username', 'password')  # 指定查询的字段,返回的是列表套字典
# <QuerySet [{'username': 'tom', 'password': '888'}, {'username': 'tank', 'password': '123'}]>
print(res)

res = models.User.objects.values_list('username', 'password', 'age')  # 指定查询的字段,返回的是列表套元组
# <QuerySet [('tom', '888', 16), ('tank', '123', 23)]>
print(res)
    

7.distinct()
# 去重, 每一条数据都要完全一样,如果说带主键,一定不会重复
res = models.User.objects.all().values('password', 'age').distinct()
print(res)


8.排序order_by()
# order by age asc/desc
res = models.User.objects.all().order_by('age')  # 默认是升序排列
res = models.User.objects.all().order_by('-age')  # 降序排列
res = models.User.objects.all().order_by('age', 'id')
res = models.User.objects.all().order_by('age', '-id')
print(res)
    

9.反转reverse()
# 反转,先排序,数据要先有序才能翻转
res = models.User.objects.all().order_by('-age').reverse()  # 与order_by('age')一致
print(res)


10.计数count()
# 对表中的数据计数
# select count(*) from user
res = models.User.objects.count()
print(res)


11.排除exclude()
# 排除用户名等于kevin1,输出剩下的用户对象
res = models.User.objects.exclude(username='kevin1')
print(res)


12.存在exists()
# 返回布尔值,存在该对象返回True,不存在返回False。
# res = models.User.objects.filter(pk=3).exists()
print(res)

基于双下划线查询

"""
属性__gt=值:大于值
属性__lt=值:大于值
属性__gte=值:大于等于值
属性__lte=值:大于等于值
属性__in=[值1,值2,值3]:值在列表中
属性__range=[11,40]:值位于区间[11,40]之间
属性__contains='s':模糊查找,属性值中有s
属性__startswith='s':模糊查找,属性值以s开头
属性__endswith='s':模糊查找,属性值中有s结尾

# 针对时间有:
属性__year='2023':年
属性__month='5' :月
属性__day='28' :日
属性__week_day='5' :星期
"""

# 实际应用
# 1.年龄大于19岁的数据
# select * from user where age > 10
res = models.User.objects.filter(age__gt=19).all()
print(res)

# 2.年龄小于19岁的数据
res = models.User.objects.filter(age__lt=19).all()
print(res)

# 年龄大于等于20岁的数据 e---------->equal
res = models.User.objects.filter(age__gte=20).all()
# 小于等于20岁
res = models.User.objects.filter(age__lte=20).all()

# 年龄是 16 或者 20 或者 23
# select * from user where age in (11, 20, 23)
res = models.User.objects.filter(age__in=[11,20,23]).all()
print(res)

# 年龄在18到40岁之间的,首尾都要
# select * from user where age between 18 and 40
res = models.User.objects.filter(age__range=[11,40])
print(res)

# 查询出名字里面含有s的数据,模糊查询
# select * from user where username like '%s%'
res = models.User.objects.filter(username__contains='s').all()
print(res)

# 用户名以s开头的
# select *from user where username like 's%'
res = models.User.objects.filter(username__startswith='s').all()
# 以s结尾
res = models.User.objects.filter(username__endswith='s').all()
print(res)



models.py
User表中新增一个注册时间字段
reg_time = models.DateField(default='2023-5-1')



# 数据类型
models.DateField(auto_now=True, auto_now_add=True)   # 年月日
models.DateTimeField()  # 年月日 时分秒
""""
auto_now=False,:当你往表里面更新一条记录的时候,这个字段的值会自动把当前时间每次都更新,你不用自己写了
auto_now_add=False: 当你往表里面新插入一条记录的时候,这个字段的值会自动把当前时间写进入,你不用自己写了
"""


# 查询出注册时间是 2023 5月
# select date_format(reg_time, '%Y-%m') from user where date_format(reg_time, '%Y-%m') = '2023-05'
res =  models.User.objects.filter(reg_time__month='5') # 查5月的
res =  models.User.objects.filter(reg_time__month='5', reg_time__year='2023') # 查2023年5月的
print(res)

多表

复习表关系

"""
表与表之间最多只有四种关系
	一对多关系
		在MySQL的关系中没有多对一一说
		一对多 多对一 都叫一对多!!!
	多对多关系
	一对一关系
	没有关系

表关系的建立
	一对多 ForeignKey
		外键字段建在多的一方
	多对多 ManyToManyField
		自己开设第三张存储(MySQL中)
        在Django orm中多对多的表关系有好几种(三种)创建方式
            自动创建
            手动创建
            半自动创建
        外键建在任意一方均可 但是推荐你建在查询频率较高的表中(orm查询方便)
	一对一 OneToOneField
		建在任意一方都可以 但是推荐你建在查询频率较高的表中

判断表之间关系的方式
	换位思考!!!
		员工与部门	
		图书与作者	
		作者与作者详情

"""
# 一对多关系
"""
判断表与表之间关系的时候 前期不熟悉的情况下使用换位思考方式,分别站在两张表的角度考虑:

员工表与部门表为例
	先站在员工表
		思考一个员工能否对应多个部门(一条员工数据能否对应多条部门数据)
			不能!!!
			(不能直接得出结论 一定要两张表都考虑完全)
	再站在部门表
		思考一个部门能否对应多个员工(一个部门数据能否对应多条员工数据)
			能!!!
	得出结论
		员工表与部门表示单向的一对多
		所以表关系就是一对多

foreign key/ForeignKey
	1 一对多表关系   外键字段建在多的一方
    2 在创建表的时候 一定要先建被关联表 
    3 在录入数据的时候 也必须先录入被关联表

级联更新   >>>   同步更新
级联删除   >>>   同步删除

"""

# 多对多
"""
针对多对多字段表关系 不能在两张原有的表中创建外键
需要你单独再开设一张 专门用来存储两张表数据之间的关系
"""

# 一对一

# 模型层里面1.X外键默认都是级联更新删除的
# 但是到了2.X和3.X中需要你自己手动配置参数
    # django 1.x
	models.ForeignKey() 	
    # django 2.x, 3.x中,需要两个位置参数,1:所关联的模型类,2:on_delete选项
	models.ForeignKey(User, on_delete=models.CASCADE)
    

环境准备

准备好四张表用好做测试

"""
图书表,出版社表,作者表,作者详情表

表之间的关系:
  图书表和出版社表  是 一对多
  作者表和图书表    是 多对多
  作者表和作者详情表 是 一对一
"""
from django.db import models
from django.utils import timezone


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)

    def __str__(self):
        return self.name


class AuthorDetail(models.Model):
    phone = models.BigIntegerField()  # 电话号码用BigIntegerField或者直接用CharField
    addr = models.CharField(max_length=64)


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 一对一
    author_detail = models.OneToOneField(AuthorDetail, on_delete=models.CASCADE)


class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_date = models.DateTimeField(auto_now_add=True)

    # 一对多
    publish = models.ForeignKey(Publish, on_delete=models.CASCADE, default=1)
    # 多对多
    authors = models.ManyToManyField(Author)

一对多的外键增删改查数据

book图书表和publish出版社表

增加

# 一定要先添加数据到没有外键的表
# 1.此处是先增加一条数据到出版社表
publish_obj = models.Publish.objects.create(name="上海出版社", addr="上海")
# 2.再创建书籍与出版社关联,具体有两种方式
# 第一种方式:直接指定外键字段的id值,外键id关联的是publish表的id值
models.Book.objects.create(title='我的自传', price=99, publish_id=1)  # 日期字段是自增的,所以不用指定
# 第二种方式:先查出对应出版社表的记录对象,book表中添加对象
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.create(title='我的自传1', price=99, publish=publish_obj)

删除

# 可以只删掉一个书籍对象
models.Book.objects.filter(title='我的自传1').delete()

# 因为级联删除的存在,所以删出一个出版社对象,对应的图书对象会一并清除
models.Publish.objects.filter(name="555出版社").delete()

修改

# 第一种方式
models.Book.objects.filter(pk=1).update(title='哈哈哈', publish_id=2)

# 第二种方式
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)

多对多的外键增删改查数据

author作者表和book图书表, book_authors表
多对多本质上就是在操作第三张表;book_authors表。

# 增加
对象.外键字段.add()
# 删除
对象.外键字段.remove()
# 修改
对象.外键字段.set([ ])
# 查询
对象.外键字段.all()[0]

增加

# 给书籍主键为2的图书添加一个作者
book_obj = models.Book.objects.filter(pk=3).first()
print(book_obj.authors)  # app01.Author.None------------>就相当于已经到了第三张表

book_obj.authors.add(3)  # 增加了pk=3的作者

# 图书和作者是不是多对多,一本书可以有多个作者
book_obj.authors.add(2, 1)

删除

book_obj.authors.remove(1)
book_obj.authors.remove(2, 3)

修改

# book_obj.authors.set(1, 3)  # 这样写是有问题的
# TypeError: set() takes 2 positional arguments but 3 were given

book_obj.authors.set([1, 3])

book_obj.authors.set([2])  # 单个序号也要放到列表中

查询

# 查询图书主键为1的所有作者
print(book_obj.authors)  # app01.Author.None
print(book_obj.authors.all())  
# <QuerySet [<Author: Author object>, <Author: Author object>, <Author: Author object>]>
# QuerySet列表中对象的顺序是从表中从上到下取到赋值的

author_obj = book_obj.authors.all()[0]
print(author_obj.name)  # tank

多表查询

多表查询的方式有两种:子查询和连表查询

正反向的概念

  • 正向: 外键在我手上,我查你,就是正向查询
  • 反向: 外键在我手上,你查我,就是反向查询
eg:
	book----------外键字段在book表中------------>publish-------------->正向查询
    publish-------外键字段在book表中------------->book----------------->反向查询

'''
	判断出来正反向之后,如何查询
		正向查询按字段(外键字段)
		反向查询按表名小写或者表名小写_set
'''

子查询

# 1.查询书籍主键为1的出版社 --- 一对多
# 先查询书籍,赋值给一个对象
book_obj = models.Book.objects.filter(pk=1).first()
# book --> publish   是正向查询 ----> 正向查询使用外键字段
# book对象点外键字段(publish) ---> 就相当于是出版社对象了
print(book_obj.publish)
publish_obj = book_obj.publish  # Publish object
# 输出出版社的信息
print(publish_obj.name)  # 人民出版社
print(publish_obj.addr)  # 上海
    

# 2.查询书籍主键为2的作者 --- 多对多
# book --> author   是正向查询 ----> 正向查询使用外键字段
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.authors)  # app01.Author.None
print(book_obj.authors.all())  # <QuerySet [<Author: Author object>, <Author: Author object>]>
    

# 3.查询作者jerry的电话号码 --- 一对一
# author --> authordetail   是正向查询 ----> 正向查询使用外键字段
author_obj = models.Author.objects.filter(name='jerry').first()
print(author_obj)  # Author object
author_detail_obj = author_obj.author_detail
print(author_detail_obj)  # AuthorDetail object
print(author_detail_obj.phone)  # 120




# 4.查询出版社是北京出版社出版的书 --- 一对多
# 先查出出版社的对象
publish_obj = models.Publish.objects.filter(name='北京出版社').first()
# publish --> book   是反向查询 ----> 反向查询按表名小写或者_set
print(publish_obj.book_set)  # app01.Book.None
print(publish_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>]>
book_obj = publish_obj.book_set.all() 
print(book_obj[0].title)  # 笑傲
print(book_obj[1].title)  # 洋哥自传


# 5.查询作者是jerry写过的书  --- 多对多
author_obj = models.Author.objects.filter(name='jerry').first()
# author --> book   是反向查询 ----> 反向查询按表名小写或者_set
print(author_obj.book_set)  # app01.Book.None
print(author_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>]>
book_obj = author_obj.book_set.all()
print(book_obj[0].title)  # 红楼梦
print(book_obj[1].title)  # 余华


# 6.查询手机号是110的作者姓名  --- 一对一
# authordetail表
author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first()
# authordetail --> author   是反向查询 ----> 反向查询按表名小写或者_set
print(author_detail_obj.author)  # Author object
print(author_detail_obj.author.name)  # kevin

连表查询(基于双下划线)

基于双下划线的查询的关键是:可以使用双下滑线语法来指定多个表之间的关系。

  • 正向,按照外键字段名__字段
  • 反向,是表名小写__字段
# 1.查询jerry的手机号和作者姓名 
# author --> author_detail   是正向查询  ---> 外键字段
author_obj = models.Author.objects.filter(name='jerry').values('name', 'author_detail__phone')  # 外键字段__字段名
print(author_obj)  # QuerySet对象
# <QuerySet [{'name': 'jerry', 'author_detail__phone': 120}]>


# author_detail --> author   是反向查询 ---> 表名小写
author_detail_obj = models.AuthorDetail.objects.filter(author__name='jerry').values('author__name', 'phone')
print(author_detail_obj)
# <QuerySet [{'author__name': 'jerry', 'phone': 120}]>



# 2.查询书籍主键为1的出版社名称和书的名称
# book --> publish   是正向查询  ---> 外键字段
book_obj = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(book_obj)
# <QuerySet [{'title': '红楼梦', 'publish__name': '人民出版社'}]>


# publish --> book   是反向查询  ---> 表名小写
publish_obj = models.Publish.objects.filter(book__pk=1).values('book__title', 'name')
print(publish_obj)
# <QuerySet [{'book__title': '红楼梦', 'name': '人民出版社'}]>
    
    
# 3.查询书籍主键为1的作者姓名
# book --> author   是正向查询  ---> 外键字段
models.Book.objects.filter(pk=1).values('authors__name')
print(book_obj)
# <QuerySet [{'authors__name': 'jerry'}, {'authors__name': 'kevin'}, {'authors__name': 'tank'}]>


# publish --> book   是反向查询  ---> 表名小写
author_obj = models.Author.objects.filter(book__pk=1).values('name')
print(author_obj)
# <QuerySet [{'authors__name': 'jerry'}, {'authors__name': 'kevin'}, {'authors__name': 'tank'}]>


# 4.查询书籍主键是1的作者的手机号
# book ---> author ---> author_detail  正向查询   ---> 外键字段
author_detail_obj = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(author_detail_obj)
# <QuerySet [{'authors__author_detail__phone': 120}, {'authors__author_detail__phone': 110}, {'authors__author_detail__phone': 130}]>
    

 # 反向查询 ---> 表名小写
book_obj = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
print(book_obj)
# <QuerySet [{'phone': 120}, {'phone': 110}, {'phone': 130}]>
    
    
    '''当表特别多的时候,ORM语句其实并不好写,如果你真遇到这种不好写的语句的时候,就是要原生sql语句'''

查询相关参数补充

多对对参数:symmetrical
一对多参数:related_name与related_query_name

聚合与分组查询

聚合查询

分组查询

F与Q查询

F查询

F查询够帮你直接获取到表中的某个字段对应的值,具体应用如下

from django.db.models import F

# 1.查询卖出数大于库存数的书籍
# select * from book where sale_num > kucun;
res = models.Book.objects.filter(sale_num__gt=F('kucun'))
print(res)

# 2.将所有书籍的价格提升500块
# update app01_book set price = price+500;
res = models.Book.objects.update(price=F('price')+500)

# 3.将所有书的名称后面加上爆款两个字
# update app01_book set title = title + 'haha' ;
# models.Book.objects.update(title=F('title')+'haha') # 不能这样写
    # 1.先导入两个模块
    # 2.原有字段包裹在F中
    # 3.新的字符包裹在Value中

from django.db.models.functions import Concat
from django.db.models import Value

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

Q查询

对于filter()方法内逗号分隔开的多个条件,都是and关系,如果想用or或者not关系,则需要使用Q

# 1.查询卖出数大于100或者价格小于600的书籍
# select * from book where sale_num > 100 or price < 600;
res = models.Book.objects.filter(sale_num__gt=100, price__lt=600) # and关系
res = models.Book.objects.filter(sale_num__gt=100).filter(price__lt=600) # and关系


# 导入Q
from django.db.models import Q
res = models.Book.objects.filter(Q(sale_num__gt=100), Q(price__lt=600))  # , 是and关系
res = models.Book.objects.filter(Q(sale_num__gt=100)|Q(price__lt=600))  # | 是or关系
res = models.Book.objects.filter(~Q(sale_num__gt=100)|Q(price__lt=600))  # ~ 是非的关系
print(res)

Q查询的高阶用法:能够以字符串作为查询字段

res = models.Book.objects.filter(Q(sale_num__gt=100) | Q(price__lt=600))

# 前端传给后端额的是字符串'price'
requests.GET.get('sort') # price
# res = models.Book.objects.filter(price__gt=100)
# res = models.Book.objects.filter('price'+'__gt'=100) 出错

q = Q()
q.connector = 'or' # 把多个查询条件改为OR关系了
q.children.append(('maichu__gt', 100))
q.children.append(('sale_num__lt',100))
# 不同条件之间使用,隔开 --- and关系 
res = models.Book.objects.filter(q)  
print(res)
posted @ 2025-09-23 17:43  城市炊烟  阅读(12)  评论(0)    收藏  举报