Django框架之模型层

一、前期准备

1、测试脚本

当你只是想要测试Django中的某一个py文件内容,那么你可以不用书写前后端交互的形式,而是直接写一个测试脚本即可

这内容其实就是最外部 manage.py 文件中的上面几句话

脚本代码无论是写在应用下的 tests.py文件还是自己新建文件,将内容写在新文件中,都会生效

python 复制代码
from django.test import TestCase

# Create your tests here.
import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django05.settings')
    import django
    django.setup()
    
	# 在下面书写我们需要测试的代码
    # 即所有的测试代码都必须等待环境准备完毕之后才能书写

2、数据准备

在models.py里面创建我们需要的数据库中的表

python 复制代码
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 f'对象:{self.name}'

3、配置文件

python 复制代码
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        # 数据库名字
        'NAME': 'day64',
        # 用户
        "USER": "root",
        # 密码
        "PASSWORD": "111111",
        # IP
        "HOST": "127.0.0.1",
        # 端口
        "PORT": 3306,
        # 编码集
        "CHARSET": "utf8",
    }
}
  • 在项目下的 init.py 中声明数据库类型
python 复制代码
import pymysql

pymysql.install_as_MySQLdb()

二、单表操作

1、数据的增加

python 复制代码
from django.test import TestCase

# Create your tests here.
import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django05.settings')
    import django
    django.setup()
    
	from app01 import models

    models.User.objects.all()

    # register_time (1)支持自己指定传值
    res = models.User.objects.create(name='zhang', age=67, register_time='2000-1-1')
    # 返回值为对象本身
    print(res)  # User object
    
    # register_time (2)支持传入日期对象
    import datetime
    # 生成一个当前的时间对象
    ctime = datetime.datetime.now()
    user_obj = models.User(name='quan', age=54, register_time=ctime)
    user_obj.save()

2、数据的删除

pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段

用了pk之后,就不需要指代当前表的主键字段到底叫什么了

  • uid
  • pid
  • sid
  • ...

(1)查询后直接删除

python 复制代码
res = models.User.objects.filter(pk=2).delete()

(2)先查询再删除

python 复制代码
user_obj = models.User.objects.filter(pk=1).first()
user_obj.delete()

3、数据的修改

(1)查询后直接修改

python 复制代码
res = models.User.objects.filter(pk=5).update(name="quan")

(2)先查询再修改

  • get方法返回的直接就是当前数据对象,但是方法不推荐使用
  • 因为一旦数据不存在该方法会直接报错,而filter则不会,所以我们一般都是用filter
python 复制代码
# 不推荐使用 : 如果查询的数据不存在会直接报错 ,fileter不会
user_obj = models.User.objects.get(pk=6)
user_obj = models.User.objects.filter(pk=6)
# 调用对象更改数据
user_obj.name = "xiao"
user_obj.save()

4、数据的查询

(1)查询全部:all

python 复制代码
user_obj = models.User.objects.all()

(2)按指定条件过滤:filter和get

  • 方式一:filter
python 复制代码
user_obj = models.User.objects.filter(age=18)
# 筛选后得到的对象是一个 QuerySet 对象,里面会有所有符合添加的数据对象
print(user_obj) # <QuerySet [<User: User object (1)>, <User: User object (2)>]>

# 方法补充
# (1)获取到当前 QuerySet 对象的第一个对象
print(user_obj.first())
# User object (1)
print(user_obj.last())
# User object (2)
  • 方式二:get
python 复制代码
user_obj = models.User.objects.get(pk=1)
# 获取到的是一个数据对象,只能获取到唯一条件的数据对象
print(user_obj) # User object (1)

# 如果有多个符合条件的数据会报错
# app01.models.User.MultipleObjectsReturned: get() returned more than one User -- it returned 2!
# 如果有不符合条件的数据会报错
# app01.models.User.DoesNotExist: User matching query does not exist.

5、获取到指定字段的数据

(1)获取到一个字段的数据:values

  • 返回的数据格式为列表套字典 - 本质上是一个 QuerySet 对象 ,而不是真的列表
python 复制代码
user_obj = models.User.objects.values("name")
print(user_obj)
# <QuerySet [{'name': 'dream'}, {'name': 'chimeng'}]>

(2)获取到多个字段的数据:values_list

  • 返回的数据格式为列表套元祖 - 本质上是一个 QuerySet 对象 ,而不是真的列表
python 复制代码
user_obj = models.User.objects.values_list("name","age")
print(user_obj) 
# <QuerySet [('dream', 18), ('chimeng', 18)]>

6、查看内部SQL语句的方式

(1)方式1

  • queryset对象.query==>查看orm内部的sql语句
python 复制代码
res = models.User.objects.values_list('name', 'age')
print(res.query)  # 查看内部SQL语句

(2)方式2:所有的sql语句都能查看

  • 去配置文件中配置一下即可
python 复制代码
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

(3)小结

  • 查看sql语句的方式,只能用于queryset对象
  • 只有queryset对象才能够点击query查看内部的sql语句

7、去重

去重一定要是一模一样的数据,如果有主键,那么肯定不一样

所以一定要去除主键后再去重

python 复制代码
res = models.User.objects.values('name', 'age').distinct()

8、排序

(1)默认升序

python 复制代码
user_obj = models.User.objects.order_by('age')
print(user_obj)
# <QuerySet [<User: User object (1)>, <User: User object (2)>, <User: User object (3)>, <User: User object (4)>]>

(2)降序

python 复制代码
user_obj = models.User.objects.order_by('-age')
print(user_obj)
# <QuerySet [<User: User object (4)>, <User: User object (3)>, <User: User object (1)>, <User: User object (2)>]>

(3)反转

  • 反转的前提是数据已经经过排序过的数据

  • 只能对有序的数据进行反转

python 复制代码
user_obj = models.User.objects.order_by('age').reverse()
print(user_obj)
# <QuerySet [<User: User object (4)>, <User: User object (3)>, <User: User object (1)>, <User: User object (2)>]>

9、统计个数

python 复制代码
user_obj = models.User.objects.count()
print(user_obj)
# 4

10、剔除结果

  • 排出在外
  • 将某个数据排出在结果之外
python 复制代码
user_obj = models.User.objects.exclude(name="dream")
print(user_obj)
# res = models.User.objects.exclude(user="dream")

11、是否存在

  • 是否存在
  • 返回的是布尔值
  • 用处不大,因为数据本身就有布尔值的状态
python 复制代码
user_obj = models.User.objects.filter(name="dream").exists()
print(user_obj)
# True

12、总结:必知必会十三条

python 复制代码
# 以下方法的前提均为 models.模型表名.objects 之后的方法

1. all()      查询所有数据
2. filter()   带有过滤条件的查询
  我们在利用数据的主键字段筛选数据的时候,可以不考虑主键字段叫什么,直接用pk代替
3. get()      直接拿数据对象,但是条件不存在直接报错
4. first()    拿queryset里面第一个元素
5. last()     拿queryset里面最后一个元素

6. value()    可以指定获取的数据字段   select name,age from ...  返回的结果是列表套字典  <QuerySet [{'name': 'xiao', 'age': 18}, {'name': 'quandsb', 'age': 54}]>
7. values_list()    返回的结果像是列表套元组  <QuerySet [('xiao', 18), ('quandsb', 54)]>

8. distinct()
    res = models.User.objects.values('name', 'age').distinct()
    去重一定要是一模一样的数据,如果有主键,那么肯定不一样
9. reverse()  反转的前提是数据已经排过序了
10. order_by()  排序
    res = models.User.objects.order_by('age')  # 默认升序
    res = models.User.objects.order_by('-age')  # 降序
11. count()    统计当前数据的个数
12. exclude()    排除在外
13. exists()    

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

三、神奇的双下划线查询

1、条件大于

  • 年龄大于35岁的数据
python 复制代码
res = models.User.objects.filter(age__gt=35)
print(res)

2、条件小于

  • 年龄小于35岁的数据
python 复制代码
res = models.User.objects.filter(age__lt=35)
print(res)

3、条件大于等于

年龄大于等于35岁的数据

python 复制代码
res = models.User.objects.filter(age__gte=35)
print(res)

4、条件小于等于

  • 年龄小于等于35岁的数据
python 复制代码
res = models.User.objects.filter(age__lte=35)
print(res)

5、或条件

  • 年龄是18或者32或者40
python 复制代码
res = models.User.objects.filter(age__in=(18, 32, 40))
print(res)

6、两个条件之间

  • 年龄是18-40之间
  • 首尾都要
python 复制代码
res = models.User.objects.filter(age__range=(18, 40))
print(res)

7、模糊查询

  • 查询出名字中含有 n 的数据 -- 模糊查询
python 复制代码
res = models.User.objects.filter(name__contains='n')
print(res)
  • 查询出名字中含有 N 的数据,默认区分大小写
python 复制代码
res = models.User.objects.filter(name__contains='N')
print(res)
  • 查询出名字中含有 N 的数据,忽略大小写
python 复制代码
res = models.User.objects.filter(name__icontains='N')
print(res)

8、以指定条件开头/结尾

  • 以什么开头/结尾
python 复制代码
res = models.User.objects.filter(name__startswith='d')
print(res)
  • 以什么结尾
python 复制代码
res = models.User.objects.filter(name__endswith='m')
print(res)

9、查询时间日期

  • 查询出注册时间是2020年1月份的数据/年/月/日
python 复制代码
res = models.User.objects.filter(register_time__month='1')
print(res)
res = models.User.objects.filter(register_time__year='2020')
print(res)
res = models.User.objects.filter(register_time__day='28')
print(res)

四、多表查询引入

1、数据准备

在models.py里面创建我们需要的数据库中的表

(1)创建图书表

  • 一本书只能有一个出版社,建立外键关系为一对一
  • 一本书可以有多个作者,一个作者可以写多本书,建立外键关系为多对多
python 复制代码
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_date = models.DateField(auto_now_add=True)

    # 库存
    kucun = models.IntegerField(default=1000)
    # 卖出
    maichu = models.IntegerField(default=1000)

    # 自定义字段使用
    # myfield = MyCharField(max_length=16,null=True)


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

    # 打印输出数据,方便观看
    def __str__(self):
        return self.title

(2)创建出版社表

  • 出版社不需要额外的外键关系
python 复制代码
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    # varchar(254)  该字段类型不是给models看的,而是给后学我们会学到的校验性组件看的

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

(3)创建作者表

  • 作者具有作者详情,所以作者和作者详情表是一对一关系
python 复制代码
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    
    # 一对一
    author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)

(4)创建作者详情表

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

(5)迁移数据库

bash 复制代码
# 生成迁移记录
python manage.py makemigrations

# 迁移记录对数据库生效
python manage.py migrate

2、外键的增删改查

(1)一对多外键的增删改查

① 增加
python 复制代码
# 1. 直接写实际字段  id
models.Book.objects.create(title='解忧杂货店', price=103.45, publish_id=1)
# 2. 虚拟字段  对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='西游记', price=666.66, publish=publish_obj)
② 删除
python 复制代码
models.Publish.objects.filter(pk=1).delete()  # 级联删除
③ 修改
python 复制代码
#  - 直接写实际字段
models.Book.objects.filter(pk=1).update(publish_id=2)
#  - 虚拟字段
publish_obj = models.Publish.objects.filter(pk=1).first()

models.Book.objects.filter(pk=1).update(publish=publish_obj)
④ 查询
python 复制代码
# 先查询对象,然后对象.id
publish_obj = models.Publish.objects.filter(pk=1).first()
publish_obj.id = 2

(2)对多对外键的增删改查

  • 多对多 增删改查 就是在操作第三张表
① 增加
  • 如何给书籍添加作者?
  • add给第三张表添加数据,括号内既可以传数字,也可以传对象,并且都支持多个
python 复制代码
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(1)  # 书籍id为1的书籍绑定了一个主键为1的作者
book_obj.authors.add(2,3)

author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj,author_obj1)
② 删除

remove:括号内既可以传数字,也可以传对象,并且都支持多个

python 复制代码
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(2)
book_obj.authors.remove(1,3)

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj,author_obj1)
③ 修改

set:括号内必须传一个可迭代对象,该对象既可以是数字也可以是对象,并且都支持多个

python 复制代码
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set([1,2])  # 括号内必须是一个可迭代对象
book_obj.authors.set([3])

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1])
④ 清空

clear:括号内不需要添加任何参数

python 复制代码
# 在第三张表中清除某一本书和作者的绑定关系
book_obj = models.Book.objects.filter(pk=1).first()
# 不要加任何参数
book_obj.authors.clear()

五、多表查询应用

1、正反向的概念

在Django中,正向和反向查询是针对模型之间的关联关系而言的。这两个概念可以帮助您理解如何在模型之间进行数据访问和查询。

(1)正向查询

  • 正向查询:正向查询是指您从定义关系的模型开始向关联模型查询数据。在一对多关系中,正向查询是从拥有外键的模型(一的一方)到外键所关联的模型(多的一方)的查询。

在Django中,正向查询是指从指定的模型实例开始,沿着定义的关联向另一个模型实例查询数据。正向查询是通过使用关联字段(ForeignKey、OneToOneField、ManyToManyField)来实现的。以下是正向查询的详细说明和示例:

① 一对多关系的正向查询

在一对多关系中,一个模型实例可以关联多个另一个模型实例。通过正向查询,您可以从一的一方访问多的一方的数据。

假设有以下模型:

python 复制代码
class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  • 正向查询示例

    python 复制代码
    # 从 Author 到 Book,获取作者写的所有书籍
    author = Author.objects.get(id=1)
    books = author.book_set.all()
② 多对多关系的正向查询

在多对多关系中,两个模型之间存在多对多的关联。通过正向查询,您可以从一个模型实例访问另一个模型实例的数据。

假设有以下模型:

python 复制代码
class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course)

class Course(models.Model):
    name = models.CharField(max_length=100)
  • 正向查询示例

    python 复制代码
    # 从 Student 到 Course,获取学生选修的所有课程
    student = Student.objects.get(id=1)
    courses = student.courses.all()
③ 一对一关系的正向查询

在一对一关系中,每个模型实例只能与另一个模型实例相关联。通过正向查询,您可以从一个模型实例访问另一个模型实例的数据。

假设有以下模型:

python 复制代码
class Person(models.Model):
    name = models.CharField(max_length=100)

class Profile(models.Model):
    bio = models.TextField()
    person = models.OneToOneField(Person, on_delete=models.CASCADE)
  • 正向查询示例

    python 复制代码
    # 从 Person 到 Profile,获取人物的个人资料
    person = Person.objects.get(id=1)
    profile = person.profile

通过这些示例,您可以更好地理解和使用Django中的正向查询功能,从而方便地在模型之间进行数据访问和查询操作。

(2)反向查询

  • 反向查询:反向查询是指您从关联模型开始向定义关系的模型查询数据。在一对多关系中,反向查询是从外键所关联的模型(多的一方)到拥有外键的模型(一的一方)的查询。

在Django中,反向查询是通过使用双下划线(__)来访问定义关系的模型的字段。这种查询方法允许您从关联模型开始向定义关系的模型查询数据。下面是一些反向查询的详细内容和示例:

① 一对多关系的反向查询

在一对多关系中,一个模型拥有多个关联模型的实例。通过反向查询,您可以从多的一方访问一的一方的数据。

假设有以下模型:

python 复制代码
class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  • 反向查询示例

    python 复制代码
    # 从 Book 到 Author,获取书籍的作者
    book = Book.objects.get(id=1)
    author = book.author
② 多对多关系的反向查询

在多对多关系中,两个模型之间存在多对多的关联。通过反向查询,您可以从一个模型访问另一个模型的数据。

假设有以下模型:

python 复制代码
class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course)

class Course(models.Model):
    name = models.CharField(max_length=100)
  • 反向查询示例

    python 复制代码
    # 从 Course 到 Student,获取选修该课程的学生
    course = Course.objects.get(id=1)
    students = course.student_set.all()
③ 多层级关联的反向查询

如果模型之间存在多层级的关联,您可以通过多次使用双下划线来进行多层级反向查询。

假设有以下模型:

python 复制代码
class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

class Review(models.Model):
    content = models.TextField()
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
  • 多层级反向查询示例

    python 复制代码
    # 从 Review 到 Author,获取书评所属书籍的作者
    review = Review.objects.get(id=1)
    author = review.book.author

通过这些示例,您可以更好地理解和使用Django中的反向查询功能,从而方便地在模型之间进行数据访问和查询操作。

(3)查询方法

  • 正向查询按字段
  • 反向查询按表明名(小写),_set
  • book>>>>外键字段在书那(正向)>>>>publish
    publish>>>>外键字段在书那(反向)>>>>book

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

(1)查询书籍主键为1的出版社

python 复制代码
book_obj = models.Book.objects.filter(pk=1).first()
# 书查出版社 - 正向 - 按字段查
res = book_obj.publish
print(res)  # Publish object
print(res.name)  # 东方出版社
print(res.addr)  # 东方

(2)查询书籍主键为2的作者

python 复制代码
book_obj = models.Book.objects.filter(pk=1).first()
# 书查作者 - 正向查询按字段
res = book_obj.authors
print(res)  # app01.Author.None
# 列表中存放的是作者对象
print(res.all())  # <QuerySet [<Author: Author object>]>

(3)查询作者的电话号码

python 复制代码
author_obj = models.Author.objects.filter(name="dream").first()
# 作者查询作者详情 - 正向查询按字段
res = author_obj.author_detail
print(res)  # AuthDetail object
print(res.phone)  # 110
print(res.addr)  # 山东

(4)查询出版社是东方出版社出版的书

python 复制代码
# 先拿到出版社对象
publish_obj = models.Publish.objects.filter(name="东方出版社").first()
# 出版社查书 - 主键字段在书 - 反向查询
res = publish_obj.book_set.all()
# publish_obj.book_set
# print(res) # app01.Book.None
# publish_obj.book_set.all()
print(res)  # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>

(5)查询作者是xiao写过的书

python 复制代码
# 先拿到作者对象
author_obj = models.Author.objects.filter(name="xiao").first()
# 作者查书 - 主键在书 - 反向
res = author_obj.book_set.all()
print(res)  # <QuerySet [<Book: Book object>]>

(6)查询手机号是 110的作者姓名

python 复制代码
# 先拿到作者详情的对象
author_detail_obj = models.AuthDetail.objects.filter(phone=110).first()
# 详情查作者 - 主键在作者 - 反向
res = author_detail_obj.author
print(res)  # Author object
print(res.name)  # xiao

(7)补充

  • 正向查询什么时候需要加.all()

    • 当查询返回的结果可以是多个的时候就需要用 .all()
    • 当查询的结果只有一个的时候就不需要加
    • 例如
      • book_obj.publish 一本书对应一个出版社
      • book_obj.authors.all() 一本书可能对应多个作者,所以需要.all()
      • author_obj.author_detail 一个作者对应一个作者详情
  • 反向查询什么时候需要加 _set.all()

    • 查询结果可以是多个的时候需要加
    • 当查询的结果只有一个的时候就不需要加
    • 如果不加的话,会报 app01.Book.None这个错误
  • 在书写ORM语句的时候跟写SQL语句一样的

  • 不要企图一次性将ORM语句写完,如果比较复杂,需要写一些看一些

3、基于双下划线的跨表查询(联表操作)

(1)查询xiao的手机号和作者的姓名

  • 正向:先查询到作者信息再 .value(需要查询信息的表__需要查询的字段,其他字段)
python 复制代码
res = models.Author.objects.filter(name="xiao").values('author_detail__phone', 'name')
print(res)  # <QuerySet [{'author_detail__phone': 110, 'name': 'xiao'}]>
  • 反向:先拿到详情,再用作者详情关联作者表,通过 __字段的方法 过滤出我们想要的指定数据
python 复制代码
res = models.AuthDetail.objects.filter(author__name="xiao").values('phone', 'author__name')
# AuthDetail.objects.filter(author__name="xiao")
print(res)  # <QuerySet [<AuthDetail: AuthDetail object>]>
# AuthDetail.objects.filter(author__name="xiao").values('phone','author__name')
print(res)  # <QuerySet [{'phone': 110, 'author__name': 'xiao'}]>

(2)查询书籍主键ID为1的出版社名字和书的名字

  • 正向:先过滤出书籍ID为1的书籍对象,再去关联出版者表,利用__字段取值
python 复制代码
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': '三国演义', 'publish__name': '东方出版社'}]>
  • 反向:先查询到指定出版社,再从出版社反向找到书籍名字
python 复制代码
res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '东方出版社', 'book__title': '三国演义'}]>

(3)查询书籍主键ID为1的作者姓名

  • 正向:先拿到 书籍主键ID为1的对象,再关联作者信息表,通过__字段取值
python 复制代码
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)  # <QuerySet [{'authors__name': 'xiao'}]>
  • 反向 : 先拿到 书籍ID为1的作者数据再去取作者的名字
python 复制代码
res = models.Author.objects.filter(book__id=1).values('name')
print(res)  # <QuerySet [{'name': 'xiao'}]>

(4)查询书籍主键是1的作者的手机号

python 复制代码
# book author authordetail
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 110}]>

(5)小结

  • 只要掌握了正反向的概念以及双下划线查询
  • 就可以无限跨表

六、聚合查询

1、知识储备

  • 聚合查询通常情况下都是配合分组一起使用的
  • 只要是和数据库相关的模块基本上都在 django.db.models 里面
  • 如果这里面没有 那大概率可能在 django.db 里面

正常情况下,我们是需要 先进行分组再进行 聚合函数运算的

但是Django给我们提供了一种方法 : aggregate 可以不分组进行某个字段的聚合函数。

2、aggregate()

在Django中,aggregate()方法用于执行聚合查询,对查询结果集进行聚合计算并返回一个包含聚合计算结果的字典。这个方法允许您在查询中使用各种聚合函数(如Count、Sum、Avg、Min、Max等)来实现不同的聚合操作。

(1)语法

python 复制代码
aggregate(**kwargs)

(2)参数

  • **kwargs: 一个字典,其中键是用于标识聚合计算结果的别名,值是聚合表达式(如Count、Sum、Avg等)。

(3)示例

  • 导入聚合函数
python 复制代码
from django.db.models import Max, Min, Sum, Count, Avg

示例1:所有书的平均价格

python 复制代码
res = models.Book.objects.aggregate(Avg('price'))
print(res)  # {'price__avg': 1890.083333}

在这个示例中,Avg('price')表示对Book模型中的书籍价格进行平均值计算。

示例2:所有聚合函数一起使用

python 复制代码
res = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Sum('price'), Count('pk'))
print(res) # {'price__avg': 1890.083333, 'price__max': Decimal('5959.25'), 'price__min': Decimal('555.25'), 'price__sum': Decimal('11340.50'), 'pk__count': 6}

(4)注意事项

  • aggregate()方法返回一个字典,其中键是您为每个聚合结果指定的别名,值是相应的聚合计算结果。
  • 您可以同时指定多个聚合计算,每个计算都应该作为**kwargs参数传递。
  • 聚合查询通常用于获取整个查询集的聚合信息,而不是在每个对象上执行聚合计算。

(5)总结

通过使用aggregate()方法,您可以方便地在Django中执行各种聚合查询操作,如计数、求和、平均值等。这对于分析数据和生成汇总报告非常有用。记住,在使用aggregate()时,您可以根据需要选择合适的聚合函数,并为每个聚合结果指定一个别名以便后续引用。

七、分组查询

1、知识储备

MySQL分组查询都有哪些特点?

分组之后默认只能获取到分组的数据,组内其他字段都无法直接获取了

2、annotate()

在Django中,annotate()方法用于在查询结果集中添加聚合计算的注释,而不是对整个查询结果集进行聚合。这允许您在每个对象上执行聚合计算,并将计算结果作为新的字段添加到每个对象中。

(1)语法

python 复制代码
annotate(**kwargs)
  • models 后面跟的是什么,就是按什么分组
python 复制代码
res = models.Book.objects.annotate()
  • 如果想按照指定的字段分组该如何处理

  • 如果 annotate 前面没东西 则会按照 Book 分组 ,如果前面有参数 就会按照前面的参数进行分组 price

python 复制代码
models.Book.objects.values('price').annotate()

(2)参数

  • **kwargs: 一个字典,其中键是用于标识注释字段的新字段名,值是聚合表达式(如Count、Sum、Avg等)。

(3)示例

  • 导入聚合函数
python 复制代码
from django.db.models import Max, Min, Sum, Count, Avg

示例1:计算每个作者的书籍数量

python 复制代码
from django.db.models import Count

authors = Author.objects.annotate(book_count=Count('book'))
for author in authors:
    print(author.name, author.book_count)

在这个示例中,Count('book')表示对每个作者的书籍数量进行计数,并将计算结果作为新字段book_count添加到每个作者对象中。

示例2:统计不止一个作者的图书

python 复制代码
# (3.1)先按照图书分组
# (3.2)过滤出不止一个作者的图书
# 我的数据有限,我统计的是大于 0 的作者的图书
res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=0).values('title','author_num')
print(res)  # <QuerySet [{'title': '三国演义', 'author_num': 1}]>

只要你的ORM语句得出的结果还是一个queryset对象,那么他就还可以继续无限制的点queryset对象封装的方法

(4)注意事项

  • annotate()方法用于对每个对象执行聚合计算,将计算结果作为新字段添加到每个对象中。
  • 您可以同时指定多个注释字段,每个字段应作为**kwargs参数传递。
  • annotate()方法通常用于在查询结果集中添加聚合计算的注释,以便在后续操作中使用这些计算结果。
  • 如果你们的机器上如果出现分组查询报错的情况,那么你就需要修改数据库严格模式了

(5)总结

通过使用annotate()方法,您可以在Django查询结果集中添加聚合计算的注释,为每个对象添加新的聚合计算字段。这对于在每个对象级别执行聚合操作,并在结果中包含这些计算值非常有用。记住,在使用annotate()时,您可以根据需要选择合适的聚合函数,并为每个注释字段指定一个新的字段名以便后续引用。

八、F与Q查询

1、关键词引入

python 复制代码
from django.db.models import F, Q

2、F查询

  • 能够帮助你直接获取到列表中某个字段对应的数据

注意: 在操作字符串类型的数据的时候, F不能够直接做到字符串的拼接

(1)查出卖出数大于库存数的书籍

python 复制代码
# F 查询 : 帮助我们直接获取到表中的某个字段对应的数据
res = models.Book.objects.filter(sales__gt=F('stock'))
print(res)  # <QuerySet [<Book: 水浒传>]>

(2)将所有书籍的价格提升 50

python 复制代码
res = models.Book.objects.update(price=F('price') + 500)
print(res)  # 6 - 影响到了 6 条数据

(3)将所有书的名称后边加上 爆款 两个字

python 复制代码
# 在操作字符串的时候,F查询不能够直接做到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
# 上述的两个导入是对字符串的操作,如果不导入而直接+"爆款",名字就会变成空白

res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
print(res) # 6 - 影响到了 6 条数据

3、Q查询

  • filter()等方法中的关键字参数查询都是一起进行"and"
  • 如果你需要执行更复杂的查询(列如ORM语句),你可以使用Q对象。

(1)查询卖出数大于100或者价格小于500的书籍

python 复制代码
# (1.1)直接使用 filter 查询数据,逗号隔开,里面放的参数是 and 关系
res = models.Book.objects.filter(sales__gt=100, price__lt=500)
print(res)  # <QuerySet []>

# (1.2)直接使用 Q 查询数据,逗号隔开,里面放的参数还是 and 关系
res = models.Book.objects.filter(Q(sales__gt=100), Q(price__lt=500))
print(res)  # <QuerySet []>

# (1.3)直接使用 Q 查询数据,逗号可以换成其他连接符达到效果
res = models.Book.objects.filter(Q(sales__gt=100) or Q(price__lt=500))
# 二者等价 (| :或关系) ( ~ : 取反 not 关系)
res = models.Book.objects.filter(Q(sales__gt=100) | Q(price__lt=500))
print(res)  # <QuerySet [<Book: 三国演义爆款>, <Book: 水浒传爆款>, <Book: 论语爆款>, <Book: 孙子兵法爆款>]>

(2) Q的高阶用法 能够将查询条件的左边也变成 字符串形式

  • 能够将查询条件的左边也变成字符串的形式 而 不是变量形式
python 复制代码
# 先产生一个空对象  实列化
q = Q()
q.connector = 'or'  # and修改成or
# q对象里面有一个children
q.children.append(('maichu__gt', 100))
# 第一个元素就会被当作查询条件的左边 第二个元素会被当作查询条件右边
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)  # filter 除了可以放条件 还可以放对象
print(res)  # 默认还是and关系
  • 可以在去对象内 children里面 无限制的添加元素 添加元组,两个元素。
  • 而且还支持修改 orandnot
  • 默认是and
python 复制代码
# 或查询
q = Q()
q |= Q(条件一)
q |= Q(条件二)
qs = Model.objects.filter(q)

# 并查询
q = Q()
q &= Q(条件一)
q &= Q(条件二)
qs = Model.objects.filter(q)

4、总结

  • F() 表达式允许您在查询中引用字段值,执行数据库字段之间的比较或进行算术操作。
  • Q() 对象允许您构建复杂的查询逻辑,可以组合多个查询条件并使用逻辑运算符进行连接。

九、django中如何开启事务

1、知识回顾

事务的四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

(1)原子性(Atomicity)

  • 事务被视为一个不可分割的原子操作单元。

  • 这意味着要么全部操作成功并永久保存,要么全部操作失败并回滚到事务开始前的状态,不存在部分成功或部分失败的情况。

(2)一致性(Consistency)

  • 事务在执行前后,数据库都必须保持一致状态。

  • 这意味着事务执行前后,数据库中的数据必须满足所有定义的完整性约束,例如列级别的约束、外键关系等。

(3)隔离性(Isolation)

  • 事务之间应该相互隔离,每个事务的执行应该与其他事务的执行相互独立,互不干扰。

  • 隔离性确保了多个事务可以并发执行,而不会产生不一致的结果。

(4)持久性(Durability)

  • 一旦事务成功提交后,其所做的修改将永久保存在数据库中,即使发生系统故障或重启,数据也能够恢复到提交后的状态。
  • 持久性通过将事务日志写入非易失性存储介质来实现,如硬盘驱动器或固态硬盘。

2、事务名词

  • 开启事务:Start Transaction
  • 事务结束:End Transaction
  • 提交事务:Commit Transaction
  • 回滚事务:Rollback Transaction

3、默认事务行为

  • Django是支持事务操作的,它的默认事务行为是自动提交
  • 具体表现形式为:每次数据库操作(比如调用save()方法)会立即被提交到数据库中。
  • 但是如果你希望把连续的SQL操作包裹在一个事务里,就需要手动开启事务。

4、开启事务

(1)全局开启事务

① 配置
  • 在Web应用中,常用的事务处理方式是将每次请求都包裹在一个事务中。
  • 全局开启事务只需要将数据库的配置项ATOMIC_REQUESTS设置为True,如下所示:
python 复制代码
DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.mysql',
       'NAME': 'db1',
       'HOST': 'dbhost',
       'PORT': '3306',
       'USER': 'dbuser',
       'PASSWORD': 'password',
        #全局开启事务,绑定的是http请求响应整个过程
       'ATOMIC_REQUESTS': True, 
   }
② 原理
  • 每当有请求过来时,Django会在调用视图方法前开启一个事务。
  • 如果完成了请求处理并正确返回了结果,Django就会提交该事务。
  • 否则,Django会回滚该事务。
③ 局部取消事务
  • 如果你全局开启了事务,你仍然可以使用non_atomic_requests装饰器让某些视图方法不受事务控制
python 复制代码
from django.db import transaction

 
@transaction.non_atomic_requests
def my_view(request):
    do_stuff()
 
 
# 如有多个数据库,让使用otherdb的视图不受事务控制
@transaction.non_atomic_requests(using='otherdb')
def my_other_view(request):
    do_stuff_on_the_other_database()
  • 虽然全局开启事务很简单,但Django并不推荐开启全局事务。
  • 因为一旦将事务跟 HTTP 请求绑定到一起时,每一个请求都会开启事务,当访问量增长到一定的时候会造成很大的性能损耗。
  • 在实际开发过程中,很多GET请求根本不涉及到事务操作,一个更好的方式是局部开启事务按需使用。

(2)局部开启事务

  • Django项目中局部开启事务,可以借助于transaction.atomic方法。
  • 使用它我们就可以创建一个具备原子性的代码块,一旦代码块正常运行完毕,所有的修改会被提交到数据库。
  • 反之,如果有异常,更改会被回滚。
① 装饰器使用
  • atomic经常被当做装饰器来使用,如下所示:
python 复制代码
# 案例一:函数视图
 from django.db import transaction
 
 
 @transaction.atomic
 def viewfunc(request):
     # This code executes inside a transaction.
     do_stuff()
 
 
 # 案例二:基于类的视图
 from django.db import transaction
 from rest_framework.views import APIView
 
 
 class OrderAPIView(APIView):
       # 开启事务,当方法执行完以后,自动提交事务
       @transaction.atomic  
       def post(self, request):
           pass 
② 局部代码
  • 使用了atomic装饰器,整个视图方法里的代码块都会包裹着一个事务中运行。
  • 有时我们希望只对视图方法里一小段代码使用事务,这时可以使用transaction.atomic()显式地开启事务,如下所示:
python 复制代码
 from django.db import transaction
 
 
 def viewfunc(request):
     # 默认自动提交
     do_stuff()
       
     # 显式地开启事务
     with transaction.atomic():
         # 下面这段代码在事务中执行
         do_more_stuff()

5、Savepoint保存点

  • 在事务操作中,我们还会经常显式地设置保存点(savepoint)。
  • 一旦发生异常或错误,我们使用savepoint_rollback方法让程序回滚到指定的保存点。
  • 如果没有问题,就使用savepoint_commit方法提交事务。
python 复制代码
def viewfunc(request):
   # 默认自动提交
   do_stuff()

   # 显式地开启事务
   with transaction.atomic():
       # 创建事务保存点
       sid = transaction.savepoint()

       try:
           do_more_stuff()
       except Exception as e:
           # 如发生异常,回滚到指定地方。
           transaction.savepoint_rollback(sid)          
       # 如果没有异常,显式地提交一次事务
       transaction.savepoint_commit(sid)


   return HttpResponse("Success")
  • 注意:虽然SQLite支持保存点,但是sqlite3 模块设计中的缺陷使它们很难使用。

6、事务提交后回调函数

  • 有的时候我们希望当前事务提交后立即执行额外的任务
  • 比如客户下订单后立即邮件通知卖家,这时可以使用Django提供的on_commit方法
python 复制代码
# 例1
from django.db import transaction

def do_something():
  pass  # send a mail, invalidate a cache, fire off a Celery task, etc.


transaction.on_commit(do_something)
python 复制代码
# 例2:调用celery异步任务
transaction.on_commit(lambda: some_celery_task.delay('arg1'))

十、ORM常用字段及参数

1、常用字段类型

(1)CharField

  • max_length :指定字段的最大长度,用于存储较短的字符串。例如,models.CharField(max_length=100)
  • verbose_name: 字段的注释

(2)TextField

  • 用于存储较长的文本数据,没有固定长度限制。
    • 例如,models.TextField()

(3)IntegerField

  • 用于存储整数值。可以指定参数如defaultnullblank等。
    • 例如,models.IntegerField(default=0)

(4)FloatField

  • 用于存储浮点数。可以指定参数如defaultnullblank等。
    • 例如,models.FloatField(default=0.0)

(5)DecimalField

  • max_digits:指定存储在数据库中的数字的最大位数(包括小数点前后的位数)。

    • 例如,如果 max_digits=6,则表示最多可以存储 6 位数字。
  • decimal_places:指定存储在数据库中的小数部分的位数。

    • 例如,如果 decimal_places=2,则表示小数点后最多可以有 2 位数字。

(6)BooleanField

  • 用于存储布尔值(True或False)。
    • 例如,models.BooleanField(default=False)

(7)DateTimeField

  • 用于存储日期和时间。可以指定参数如auto_nowauto_now_add等。例如,models.DateTimeField(auto_now=True)

(8)DateField

  • 用于存储日期。
    • 例如,models.DateField()

(9)TimeField

  • 用于存储时间。
    • 例如,models.TimeField()

(10)EmailField

  • 用于存储电子邮件地址。
    • 例如,models.EmailField()

(11)ImageField

  • 用于存储图片文件。
    • 例如,models.ImageField(upload_to='images/')

(12)FileField

  • 用于存储文件。
    • 例如,models.FileField(upload_to='files/')

(13)ForeignKey

  • 用于定义一对多关系。指向另一个模型的主键。
    • 例如,models.ForeignKey(OtherModel, on_delete=models.CASCADE)

(14)ManyToManyField

  • 用于定义多对多关系。
    • 例如,models.ManyToManyField(OtherModel)

(15)OneToOneField

  • 用于定义一对一关系。
    • 例如,models.OneToOneField(OtherModel, on_delete=models.CASCADE)

(16)UUIDField

  • 用于存储UUID(通用唯一标识符)。
    • 例如,models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

2、常用字段参数

(1)default

  • 指定字段的默认值。
    • 例如,default=0

(2)null

  • 设置为True允许字段为空。
    • 例如,null=True

(3)blank

  • 设置为True允许字段为空白。
    • 例如,blank=True

(4)choices

  • 提供选项列表,用于下拉框等选择字段。
    • 例如,choices=[('M', 'Male'), ('F', 'Female')]

(5)unique

  • 设置为True确保字段值在整个表中唯一。
    • 例如,unique=True

(6)verbose_name

  • 设置字段的可读性更好的名称。
    • 例如,verbose_name='Full Name'

(7)related_name

  • 指定反向关系的名称。
    • 例如,related_name='books'

(8)on_delete

  • 在外键关系中定义级联删除行为。
    • 例如,on_delete=models.CASCADE

(9)db_index

  • 设置为True在数据库中创建索引。
    • 例如,db_index=True

(10)**auto_now **和 auto_now_add

  • 分别用于自动设置字段的值为当前时间。
    • 例如,auto_now=True

(11)validators

  • 提供验证器函数列表,用于验证字段值。
    • 例如,validators=[validate_email]

(12)to

  • 设置要关联的表,用于外键字段
    • 例如,to='Author'

(13)to_field

  • 设置要关联的表的字段,用于外键字段,默认不写关联的就是另外一张表的主键字段。
    • 例如,to_field='id'

3、字段与sql语句之间的关系

python 复制代码
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

4、自定义字段

django除了给你提供了很多字段类型之外,还支持你自定义字段

python 复制代码
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        # 调用父类的init方法
        super().__init__(max_length=max_length, *args, **kwargs)  # max_length一定要是关键字的形式传入

    def db_type(self,connection):
        """
        返回真正的数据类型及各种约束条件
        :param connection:
        :return:
        """
        return f'char({self.max_length})'
    
# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)

十一、数据库查询优化

1、ORM语句的特点

惰性查询:如果你仅仅只是书写了ORM语句,在后面根本没有用到该语句所查询出来的参数,那么ORM会自动识别,直接不执行。

2、only与defer

(1)惰性查询的体现

  • 运行这句话的时候没有SQL语句执行
python 复制代码
models.Book.objects.all()
  • 这样运行的时候就会返回相应的数据
python 复制代码
res = models.Book.objects.all()
print(res)  # 只有需要用到真正的数据时,才会走数据库查询数据

(2)示例

  • 想要获取书籍表中所有数的名字
python 复制代码
res = models.Book.objects.values('title')
for d in res:
    print(d.get('title'))

(3)优化

根据上述代码进行优化:实现获取到的是一个数据对象,然后点 title 就能够拿到书名,并且没有其他字段

① only
python 复制代码
res = models.Book.objects.only('title')
res = models.Book.objects.all()  # all不需要走数据库
print(res)  # <QuerySet [<Book: 水浒传爆款爆款>, <Book: 西游记爆款爆款>, <Book: 钢铁是怎样炼成的爆款爆款>, <Book: 白夜行爆款爆款>, <Book: 解忧杂货店爆款爆款>]>
for i in res:
    print(i.title)  # 点击only括号内的字段,不会走数据库
    print(i.price)  # 点击only括号内没有的字段,会重新走数据库查询,而all不需要走数据库
② defer
  • defer 和 only 相反
  • defer 括号内放的字段不在查询的对象里面 ,查询该字段需要重新走数据库
  • 而如果查询的是非括号内的字段 则不需要走数据库
python 复制代码
res = models.Book.objects.defer('title')  # 对象除了没有title属性之外其他都有
print(res)
for s in res:
  print(res.price)

(4)小结

  • only:

    • only方法用于指定只返回指定字段的查询结果。
    • 通过only方法,您可以限制查询结果中包含的字段,这有助于减少数据传输量和提高查询性能。
    • 例如,Model.objects.only('field1', 'field2')将只返回field1field2字段的查询结果。
  • defer:

    • defer方法用于延迟加载指定字段,这意味着这些字段将在被访问时才从数据库中加载。
    • 通过defer方法,您可以推迟加载某些字段,这在处理大型数据集时可以提高性能。
    • 例如,Model.objects.defer('field1', 'field2')将推迟加载field1field2字段,直到访问这些字段时才会从数据库中加载。

区别总结:

  • only用于选择要返回的字段,只返回指定字段的查询结果。
  • defer用于推迟加载字段,这些字段在被访问时才会从数据库中加载。
  • 与跨表操作有关
python 复制代码
res = models.Book.objects.all()
for i in res:
    print(i.publish.name)  # 每循环一次就要走一次数据库查询
  • 内部直接将book表和publish表的数据连起来,一次性将所有数据封装到查询出来的对象,这个时候对象可以直接点去取两个变中的数据而不需走数据库。
  • select_related括号内只能放外键字段(一对一 一对多)
  • 但是多对多的外键字段不行
python 复制代码
res = models.Book.objects.select_related('publish')  # INNER JOIN:联表操作
print(res)
for i in res:
    print(i.publish.name)
  • prefetch_related该方法内部其实就是子查询,就是将子查询查询出来的所有结果也给你封装到对象中,给你的感觉就好像也是一次性搞定的
  • 较联表操作多了一步
python 复制代码
res = models.Book.objects.prefetch_related('publish')  # 子查询
for i in res:
    print(i.publish.name)  # 每循环一次就要走一次数据库查询

(3)小结

在Django中,select_relatedprefetch_related都是用于优化数据库查询的方法,特别是在处理关联表时非常有用。这里我将解释它们之间的区别:

  • select_related:

    • select_related用于在查询时一次性获取关联对象的数据,而不是每次访问关联对象时都要执行额外的查询。
    • 当您知道查询将涉及到某些关联字段,并且希望在单个查询中获取这些关联对象的数据时,select_related是很有用的。
    • select_related执行的是SQL的JOIN操作,将关联对象的数据一起取出,因此可以减少数据库查询次数,提高性能。
    • 例如,Model.objects.select_related('related_model')将在查询Model对象时一起获取related_model的数据。
  • prefetch_related:

    • prefetch_related用于在查询时获取相关对象的数据,但它是通过执行额外的查询来实现的,而不是通过JOIN操作。
    • prefetch_related适用于多对多关系或反向外键关系,并且当您需要在查询结果中访问相关对象的数据时,这是一个不错的选择。
    • prefetch_related执行额外的查询来获取相关对象的数据,然后将这些数据缓存起来,以便在访问时使用。
    • 例如,Model.objects.prefetch_related('related_model')将在查询Model对象时获取related_model的数据,但是通过额外的查询来实现。

区别总结:

  • select_related使用JOIN操作一次性获取关联对象的数据,减少数据库查询次数,适用于正向关系。
  • prefetch_related通过额外的查询获取关联对象的数据并缓存,适用于多对多关系或反向外键关系。

通过合理使用select_relatedprefetch_related,您可以减少数据库查询次数,减少查询时间,并提高查询性能。希望这能帮助您理解它们之间的区别和用法。如果您有任何进一步的问题,请随时提出。

需要注意的是,使用select_relatedprefetch_related方法时,需谨慎选择要加载的关联对象

避免过度加载导致的性能问题,同时也要注意数据库索引的优化以提高查询效率。

十二、choices参数(数据字段设计常见)(重要)

1、引入

这个世界上不是所有的东西都是非黑即白,针对某个可以列举完全的可能性字段,我们应该如何存储?

只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数

2、数据准备

python 复制代码
from django.db import models

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    # 性别
    gender_choices = (
        (1, '男'),
        (2, '女'),
        (3, '其他')
    )
    gender = models.IntegerField(choices=gender_choices)

    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不及格')
    )
    # 保证字段类型跟列举出来的元组第一个数据类型一致即可
    score = models.CharField(choices=score_choices,null=True)
    """
    该gender字段存的还是数字,但是如果存的数字在上面元组列举的范围之内,那么可以非常轻松的获取到数字对应的真正的内容。
    """

那么问题又来了

  1. gender字段存的数字不在上述元组列举的范围内容,该怎么办?
  2. 如果在,如何获取对应的中文信息?

3、问题解决

(1)gender字段存的数字不在上述元祖列举的范围内

python 复制代码
import os

from django.test import TestCase

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django06.settings')
    import django
    django.setup()

    from app01 import models
    # 存
    models.User.objects.create(username='xiao',age=18,gender=1)
    models.User.objects.create(username='xu',age=34,gender=2)
    models.User.objects.create(username='zhang',age=45,gender=3)
    
    # 存的时候,没有列举出来的数字也能存(范围还是按照字段类型决定)
    models.User.objects.create(username='quan',age=54,gender=4)

没有报错,且第四条已经插入到数据库中

(2)获取在上述元祖列举的范围内gender字段存的数字

  • 只要是choices参数的字段,如果你想要获取对应信息,固定的写法为 get_字段名_display()
python 复制代码
# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)  # 1
"""只要是choices参数的字段,如果你想要获取对应信息,固定的写法为 get_字段名_display()"""
print(user_obj.get_gender_display())  # 男
user_obj = models.User.objects.filter(pk=4).first()


"""如果没有对应字段,不会报错,字段存入是什么展示的就是什么"""
print(user_obj.get_gender_display())  # 4

4、总结

  • choice参数使用场景非常广泛

  • 例如

    • 支付方式的选择
    • 生源的来源地
    • 分数的分类
    • 学历的分类
    • ...

十三、多对多三种创建方式

1、全自动

  • 利用ORM自动帮我们创建第三张表关系
python 复制代码
class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自动
    authors = models.ManyToManyField(to='Author')


class Author(models.Model):
    name = models.CharField(max_length=32)
  • 优点
    • 代码不需要自己写,非常方便,还支持ORM提供操作第三张表的方法
  • 缺点
    • 第三张关系表的扩展性极差(没办法添加额外字段)

2、纯手动(不建议使用)

python 复制代码
class Book(models.Model):
    name = models.CharField(max_length=32)

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

class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 优点
    • 第三张表完全取决于自己进行额外的拓展
  • 缺点
    • 需要写代码较多
    • 不能使用ORM提供的相关方法

3、半自动(实际项目用这个)

python 复制代码
class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自动
    # through_fields : 当前表是谁,第一个参数就是谁
    # 判断的本质:通过第三张表查询对应的表,需要用到哪个字段就把哪个字段放在前面
    authors = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))


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


class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 这样虽然可以使用ORM的正反向查询,但是没法使用add,set,remove,clear这四个方法

十四、补充MTV与MVC模型

  • MTV模型和MVC模型是两种常见的软件设计模式,用于组织和管理用户界面和应用程序的逻辑。
  • 虽然它们存在一些相似之处,但它们在设计和应用上有一些不同。

1、MTV模型

MTV模型是指Model-Template-View(模型-模板-视图)模型,是Django框架中采用的一种设计模式。它的核心思想是将应用程序分为三个主要部分:

  • 模型(Model):

    • 模型表示应用程序中处理数据的结构和行为。
    • 它通常与数据库交互,并定义了数据的存储和操作方式。
  • 模板(Template):

    • 模板负责处理用户界面的显示。
    • 它定义了应用程序的外观和布局,并将动态数据与静态页面结合在一起,生成最终的用户界面。
  • 视图(View):

    • 视图处理应用程序的逻辑和业务流程。
    • 它接收用户的请求,从模型中获取数据,将数据传递给模板进行渲染,并生成响应返回给用户。

MTV模型的优点在于它可以很好地将应用程序的逻辑和用户界面进行分离,使代码更容易维护和扩展。

2、MVC模型

MVC模型是指Model-View-Controller(模型-视图-控制器)模型,是一种常见的软件设计模式,广泛应用于Web开发和其他应用程序中。

  • 模型(Model):

    • 模型负责处理应用程序的数据逻辑。
    • 它包含了数据的存储和操作方式,并定义了数据在应用程序内部如何交互和被操作。
  • 视图(View):

    • 视图是用户界面的表示,负责展示数据给用户并接收用户的输入操作。
    • 它通常从模型中获取数据,并将其显示给用户。
  • 控制器(Controller):

    • 控制器处理用户的交互和请求,并根据用户的行为作出相应的响应。
    • 它接收用户的输入,并更新模型和视图以反映用户的操作。

MVC模型的优点在于它可以很好地分离应用程序的不同组件,使得代码更易于维护、测试和重用。

3、总结

  • MTV模型主要用于Django框架中,通过将应用程序分为模型、模板和视图,提供了一种清晰的架构方案。
  • MVC模型则是一个通用的设计模式,广泛应用于各种类型的应用程序中。
  • 无论使用哪种模型,都能帮助开发者更好地组织和管理代码,并实现可扩展和可维护的应用程序。
相关推荐
尘浮生6 分钟前
Java项目实战II基于SpringBoot的共享单车管理系统开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
杨江2 小时前
ThingsBoard安装测试
服务器·数据库
mit6.8242 小时前
[Redis#4] string | 常用命令 | + mysql use:cache | session
数据库·redis·后端·缓存
Beekeeper&&P...3 小时前
map和redis关系
数据库·redis·缓存
jianqimingtian3 小时前
如何使用 Matlab 制作 GrabCAD 体素打印切片
数据结构·数据库
真真假假々3 小时前
MySQL和ADSDB
数据库·mysql
秦老师Q3 小时前
MySQL第二章 sql约束与sql数据类型
数据库·sql·mysql
不是二师兄的八戒3 小时前
mysql in查询大数据量业务无法避免情境下优化
数据库·mysql
----云烟----4 小时前
Qt获取文件夹下的文件个数(过滤和不过滤的区别)
数据库·qt
Dotrust东信创智4 小时前
浅谈丨功能安全测试,汽车的守护者
运维·服务器·数据库