Django学习(2):Django的ORM操作

一,ORM简介

1.1 什么是ORM

        Django 中的 ORM:Object Relational Mapping(对象关系映射)是一种数据库访问技术,它允许开发者使用 Python 对象来操作数据库,而不必直接编写 SQL 查询。ORM 将数据库表映射到 Python 类,将表的行映射到类的实例,将表的列映射到类的属性。这使得数据库操作更容易、更直观,同时提供了数据库独立性,因为可以在不更改代码的情况下切换不同的后端数据库。

1.2 ORM主要特点和优点

  1. 简化数据库操作: 使用 ORM,可以使用 Python 代码来执行数据库操作,而不必编写复杂的 SQL 查询。这使得数据库操作更加直观和容易。

  2. 数据库独立性: 可以在不同的数据库系统之间切换,而不必更改应用程序代码。Django 的 ORM 支持多个后端数据库,包括 PostgreSQL、MySQL、SQLite、Oracle 等。

  3. 模型定义: 在 Django 中,使用模型类来定义数据模型,这些模型类是 Python 类,它们继承自 models.Model。模型类的属性对应于数据库表的列。

  4. 自动生成表格: 一旦定义了模型类,Django 的 ORM 将自动生成相应的数据库表格,包括表的结构和字段类型。

  5. 查询 API: Django 提供了强大的查询 API,允许执行复杂的查询操作,如过滤、排序、聚合和连接等。

  6. 事务支持: 可以使用 Django 的 ORM 来处理事务,确保数据库操作的原子性和一致性。

  7. 数据库迁移: Django 的 ORM 提供了数据库迁移工具,用于管理数据模型的变化。这使得数据库结构的演化变得简单和可维护。

  8. 安全性: 使用 ORM 可以减少 SQL 注入攻击的风险,因为 ORM 会自动参数化 SQL 查询。

  9. 可测试性: 由于数据库操作是使用 Python 对象进行的,因此可以轻松编写单元测试来测试数据访问代码。

        总之,Django 的 ORM 是一个强大且易于使用的工具,它简化了数据库操作,提供了数据库独立性,并允许以更直观的方式处理数据。这使得开发和维护 Django 应用程序的数据库部分变得更加高效和可维护。

二,ORM具体操作

        ORM主要是用于在视图函数与模板中对数据库中的数据进行获取,这里主要先从终端进行演示ORM具体操作。

2.1 终端ORM操作

2.1.1 进入django终端

        从pycharm命令行进入项目后输入以下命令进入django终端:python manage.py shell

 2.1.2 导入模型类 

        进入终端后,从我们自己的应用导入我们定义好的并且已经迁移过的模型类:

from news(应用名).models(模型类的文件) import Category,News(类名,也可以直接用*)

2.1.3 ORM测试

        以下命令会在后续进行详细解释:        

导入之后使用以下命令:类名.objects.create(属性=值)(这一步用于插入数据)

然后在查入数据后输入:类名.objects.all()(查看是否插入成功)

        出现以下说明插入与查询成功,这时再次进入数据库就会发现Category表中已经多了一条数据 

2.2 ORM常见操作命令

2.2.1 查询数据

  1. 1,filter()

语法:类名.objects.filter(*args, **kwargs)

返回一个新的 QuerySet 查询集,其中包含与给定查找参数相匹配的对象。用于筛选满足特定条件的记录。

查询参数(**kwargs)的格式应在下文中会有描述。多个参数通过底层 SQL 语句中的 AND 连接。如果需要执行更复杂的查询(例如,带有 OR 语句的查询),你可以使用 Q 对象 (*args)。

2,exclude()

语法:类名.objects.exclude(*args, **kwargs)

返回一个新的 QuerySet,其中包含与给定查找参数不匹配的对象。用于排除满足特定条件的记录。

查询参数(**kwargs)的格式应在下文中会有描述。多个参数通过底层 SQL 语句中的 AND 连接,整个过程用 NOT() 括起来。

排除了所有 pub_date 晚于 2005-1-3 且 headline 为“Hello”的条目:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline="Hello")

3get()

语法:类名.objects.get (*args, **kwargs)返回与给定的查找参数相匹配的对象,其格式应在下文中会有描述。用于获取单个符合条件的记录。应该使用保证唯一的查询,比如主键或唯一约束中的字段。

Entry.objects.get(id=1)

Entry.objects.get(Q(blog=blog) & Q(entry_number=1))

如果希望一个查询集已经返回一条记录,可以在没有任何参数的情况下使用 get() 来返回该行的对象:

​
Entry.objects.filter(pk=1).get()

如果 get() 没有找到任何对象,它会引发一个 Model.DoesNotExist 异常:

Entry.objects.get(id=-999)  # raises Entry.DoesNotExist

如果 get() 发现多个对象,会引发一个 Model.MultipleObjectsReturned 异常:

Entry.objects.get(name="A Duplicated Name")  # raises Entry.MultipleObjectsReturned

​

4,all()

语法:类名.objects.all()

返回当前 QuerySet(或 QuerySet 子类)的副本。用于获取模型的所有记录。

这在以下情况下很有用:可能想传入一个模型管理器或一个 QuerySet,并对结果做进一步过滤。在任何一个对象上调用 all() 后,肯定会有一个 QuerySet 可以使用。

当一个 QuerySet 被执行时,它通常会缓存其结果。如果数据库中的数据可能在 QuerySet 被评估后发生了变化,可以通过调用 all() 对以前执行过的 QuerySet 进行更新。

>>> Category.objects.all()  

<QuerySet [<Category: Category object (1)>, <Category: Category object (2)>, <Category: Category object (3)>, <Category: Category object (4)>, <Category: Category object (5)>]>

>>> News.objects.all()    

<QuerySet [<News: 优化营商网络环境措施>, <News: 体育河南 健康中原>, <News: 山坪篮球队走向全国>]>

5,first()/last():

语法:类名.objects.first()/last()

用于获取第一条或最后一条记录。

返回查询集匹配的第一个对象或最后一个对象,如果没有匹配的对象,则返回 None。如果 QuerySet 没有定义排序,那么查询集自动按主键排序。这可能会影响聚合结果。

p = Article.objects.order_by("title", "pub_date").first()/last()

请注意,first()和last() 是方便的方法,下面的代码示例相当于上面的例子:

try:

    p = Article.objects.order_by("title", "pub_date")[0]/[-1]

except IndexError:

p = None

>>> Category.objects.first()

<Category: Category object (1)>

>>> News.objects.first()    

<News: 优化营商网络环境措施>

6,order_by():

语法:类名.objects.order_by(*fields)

用于对查询结果进行排序,可以按一个或多个字段进行升序或降序排序。返回一个新的查询集(QuerySet),其中的记录按照指定的字段进行排序。它不会改变原始的查询集。默认情况下,QuerySet 返回的结果是按照模型 元类(Meta)中的 ordering 选项给出的排序元组排序的。可以通过使用 order_by 方法在每个 QuerySet 的基础上覆盖这一点。

实例:

Entry.objects.filter(pub_date__year=2005).order_by("-pub_date", "headline")

上述结果将按 pub_date 降序排列,然后按 headline 升序排列。"-pub_date" 前面的负号表示降序。升序是隐含的。要随机排序,使用 "?",如:

Entry.objects.order_by("?")

注意:order_by('?') 查询可能会很贵,而且速度很慢,这取决使用的数据库后端。

要按不同模型中的字段排序,使用与跨模型关系查询时相同的语法。也就是说,字段的名称,后面是双下划线(__),再后面是新模型中的字段名称,以此类推,想加入多少模型就加入多少。例如:

Entry.objects.order_by("blog__name", "headline")

如果你试图通过与另一个模型有关系的字段进行排序,Django 将使用相关模型上的默认排序,如果没有指定 Meta.ordering,则通过相关模型的主键进行排序。例如,由于 Blog 模型没有指定默认排序:

Entry.objects.order_by("blog")等同于:Entry.objects.order_by("blog__id")

如果 Blog 有 ordering = ['name'],那么第一个查询集将与以下内容相同:

Entry.objects.order_by("blog__name")

你也可以通过在表达式上调用 asc() 或 esc(),按c排序:

Entry.objects.order_by(Coalesce("summary", "headline").desc())

7,reverse():

语法:类名.objects.reverse()

用于反转查询结果的顺序。它通常用于在已经排序的结果上进行反向排序。返回一个新的查询集,其中的记录与原始查询集相同,但是它们的顺序被反转。

反转查询结果的顺序:reversed_records = records.reverse()

要检索一个查询集中的“最后”五个项目,也可以这样做:

my_queryset.reverse()[:5]

8,values():

语法:类名.objects.distinct(*fields)

用于将查询结果转化为包含字段值的字典,每个字典表示一条记录。这对于选择部分字段并将其用于序列化或JSON输出很有用。

返回一个 QuerySet,其中的记录被转换为包含字段值的字典。每个字典表示一条记录。

当用作可迭代对象时,返回字典,而不是模型实例。其中每一个字典都代表一个对象,键与模型对象的属性名相对应。

>>> Category.objects.values()

<QuerySet [{'id': 1, 'name': '体育'}, {'id': 2, 'name': '时政'}, {'id': 3, 'name': '八卦'}, {'id': 4, 'name': '游戏'}, {'id': 5, 'name': '郑州'}]>

# This list contains a Blog object.

>>> Blog.objects.filter(name__startswith="Beatles")

<QuerySet [<Blog: Beatles Blog>]>

# This list contains a dictionary.

>>> Blog.objects.filter(name__startswith="Beatles").values()

<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

values() 方法接受可选的位置参数 *fields,它指定了 SELECT 应该被限制的字段名。如果你指定了字段,每个字典将只包含你指定字段的字段键/值。如果不指定字段,每个字典将包含数据库表中每个字段的键和值。

>>> Blog.objects.values()

<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

>>> Blog.objects.values("id", "name")

<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

10,values_list():

语法:类名.objects.values_list(*fields, flat=False)

用于将查询结果转化为包含字段值的元组,每个元组表示一条记录。返回一个 QuerySet,其中的记录被转换为包含字段值的元组。每个元组表示一条记录。传递一个或多个字段名称作为位置参数,用于指定要包含在返回的元组中的字段。

flat(可选):默认为 False,如果设置为 True,将返回一个扁平的列表而不是元组。

# 获取包含字段值的元组列表

>>> Category.objects.values_list()

<QuerySet [(1, '体育'), (2, '时政'), (3, '八卦'), (4, '游戏'), (5, '郑州')]>

>>> Category.objects.values_list("name")

<QuerySet [('体育',), ('时政',), ('八卦',), ('游戏',), ('郑州',)]>

11,distinct():、

语法:类名.objects.distinct(*fields)

用于获取查询结果中唯一的记录。在其 SQL 查询中使用 SELECT DISTINCT它通常与 values()values_list() 方法一起使用,以去除重复的记录。返回一个新的查询集,其中的记录是原始查询集中去除重复记录后的结果。通常与 values()values_list() 方法一起使用,以确保返回唯一的记录。

>>> Category.objects.distinct()        

<QuerySet [<Category: Category object (1)>, <Category: Category object (2)>, <Category: Category object (3)>, <Category: Category object (4)>, <Category: Category object (5)>]>

>>> News.objects.distinct()             

<QuerySet [<News: 优化营商网络环境措施>, <News: 体育河南 健康中原>, <News: 山坪篮球队走向全国>]>

# 获取唯一的记录,去除重复

unique_names = MyModel.objects.values('name').distinct()

>>> Author.objects.distinct()

[...]

>>> Entry.objects.order_by("pub_date").distinct("pub_date")

[...]

>>> Entry.objects.order_by("blog").distinct("blog")

[...]

>>> Entry.objects.order_by("author", "pub_date").distinct("author", "pub_date")

[...]

>>> Entry.objects.order_by("blog__name", "mod_date").distinct("blog__name", "mod_date")

[...]

>>> Entry.objects.order_by("author", "pub_date").distinct("author")

[...]

12,exists():

语法:类名.objects.exists()

用于检查查询结果中是否存在至少一条记录。它通常用于条件检查,以确定是否有满足特定条件的记录。返回一个布尔值,指示查询结果是否存在记录。

>>> Category.objects.all().exists()     

True

>>> Category.objects.filter(id=20).exists() 

False

13,count():

语法:类名.objects.count()

用于统计满足特定条件的记录数量。它返回一个整数,表示查询结果中满足条件的记录数量。count() 方法通常比 len() 方法更高效,因为它在数据库层面执行统计,而不是在Python中加载所有记录并计算数量。

>>> Category.objects.filter(id=20).count()

0

>>> Category.objects.all().count()        

5

14,实例查询:

可以先使用查询语句将一条数据对象赋予一个类的实例,然后使用实例属性获取各数据

>>> c1 = Category.objects.get(id=1)

>>> c1.name

'体育'

>>> c1.id

1

>>> c1

<Category: Category object (1)>

2.2.2 插入数据

 1,create()

语法:类名.objects.create(**kwargs)

需要注意这个方法,需要一次性填完所有数据,没有默认值的数据都需要进行填写。返回一个对象。create() 方法只创建并插入一条记录,如果你需要插入多个相关记录,你需要多次调用 create() 方法或使用其他方法比如save()bulk_create()

  • create() 方法实际上是一个两步操作的组合。它首先创建一个新的模型实例,然后调用该实例的save()方法将记录保存到数据库。
  • create() 方法返回新创建的记录对象。
MyModel.objects.create(field1=value1, field2=value2)

>>> Category.objects.create(name="河南")

<Category: Category object (6)>

 2,bulk_create()

语法:类名.objects.bulk_create(objects, batch_size=None)

用于批量插入多条记录的高效方法。objects 是一个包含要插入的模型实例的列表(或其他可迭代对象)。batch_size(可选)是一个整数,用于指定每批次插入的记录数。如果不指定,Django将尝试选择一个合适的默认值。

  • bulk_create() 方法通过生成一个单一的SQL插入查询来批量插入多条记录,而不是为每个记录生成一个独立的SQL查询,从而提高了性能。
  • bulk_create() 方法返回插入的记录数量。
persons_to_insert = [

    Person(name="John", age=30),

    Person(name="Alice", age=25),

    Person(name="Bob", age=35),

]

inserted_records = Person.objects.bulk_create(persons_to_insert)

>>> Category.objects.bulk_create([Category(name="电影"),Category(name="音乐")])

[<Category: Category object (8)>, <Category: Category object (9)>]

 3,get_or_create()

语法:类名.objects.get_or_create(defaults=None, **kwargs)

用于在数据库中查询指定条件的记录,并在找到时返回该记录,或者在找不到时创建新的记录。通常用于确保数据库中存在唯一的记录,并且可以在需要时创建该记录。defaults(可选)是一个字典,用于指定在创建新记录时的默认字段值。**kwargs 是查询条件,用于指定要查找或创建的记录的条件。

  • get_or_create() 方法首先尝试使用指定的查询条件查询数据库中的记录。
  • 如果找到匹配的记录,它将返回该记录和一个布尔值createdcreated表示是否创建了新的记录,通常为False
  • 如果找不到匹配的记录,它将使用查询条件和指定的默认字段值创建一个新的记录,并返回该记录和一个布尔值createdcreated通常为True
>>> Category.objects.get_or_create(name="河南")

(<Category: Category object (6)>, False)

>>> Category.objects.get_or_create(name="全球")

(<Category: Category object (10)>, True)

4,实例插入

可以先创建一个类的实例,然后把实例的所有属性都赋值,最后实例.save()保存

>>> c2 = Category()                                                                        

>>> c2.name = "经济"

>>> Category.object.all()

>>> Category.objects.all()

<QuerySet [<Category: Category object (1)>, <Category: Category object (2)>, <Category: Category object (3)>, <Category: Category object (4)>, <Category: Category object (5)>, <Category: Category object (6)>]>

>>> c2.save()             

>>> Category.objects.all()

<QuerySet [<Category: Category object (1)>, <Category: Category object (2)>, <Category: Category object (3)>, <Category: Category object (4)>, <Category: Category object (5)>, <Category: Category object (6)>, <Category: Category object (7)>]>

需要注意在实例.save()后才会加入到数据库

2.2.3 修改数据

 1,update():

语法:类名.objects.update(**kwargs)

用于更新数据库中符合特定条件的记录的字段值。它不返回任何对象,只返回更新的记录数。

update() 方法是直接在数据库中执行更新操作,不会加载或返回模型对象。由于 update() 是在数据库级别执行的,所以它通常比逐个更新模型实例更高效。

# 将名字为 "John" 的人的年龄更新为 35

MyModel.objects.filter(name="John").update(age=35)

>>> Category.objects.filter(id=9).update(name="视野")

1

 2,update_or_create():

语法:类名.objects.update_or_create(条件, defaults={'字段1': 新值1, '字段2': 新值2, ...})

用于查找符合特定条件的记录,如果找到则更新记录的字段值,如果找不到则创建新记录。

用给定的 kwargs 更新对象的一种方便方法,是必要时创建一个新对象。defaults 是用来更新对象的 (field, value) 对的字典。defaults 中的值可以是可调用对象。返回 (object, created) 的元组,其中 object 是创建或更新的对象,created 是一个布尔值,指定是否创建了一个新对象。update_or_create 方法根据给定的 kwargs 尝试从数据库中获取一个对象。如果找到了匹配的对象,它就会更新 defaults 字典中传递的字段。

  • update_or_create() 方法返回一个包含更新后的记录(或新创建的记录)和一个布尔值 created,表示是否创建了新的记录。
  • 你可以使用 defaults 参数指定要更新的字段和新值,以及要用于创建新记录的默认值。
# 查找名字为 "John" 的人,如果找到则更新年龄为 35,如果找不到则创建新记录

person, created = Person.objects.update_or_create(name="John", defaults={'age': 35})

>>> Category.objects.update_or_create(name="河南",defaults={"name":"台球"})

(<Category: Category object (6)>, False)

 3,bulk_update():

语法:MyModel.objects.bulk_update(objects, fields)

用于批量更新一组模型实例的字段值。它通常用于一次性更新多个模型实例的相同字段。

  • bulk_update() 方法允许批量更新多个模型实例的字段,以减少数据库的往返次数,提高性能。
  • 需要指定要更新的字段列表,而不是一个字典。
# 批量更新一组人的年龄字段

persons_to_update = Person.objects.filter(name__in=["John", "Alice"])

for person in persons_to_update:

    person.age = 40

Person.objects.bulk_update(persons_to_update, ["age"])

 4,实例更新

可以先把数据中的某一行赋予一个实例,然后进行更新注意也需要使用实例.save进行保存

>>> n1 = News.objects.get(id=1)

>>> n1

<News: 优化营商网络环境措施>

>>> n1.title="优化措施"

>>> News.objects.all()

<QuerySet [<News: 优化营商网络环境措施>, <News: 体育河南 健康中原>, <News: 山坪篮球队走向全国>]>

>>> n1.save()

>>> News.objects.all()

<QuerySet [<News: 优化措施>, <News: 体育河南 健康中原>, <News: 山坪篮球队走向全国>]>

2.2.4 删除数据

 1,delete():

用于删除符合特定条件的记录,或者删除指定模型实例对应的记录。对 QuerySet 中的所有行执行 SQL 删除查询,并返回删除的对象数量和每个对象类型的删除数量的字典。

delete() 是即时应用的。不能对已经被取走一个片断或不能再被过滤的 QuerySet 调用 delete()

删除满足条件的记录

可以使用 filter() 方法来筛选满足特定条件的记录,然后调用 delete() 方法来删除这些记录。

# 删除年龄小于 18 的记录

MyModel.objects.filter(age__lt=18).delete()

删除指定模型实例的记录

也可以直接删除指定模型实例对应的记录,使用 delete() 方法即可。这将删除 person 对象对应的数据库记录。

# 创建一个模型实例

person = Person.objects.create(name="John", age=30)

# 删除该模型实例对应的记录

person.delete()

2.3 ORM查找字段Field

        字段查找参数:这些参数用于指定查询条件的字段和条件类型。这些字段查找参数可以与 filter()exclude()get() 等查询方法一起使用,以帮助在数据库中检索满足特定条件的记录。根据具体的查询需求,可以选择合适的字段查找参数来构建查询。常见的字段查找参数包括:

  • exact:精确匹配,例如 field_name__exact=value
Entry.objects.get(id__exact=14)

Entry.objects.get(id__exact=None)

SQL 等价于:

SELECT ... WHERE id = 14;

SELECT ... WHERE id IS NULL;
  • iexact:不区分大小写的精确匹配,例如 field_name__iexact=value
Blog.objects.get(name__iexact="beatles blog")

Blog.objects.get(name__iexact=None)

SQL 等价于:

SELECT ... WHERE name ILIKE 'beatles blog';

SELECT ... WHERE name IS NULL;
  • contains:包含某个值,例如 field_name__contains=value
Entry.objects.get(headline__contains="Lennon")

SQL 等价于:

SELECT ... WHERE headline LIKE '%Lennon%';
  • icontains:不区分大小写的包含,例如 field_name__icontains=value
Entry.objects.get(headline__icontains="Lennon")

SQL 等价于:

SELECT ... WHERE headline ILIKE '%Lennon%';
  • gt:大于,例如 field_name__gt=value
Entry.objects.filter(id__gt=4)

SQL 等价于:

SELECT ... WHERE id > 4;
  • lt:小于,例如 field_name__lt=value
  • gte:大于等于,例如 field_name__gte=value
  • lte:小于等于,例如 field_name__lte=value
  • in在一个给定的可迭代对象中;通常是一个列表、元组或查询集。例如 field_name__in=[value1, value2]
Entry.objects.filter(id__in=[1, 3, 4])

Entry.objects.filter(headline__in="abc")

SQL 等价于:

SELECT ... WHERE id IN (1, 3, 4);

SELECT ... WHERE headline IN ('a', 'b', 'c');
  • range:在某个范围内,例如 field_name__range=(value1, value2)
import datetime

start_date = datetime.date(2005, 1, 1)

end_date = datetime.date(2005, 3, 31)

Entry.objects.filter(pub_date__range=(start_date, end_date))

SQL 等价于:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
  • startswith:区分大小写的开头为。
Entry.objects.filter(headline__startswith="Lennon")

SQL 等价于:

SELECT ... WHERE headline LIKE 'Lennon%';
  • istartswith:不区分大小写的开头为。
Entry.objects.filter(headline__istartswith="Lennon")

SQL 等价于:

SELECT ... WHERE headline ILIKE 'Lennon%';
  • endswith:区分大小写的结尾为。
Entry.objects.filter(headline__endswith="Lennon")

SQL 等价于:

SELECT ... WHERE headline LIKE '%Lennon';
  • iendswith:不区分大小写的结尾为。
Entry.objects.filter(headline__iendswith="Lennon")

SQL 等价于:

SELECT ... WHERE headline ILIKE '%Lennon'
  • isnull:查找字段值为空的记录。取 True 或 False,分别对应 IS NULL 和 IS NOT NULL 的 SQL 查询。
Entry.objects.filter(pub_date__isnull=True)

SQL 等价于:

SELECT ... WHERE pub_date IS NULL;
  • date:根据日期进行比较。对于日期时间字段,将值投射为日期。允许链接其他字段的查找。取一个日期值。
Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))

Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
  • year:根据年份进行比较。对于日期和日期时间字段,精确匹配年份。允许链接其他字段的查询。取整数年。
Entry.objects.filter(pub_date__year=2005)

Entry.objects.filter(pub_date__year__gte=2005)

SQL 等价于:

SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';

SELECT ... WHERE pub_date >= '2005-01-01';
  • month:根据月份进行比较。对于日期和日期时间字段,精确的月份匹配。允许链接其他字段的查询。取整数 1(1 月)到 12(12 月)。
Entry.objects.filter(pub_date__month=12)

Entry.objects.filter(pub_date__month__gte=6)

SQL 等价于:

SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';

SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';

  • day:根据日期中的日进行比较。对于日期和日期时间字段,精确匹配日期。允许链接其他字段的查询。取整数日。
Entry.objects.filter(pub_date__day=3)

Entry.objects.filter(pub_date__day__gte=3)

SQL 等价于:

SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';

SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';

  • hour:根据小时进行比较。对于日期时间和时间字段,精确的小时匹配。允许链式查找其他字段。取 0 到 23 之间的整数。
Event.objects.filter(timestamp__hour=23)

Event.objects.filter(time__hour=5)

Event.objects.filter(timestamp__hour__gte=12)

SQL 等价于:

SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';

SELECT ... WHERE EXTRACT('hour' FROM time) = '5';

SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';
  • minute:根据分钟进行比较。对于日期时间和时间字段,精确的分钟匹配。允许链式查找其他字段。取 0 到 59 之间的整数。
Event.objects.filter(timestamp__minute=29)

Event.objects.filter(time__minute=46)

Event.objects.filter(timestamp__minute__gte=29)

SQL 等价于:

SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29';

SELECT ... WHERE EXTRACT('minute' FROM time) = '46';

SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';

  • second:根据秒进行比较。对于日期时间和时间字段,完全秒配。允许链式查找其他字段。取 0 到 59 之间的整数。
Event.objects.filter(timestamp__second=31)

Event.objects.filter(time__second=2)

Event.objects.filter(timestamp__second__gte=31)

SQL 等价于:

SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';

SELECT ... WHERE EXTRACT('second' FROM time) = '2';

SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';
  • regex:区分大小写的正则表达式匹配正则表达式语法是使用中的数据库后端的语法。对于没有内置正则表达式支持的 SQLite 来说,这个功能是由(Python)用户定义的 REGEXP 函数提供的,因此正则表达式语法是 Python 的 re 模块的语法。
Entry.objects.get(title__regex=r"^(An?|The) +")

SQL 等价于:

SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL

SELECT ... WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); -- Oracle

SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL

SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite
  • iregex:不区分大小写的正则表达式匹配
Entry.objects.get(title__iregex=r"^(an?|the) +")

SQL 等价于:

SELECT ... WHERE title REGEXP '^(an?|the) +'; -- MySQL

SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); -- Oracle

SELECT ... WHERE title ~* '^(an?|the) +'; -- PostgreSQL

SELECT ... WHERE title REGEXP '(?i)^(an?|the) +'; -- SQLite

2.4 聚合函数

        在 Django 中,聚合函数用于对数据库中的数据进行汇总、计算和分析。这些函数允许你执行各种数据库聚合操作,如计数、总和、平均值、最大值和最小值等,而无需手动编写 SQL 查询。Django 的聚合函数通常与查询集(QuerySet)一起使用,以便从数据库中检索和处理数据。

  1. expressions(表达式): 在 Django ORM 中,表达式是一种用于表示数据库查询的复杂计算或运算的方法。它可以用于创建自定义计算字段、排序规则和筛选条件。

  2. output_field(输出字段): output_field 用于指定聚合函数的输出数据类型,以确保正确的数据类型转换。

  3. filter(过滤): filter 方法用于在查询集中筛选数据,根据指定的条件排除不符合条件的数据。

  4. default(默认值): default 用于设置聚合函数的默认值,当没有数据可用于计算时,将返回该默认值。

  5. extra: extra 允许你向查询添加额外的 SQL 片段,以支持更复杂的查询。

  6. Avg(平均值): Avg 用于计算某个字段的平均值。例如,MyModel.objects.aggregate(Avg('field_name')) 将返回指定字段的平均值。

  7. Count(计数): Count 用于计算查询集中对象的数量。例如,MyModel.objects.aggregate(Count('field_name')) 将返回指定字段的数量。

  8. Max(最大值): Max 用于查找查询集中某个字段的最大值。例如,MyModel.objects.aggregate(Max('field_name')) 将返回指定字段的最大值。

  9. Min(最小值): Min 用于查找查询集中某个字段的最小值。例如,MyModel.objects.aggregate(Min('field_name')) 将返回指定字段的最小值。

  10. Sum(总和): Sum 用于计算查询集中某个字段的总和。例如,MyModel.objects.aggregate(Sum('field_name')) 将返回指定字段的总和。

  11. StdDev(标准差): StdDev 用于计算查询集中某个字段的标准差。

  12. Variance(方差): Variance 用于计算查询集中某个字段的方差。

三,执行查询

        在创建完成数据模型后,Django 自动给予提供一套数据库抽象 API,允许我们创建,检索,更新和删除对象。这里介绍如何使用这些 API。参考数据模型参考获取所有查询选项的完整细节,在下面讲解中将使用以下模型,它们构成了一个博客应用程序:

from datetime import date
from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

3.1 创建对象

3.1.1 保存普通字段

        为了用 Python 对象展示数据表对象,Django 使用了一套直观的系统:一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。要创建一个对象,用关键字参数初始化它,然后调用 save() 将其存入数据库。

# 在django shell中操作
# 从模型中导入类
>>> from blog.models import Blog
# 创建一个类的实例并赋予属性
>>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
# 实例.save()后才会同步保存到数据库!!!
>>> b.save()
# 这在幕后执行了 INSERT SQL 语句。Django 显式调用 save() 后才操作数据库。

注意:save() 方法没有返回值。如果想要一步创建并保存一个对象,使用 create() (不需要再调用save())方法。
        将实例对象修改后再进行保存至数据库。要将修改保存至数据库中已有的某个对象,也需要使用 save()。 

# 给定一个已保存到数据库的 Blog 实例 b5,此示例更改其名称并更新其在数据库中的记录
# 修改实例b5的name属性
>>> b5.name = "New name"
# 同步保存到数据库
>>> b5.save()

3.1.2 保存 ForeignKey 和 ManyToManyField 字段

        更新 ForeignKey 外键字段的工作方式与保存普通字段完全相同——将正确类型的对象分配给相关字段。 此示例更新 Entry 实例条目的 blog 属性,假设 Entry 和 Blog 的相应实例已保存到数据库中(因此我们可以在下面检索它们):

# 导入 Blog 和 Entry 模型
from blog.models import Blog, Entry
# 获取 主键 为 1 的 Entry 对象 pk(primary key)
entry = Entry.objects.get(pk=1)
# 获取name属性为 "Cheddar Talk" 的博客对象
cheese_blog = Blog.objects.get(name="Cheddar Talk")
# 将 entry 的博客属性设置为 cheese_blog
entry.blog = cheese_blog
# 保存 entry 对象的更改
entry.save()

         更新 ManyToManyField 的工作方式略有不同 ——使用字段上的 add() 方法将记录添加到关系中。 此示例将 Author 实例 joe 添加到条目对象:

# 导入 Author 模型
from blog.models import Author
# 使用 create() 方法创建一个名为 "Joe" 的 Author 对象,并将其保存到数据库中
joe = Author.objects.create(name="Joe")
# 使用 add() 方法将先前获取的 "Joe" 作者对象添加到 entry 的 authors 多对多关系中
entry.authors.add(joe)

        要一次性将多条记录添加到 ManyToManyField,需要在对 add() 的调用中包含多个参数,如下所示: 

# 创建四个不同的作者对象,并将它们保存到数据库中
john = Author.objects.create(name="John")
paul = Author.objects.create(name="Paul")
george = Author.objects.create(name="George")
ringo = Author.objects.create(name="Ringo")
# 使用 add() 方法将这四位作者添加到 entry 的 authors 多对多关系中
entry.authors.add(john, paul, george, ringo)

注意:Django 会在添加或指定错误类型的对象时报错。 

3.2 检索对象

        要从数据库检索对象,要通过模型类的 Manager 构建一个 QuerySet。一个 QuerySet 代表来自数据库中对象的一个集合。它可以有 0 个,1 个或者多个 filters. Filters,可以根据给定参数缩小查询结果量。在 SQL 的层面上, QuerySet 对应 SELECT 语句,而*filters*对应类似 WHERE 或 LIMIT 的限制子句。

3.2.1 检索单个对象

        可以使用模型的管理器获取查询集。 每个模型至少有一个Manager,默认称为对象。 直接通过模型类访问它,如下所示:

# 导入 Blog 模型类
from myapp.models import Blog
# 访问 Blog 模型的默认管理器(Manager),它用于执行数据库操作
# Blog.objects表示可以通过该管理器执行与 Blog 模型相关的数据库操作,如查询、创建、更新、删除等。
# 这是模型级别的访问方式。
print(Blog.objects)  # 输出: <django.db.models.manager.Manager object at ...>
# 创建一个 Blog 模型的实例对象,并为其设置 name 和 tagline 属性
b = Blog(name="Foo", tagline="Bar")
# 尝试通过实例对象访问模型的默认管理器(Manager)
# 这是错误的方式,因为管理器应该通过模型类来访问,而不是实例对象。
# 所以会抛出 AttributeError 错误。
try:
    print(b.objects)
except AttributeError as e:
    print(f"Error: {e}")

        Managers 只能通过模型类访问,而不是通过模型实例,目的是强制分离 “表级” 操作和 “行级” 操作。Manager 是模型的 QuerySets 主要来源。例如 Blog.objects.all() 返回了一个 QuerySet,后者包含了数据库中所有的 Blog 对象。

3.2.2 检索全部对象

        从表中检索对象的最简单方法是获取所有对象。 为此,可以在 Manager 上使用 all() 方法:

# 获取Entry模型的全部实例对象
>>> all_entries = Entry.objects.all()
# 方法 all() 会返回了一个包含数据库中所有对象的 QuerySet 对象。

3.2.3 过滤器检索指定对象

        all() 返回的 QuerySet 包含了数据表中所有的对象。但是大多数情况下只需要完整对象集合的一个子集。要创建一个这样的子集,可以通过添加过滤条件来操作原始的 QuerySet。两种最常见的精炼 QuerySet 的方式是:

filter(**kwargs):返回一个新的 QuerySet,包含的对象满足给定查询参数。
exclude(**kwargs):返回一个新的 QuerySet,包含的对象 不 满足给定查询参数。
查询参数(**kwargs)需要符合 Field 查询的要求。

        例如,要包含获取 2006 年的博客条目(entries blog)的 QuerySet,像这样使用 filter():

Entry.objects.filter(pub_date__year=2006)
# 通过默认管理器类也一样:
Entry.objects.all().filter(pub_date__year=2006)

3.2.4 链式过滤器

        很多过滤 QuerySet 的结果本身就是一个 QuerySet,因此可以将优化链接在一起。 例如:

>>> Entry.objects.filter(headline__startswith="What").exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(pub_date__gte=datetime.date(2005, 1, 30))
"""
Entry.objects:这部分表示从 Entry 模型中获取一个 QuerySet,Entry 是一个数据库模型,而 objects 是该模型的默认管理器,用于执行数据库查询操作。
.filter(headline__startswith="What"):这是第一个过滤器,它用于筛选具有以 "What" 开头的标题的 Entry 对象。headline__startswith 表示对 headline 字段进行前缀匹配。
.exclude(pub_date__gte=datetime.date.today()):这是第二个过滤器,它用于排除那些发布日期(pub_date)大于或等于今天的 Entry 对象。pub_date__gte 表示大于或等于的比较。
.filter(pub_date__gte=datetime.date(2005, 1, 30)):这是第三个过滤器,它用于再次筛选那些发布日期大于或等于2005年1月30日的 Entry 对象。
综合起来,这个 QuerySet 的目的是从 Entry 模型中获取那些标题以 "What" 开头,并且发布日期在2005年1月30日之后,但不是今天的所有 Entry 对象。
"""

        这个先获取包含数据库所有条目(entry)的 QuerySet,然后排除一些,再进入另一个过滤器。最终的 QuerySet 包含标题以 "What" 开头的,发布日期介于 2005 年 1 月 30 日与今天之间的所有条目。

3.3 QuerySet

        在 Django 中,QuerySet(查询集)是一种用于与数据库交互的高级抽象,它允许用户执行数据库查询和操作数据,而无需编写原始的 SQL 查询。QuerySet 允许以 Pythonic 的方式来构建、过滤和操作数据库查询,使得数据库操作更加直观和可维护。

以下是 QuerySet 的一些主要特点和功能:

总的来说,QuerySet 是 Django 中用于构建数据库查询的主要工具,它代表了一组对象,并允许你通过添加过滤器来缩小查询结果的范围。这种方式使得在 Django 中执行数据库查询变得灵活且易于使用,无需直接编写复杂的 SQL 查询语句。

  1. 查询数据: 使用 QuerySet,可以执行数据库查询以从数据库中检索数据。例如,可以执行 MyModel.objects.all() 来检索模型 MyModel 中的所有记录。
  2. 过滤数据: 可以使用 filter()、exclude() 和 get() 等方法来过滤查询集中的数据,以满足特定的条件。例如,MyModel.objects.filter(name='John') 将返回名为 "John" 的记录。
  3. 链式调用: QuerySet 支持链式调用,允许你一次又一次地对查询集进行操作。例如,可以同时使用 filter()、exclude() 和 order_by() 来构建复杂的查询。
  4. 排序数据: 使用 order_by() 方法,可以指定查询集中的结果按照指定的字段进行升序或降序排序。
  5. 聚合操作: 可以使用 aggregate() 方法执行聚合操作,如计数、总和、平均值、最大值和最小值等。
  6. 切片和分页: 使用切片操作,可以限制查询集的结果集大小,从而支持分页。
  7. 延迟执行: QuerySet 是惰性加载的,它只在需要时才执行数据库查询。这意味着可以在构建查询集时添加多个条件和操作,然后在需要时执行查询。
  8. 关联查询: QuerySet 支持模型之间的关联查询,例如使用 select_related() 和 prefetch_related() 方法来优化查询性能。
  9. 序列化: QuerySet 允许将查询集中的结果序列化为 JSON 或其他数据格式,以用于 API 或其他用途。
  10. QuerySet 代表对象集合 在 Django 中,要从数据库中检索对象(例如,从表中检索记录),你需要使用模型类的 Manager 创建一个 QuerySet。QuerySet 是一个特殊的对象,它代表了一组来自数据库中的对象,这些对象通常是模型类的实例。

  11. QuerySet 可以包含过滤器 QuerySet 不仅仅是一组对象,它还可以包含一个或多个过滤器(Filters)。过滤器是一种查询条件,它们可以根据给定的参数来缩小查询结果的数量。这意味着你可以在数据库查询中使用过滤器来筛选出符合特定条件的对象。

  12. 过滤器的作用 过滤器在 SQL 查询中对应于 WHERE 子句,它用于限制查询结果的范围。通过使用过滤器,你可以指定要检索哪些对象以及如何限制它们。此外,过滤器也可以类比于 SQL 查询中的其他限制子句,如 LIMIT、ORDER BY 等。

        总的来说,在Django提供的那些查询方法中,一部分返回的是查询集QuerySet,这一部分可以使用链式语法进行数据的继续查询,另外一部分查询直接返回单个查询实例。QuerySet 是 Django 中用于数据库交互的核心工具之一。它提供了高级的抽象,使数据库操作更加方便和可读。可以使用 QuerySet 来执行各种数据库操作,从简单的检索数据到复杂的过滤、排序、聚合和关联查询。它是 Django 中的强大工具,用于处理数据模型的查询和操作。

3.3.1 每个QuerySet都是唯一的

        每次精炼一个 QuerySet都会获得一个全新的 QuerySet,后者与前者毫无关联。每次精炼都会创建一个单独的、不同的 QuerySet,能被存储,使用和复用。

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
"""
这三个 QuerySets 是独立的。第一个是基础 QuerySet,包含了所有标题以 "What" 开头的条目。第二个是
第一个的子集,带有额外条件,排除了 pub_date 是今天和今天之后的所有记录。第三个是第一个的子集,带
有额外条件,只筛选 pub_date 是今天或未来的所有记录。最初的 QuerySet (q1) 不受筛选操作影响。
"""

3.3.2 QuerySet是惰性的

        Django 中的 QuerySets 具有惰性特性,即在创建 QuerySet 时并不会立即执行数据库查询操作。可以在 QuerySet 上一层叠加多个过滤器,但 Django 不会立即执行数据库查询,直到显式地对 QuerySet 进行评估。这意味着可以在构建查询的过程中一直添加条件,而不会导致不必要的数据库查询开销。只有真正需要查询结果时,才会触发实际的数据库查询操作。

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
"""
虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了一次。一般来说, QuerySet 的结
果直到 “要使用” 时才会从数据库中拿出。当要用时,才通过数据库 计算 出 QuerySet。关于何时才真的执
行计算的更多细节,参考 什么时候 QuerySet 被执行。
"""

3.3.3 用 get() 检索单个对象

        filter() 总是返回一个 QuerySet,即便只有一个对象满足查询条件 —— 这种情况下, QuerySet 只包含了一个元素。如果已经知道只有一个对象与想要进行的查询匹配,则可以在 Manager 上使用 get() 方法,该方法直接返回该对象:

#  直接将pk(primary key)为1的对象赋予变量one_entry
>>> one_entry = Entry.objects.get(pk=1)

        也可以对 get() 使用与 filter() 类似的所有查询表达式
        注意, 使用切片 [0] 时的 get() 和 filter() 有点不同。如果没有满足查询条件的结果, get() 会抛出一个 DoesNotExist 异常。该异常是执行查询的模型类的一个属性 —— 所有,上述代码中,若没有哪个 Entry 对象的主键是 1,Django 会抛出 Entry.DoesNotExist。
        类似,Django 会在有不止一个记录满足 get() 查询条件时发出警告。这时,Django 会抛出 MultipleObjectsReturned,这同样也是模型类的一个属性。 

3.3.4 其它 QuerySet 方法

        大多数情况下,我们会在需要从数据库中检索对象时使用 all(), get(), filter() 和 exclude()。然而,这样远远不够;完整的各种 QuerySet 方法我会在后续进行总结或参阅官方文档的 QuerySet API 参考

3.3.5 限制 QuerySet 条目数

        利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。

# 这将返回获取的QuerySet集合的前5个对象(LIMIT 5):
>>> Entry.objects.all()[:5]
# 这将返回获取的QuerySet集合第六到第十个对象(OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
# 注意QuerySet中不支持负索引 (例如 Entry.objects.all()[-1])
# 由于对 queryset 切片工作方式的模糊性,禁止对其进行进一步的排序或过滤。
# 查询返回了前10个 Entry 对象中,每隔2个对象选择一个的结果。
>>> Entry.objects.all()[:10:2]

        要检索单个对象而不是列表(例如 SELECT foo FROM bar LIMIT 1),请使用索引而不是切片。 例如,在按标题字母顺序对条目进行排序后,这将返回数据库中的第一个条目: 

>>> Entry.objects.order_by("headline")[0]
# 上方操作大致等同于:
>>> Entry.objects.order_by("headline")[0:1].get()
# 然而,注意一下,若没有对象满足给定条件,前者会抛出 IndexError,而后者会抛出 DoesNotExist。
# 参考 get()更多细节

3.4 字段查询

        字段查询即如何制定 SQL WHERE 子句。它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()。

        基本查找关键字参数采用 field__lookuptype=value 的形式。 (这是一个双下划线)。Python 能定义可接受任意数量 name-value 参数的函数,参数名和值均在运行时计算。 例如:

>>> Entry.objects.filter(pub_date__lte="2006-01-01")
转换为 SQL 语句大致如下:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

        查询子句中指定的字段必须是模型的一个字段名。不过也有个例外,在 ForeignKey 中,你可以指定以 _id 为后缀的字段名。这种情况下,value 参数需要包含 foreign 模型的主键的原始值。例子:

>>> Entry.objects.filter(blog_id=4)
# 若你传入了无效的关键字参数,查询函数会抛出 TypeError。

 3.4.1 常见字段

exact:精确查找

>>> Entry.objects.get(headline__exact="Cat bites dog")
会生成这些 SQL:
SELECT ... WHERE headline = 'Cat bites dog';

        若没有提供查询类型,也就说若关键字参数未包含双下划线:查询类型会被指定为 exact。

# 下面两个是相等的
>>> Blog.objects.get(id__exact=14)  # 显式形式
>>> Blog.objects.get(id=14)  # __exact 是隐含的
# 这是为了方便,因为 exact 查询是最常见的。

 iexact:不区分大小写的匹配。

>>> Blog.objects.get(name__iexact="beatles blog")
# 会匹配标题为 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog。

contains:大小写敏感的包含测试

Entry.objects.get(headline__contains="Lennon")
粗略地转为 SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
# 注意这将匹配标题 'Today Lennon honored',而不是 'today lennon honored'。
# 这也有个大小写不敏感的版本, icontains。

startswith / endswith:以……开头和以……结尾的查找。当然也有大小写不敏感的版本,名为 istartswith 和 iendswith。 

3.5 跨关系查询

        Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动处理 SQL JOIN 关系。为了跨越关系,跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。

# 此示例检索名称为“Beatles Blog”的 Blog 的所有 Entry 对象:
>>> Entry.objects.filter(blog__name="Beatles Blog")

        它也可以反向工作。虽然可以自定义,默认情况下,在查找中使用模型的小写名称来引用一个 “反向” 关系。 

# 此示例检索至少有一个标题包含“Lennon”的 Entry 的所有 Blog 对象:
>>> Blog.objects.filter(entry__headline__contains="Lennon")

        如果在跨多个关系进行筛选,而某个中间模型的没有满足筛选条件的值,Django 会将它当做一个空的(所有值都是 NULL)但是有效的对象。这样就意味着不会抛出错误。例如,在这个过滤器中: 

# 假设有个关联的 Author 模型,若某项条目没有任何关联的 author,它会被视作没有关联的 name,而不是因为缺失 author 而抛出错误。
Blog.objects.filter(entry__authors__name="Lennon")
# 大多数情况下,这就是我们期望的。唯一可能使我们迷惑的场景是在使用 isnull 时。
Blog.objects.filter(entry__authors__name__isnull=True)
# 这将会返回 Blog 对象,包含 author 的 name 为空的对象,以及那些 entry 的 author 为空的对象。
# 若不想要后面的对象,可以这样写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

3.6 跨多值关联 

        当跨越 ManyToManyField 或反向查询 ForeignKey (例如从 Blog 到 Entry )时,对多个属性进行过滤会产生这样的问题:是否要求每个属性都在同一个相关对象中重合。我们可能会寻找那些在标题中含有 “Lennon” 的 2008 年的博客,或者我们可能会寻找那些仅有 2008 年的任何条目以及一些在标题中含有 “Lennon” 的较新或较早的条目。

# 选择所有包含 2008 年至少一个标题中有 "Lennon" 的条目的博客(满足两个条件的同一条目)
Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)
# 如果要执行一个更为宽松的查询,选择任何只在标题中带有 "Lennon" 的条目和 2008 年的条目的博客
Blog.objects.filter(entry__headline__contains="Lennon").filter(
    entry__pub_date__year=2008
)
"""
假设只有一个博客既有包含 "Lennon" 的条目又有 2008 年的条目,但 2008 年的条目中没有包含 "Lennon"
 。第一个查询不会返回任何博客,但第二个查询会返回那一个博客。(这是因为第二个过滤器选择的条目可能
与第一个过滤器中的条目相同,也可能不相同)。我们是用每个过滤器语句来过滤 Blog 项,而不是 Entry
项)。简而言之,如果每个条件需要匹配相同的相关对象,那么每个条件应该包含在一个 filter() 调用中。
"""

        由于第二个(更宽松的)查询链接了多个过滤器,它对主模型进行了多次连接,可能会产生重复的结果。备注:

filter() 的查询行为会跨越多值关联,就像前文说的那样,并不与 exclude() 相同。相反,一次 exclude() 调用的条件并不需要指向同一项目。

例如,以下查询会排除那些关联条目标题包含 "Lennon" 且发布于 2008 年的博客:

Blog.objects.exclude(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)
但是,与 filter() 的行为不同,其并不会限制博客同时满足这两种条件。要这么做的话,也就是筛选出所有条目标题不带 "Lennon" 且发布年不是 2008 的博客,你需要做两次查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains="Lennon",
        pub_date__year=2008,
    ),
)

3.7 过滤器为模型指定字段

        在之前的例子中,已经构建过的 filter 都是将模型字段值与常量做比较。但是,要怎么做才能将模型字段值与同一模型中的另一字段做比较呢?Django 提供了 F 表达式 实现这种比较。 F() 的实例充当查询中的模型字段的引用。这些引用可在查询过滤器中用于在同一模型实例中比较两个不同的字段。

3.7.1 F表达式基本使用

        例如,要查找评论数多于 pingback 的所有博客条目的列表,我们构造一个 F() 对象来引用 pingback 计数,并在查询中使用该 F() 对象:

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))

        Django 支持对 F() 对象(包括常量和其他 F() 对象)使用加法、减法、乘法、除法、模数和幂算术。 要查找评论数量是 pingback 两倍以上的所有博客条目,我们修改查询:

>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)
# 查找条目评级小于 pingback 计数和评论计数总和的所有条目
>>> Entry.objects.filter(rating__lt=F("number_of_comments") + F("number_of_pingbacks"))

        还可以使用双下划线表示法来跨越 F() 对象中的关系。 带有双下划线的 F() 对象将引入访问相关对象所需的任何连接。 

# 检索作者姓名与博客名称相同的所有条目
>>> Entry.objects.filter(authors__name=F("blog__name"))
# 对于日期和日期/时间字段,可以添加或减去 timedelta 对象。 
# 以下命令将返回发布后 3 天以上修改的所有条目
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))

        F() 对象支持 .bitand()、.bitor()、.bitxor()、.bitrightshift() 和 .bitleftshift() 的按位运算。 例如: 

>>> F("somefield").bitand(16)

3.7.2 表达式引用变换

        Django 支持在表达式中使用转换。

# 查找上次修改同一年发布的所有 Entry 对象
>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F("mod_date__year"))
# 查找条目发布的最早年份
>>> from django.db.models import Min
>>> Entry.objects.aggregate(first_published_year=Min("pub_date__year"))
# 下面示例查找每年评分最高的条目的值以及所有条目的评论总数
>>> from django.db.models import OuterRef, Subquery, Sum
>>> Entry.objects.values("pub_date__year").annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef("pub_date__year"),
...         )
...         .order_by("-rating")
...         .values("rating")[:1]
...     ),
...     total_comments=Sum("number_of_comments"),
... )

3.8 主键pk查询

        出于方便的目的,Django 提供了一种 pk 查询快捷方式, pk 表示主键 "primary key"。

# 在示例 Blog 模型中,主键是 id 字段,因此这三个语句是等效的:
>>> Blog.objects.get(id__exact=14)  # 精确查找
>>> Blog.objects.get(id=14)  # 隐式的__exact
>>> Blog.objects.get(pk=14)  # pk意味着id__exact

        pk 的使用不限于 __exact 查询——任何查询项都可以与 pk 组合来对模型的主键执行查询:

# 获取 id 为 1、4 和 7 的博客条目
>>> Blog.objects.filter(pk__in=[1, 4, 7])

# 获取所有 id > 14 的博客条目
>>> Blog.objects.filter(pk__gt=14)

         pk 查找也可以使用连接进行操作。 例如,这三个语句是等效的:

>>> Entry.objects.filter(blog__id__exact=3)  # Explicit form
>>> Entry.objects.filter(blog__id=3)  # __exact is implied
>>> Entry.objects.filter(blog__pk=3)  # __pk implies __id__exact

 3.9 模糊查询语句转义

        等效于 LIKE SQL 语句的字段查询子句 (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) 会将 LIKE 语句中有特殊用途的两个符号,即百分号和下划线自动转义。(在 LIKE 语句中,百分号匹配多个任意字符,而下划线匹配一个任意字符。)

        这意味着事情应该直观地工作,这样抽象就不会泄漏。 例如,要检索包含百分号的所有条目,请将百分号用作任何其他字符:

>>> Entry.objects.filter(headline__contains="%")
Django 可以这样处理了引号;生成的 SQL 语句看起来像这样:
SELECT ... WHERE headline LIKE '%\%%';
同样的处理也包括下划线。百分号和下划线都可以自动处理

        Entry.objects.filter(headline__contains="%"):这是一个Django数据库查询的示例,它的目的是找到在 Entry 模型中 headline 字段包含百分号(%)字符的记录。

        生成的 SQL 语句:Django在内部会将上述查询翻译成相应的 SQL 查询语句。在这里,生成的 SQL 语句是 SELECT ... WHERE headline LIKE '%\%%';。这个SQL语句使用了LIKE操作符,它用于在数据库中执行模糊查询。

        Django不仅会处理百分号(%),还会处理下划线()。在SQL中,百分号(%)通常用于表示匹配任意字符的通配符,而下划线()通常用于匹配单个字符的通配符。由于这些字符在SQL中有特殊含义,Django会在处理时自动进行转义,以确保它们被视为普通字符而不是通配符。在生成的SQL语句中,百分号被转义为 \%,这样它就不再具有通配符的含义,而成为了普通字符,以确保正确的查询行为。

        总之,在Django中进行数据库查询时,Django会自动处理特殊字符(如百分号和下划线),以确保它们在查询中被正确处理,而不会引发意外的行为。这有助于开发人员更轻松地编写安全且可靠的数据库查询代码。

3.10 缓存和QuerySet

        每个 QuerySet 都带有缓存,尽量减少数据库访问。新创建的 QuerySet 缓存是空的。一旦要计算 QuerySet 的值,就会执行数据查询,随后,Django 就会将查询结果保存在 QuerySet 的缓存中,并返回这些显式请求的缓存(例如,下一个元素,若 QuerySet 正在被迭代)。后续针对 QuerySet 的计算会复用缓存结果。

使用缓存

        请记住这种缓存行为,因为如果没有正确使用查询集,它可能会带来麻烦。

3.11 事务

        根据官网所说:目前异步查询和更新不支持事务。 尝试使用其中之一会引发 SynchronousOnlyOperation。如果希望使用事务,可以在单独的同步函数中编写 ORM 代码,然后使用sync_to_async 调用该函数 。

3.12 查询 JSONField

         Django中的JSONField的查找实现是不一样的,主要因为存在键转换。为了演示将使用下面这个例子:

from django.db import models


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

    def __str__(self):
        return self.name

3.12.1 保存和查询 None 值 

        与其他字段一样,将 None 存储为字段值会将其存储为 SQL NULL。 虽然不推荐,但可以使用 Value(None, JSONField()) 存储 JSON 标量 null 而不是 SQL NULL。无论存储哪种值,当从数据库检索时,JSON 标量 null 的 Python 表示法与 SQL 的 NULL 相同,即 None。因此,可能很难区分它们。这只适用于 None 值作为字段的顶级值。如果 None 被保存在列表或字典中,它将始终被解释为 JSON 的 null 值。查询时,None 值将始终被解释为 JSON null。 要查询 SQL NULL,请使用 isnull:

>>> Dog.objects.create(name="Max", data=None)  # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name="Archie", data=Value(None, JSONField()))  # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value(None, JSONField()))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>
# 保存 JSON 的 null 值不违反 Django 的 null=False 。

# 除非你定要使用 SQL 的 NULL 值,否则请考虑设置 null=False 并为空值提供合适的默认值,例如
# default=dict 。

3.12.2 键、索引和路径转换 

        要根据给定的字典键进行查询,请使用该键作为查找名称:

>>> Dog.objects.create(
...     name="Rufus",
...     data={
...         "breed": "labrador",
...         "owner": {
...             "name": "Bob",
...             "other_pets": [
...                 {
...                     "name": "Fishy",
...                 }
...             ],
...         },
...     },
... )
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed="collie")
<QuerySet [<Dog: Meg>]>
# 多个键可以链接在一起以形成路径查找
>>> Dog.objects.filter(data__owner__name="Bob")
<QuerySet [<Dog: Rufus>]>
# 如果键是整数,它将被解释为数组中的索引转换
>>> Dog.objects.filter(data__owner__other_pets__0__name="Fishy")
<QuerySet [<Dog: Rufus>]>

# 如果要查询的键与另一个查询的键名冲突,请改用 contains 来查询
# 要查询丢失的键,请使用 isnull 查找
>>> Dog.objects.create(name="Shep", data={"breed": "collie"})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
# 以下代码将创建两个查询集,对它们进行评估,然后将它们丢弃
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
"""
这意味着同样的数据库查询会被执行两次,实际加倍了数据库负载。同时,有可能这两个列表不包含同样的记
录,因为在两次请求间,可能有 Entry 被添加或删除了。
"""
# 为了避免此问题,可以保存 QuerySet 并重用
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset])  # 评估查询集。
>>> print([p.pub_date for p in queryset])  # 重用评估中的缓存。

当QuerySet未被缓存时

        查询结果集并不总是缓存结果。当仅计算查询结果集的部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。特别地说,这意味着使用数组切片或索引的 限制查询结果集 不会填充缓存。

重复获取queryset对象中的某个索引,每次都会查询数据库:
>>> queryset = Entry.objects.all()
>>> print(queryset[5])  # 查询数据库
>>> print(queryset[5])  # 再次查询数据库

        但是,如果整个查询集已被评估,则将检查缓存: 

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset]  # 查询数据库
>>> print(queryset[5])  # 使用缓存
>>> print(queryset[5])  # 使用缓存

# 以下是其他操作的一些示例,这些操作将导致评估整个查询集并因此填充缓存
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

注意:只是打印查询结果集不会填充缓存。因为调用 __repr__() 仅返回了完整结果集的一个切片。

3.12.3 包含与键查找

contains:使用contains查询,可以查找包含特定键的数据行。这意味着正在寻找数据结构中至少包含查询的键的行。

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={"owner": "Bob"})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={"breed": "collie"})
<QuerySet [<Dog: Meg>]>

contained_by:查询用于查找数据结构在特定键中包含特定值的数据行。这意味着正在寻找数据结构的特定键包含查询的值的行。

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador", "owner": "Bob"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.create(name="Fred", data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>

has_key:返回给定的键位于数据顶层的对象。 

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key="owner")
<QuerySet [<Dog: Meg>]>

has_keys: 返回所有给定的键位于数据顶层的对象。

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"breed": "collie", "owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=["breed", "owner"])
<QuerySet [<Dog: Meg>]>

has_any_keys: 返回任何给定的键位于数据顶层的对象。

>>> Dog.objects.create(name="Rufus", data={"breed": "labrador"})
<Dog: Rufus>
>>> Dog.objects.create(name="Meg", data={"owner": "Bob"})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=["owner", "breed"])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>

3.13 Q对象复杂查询 

        在类似 filter() 中,查询使用的关键字参数是通过 "AND" 连接起来的。如果要执行更复杂的查询(例如,由 OR 语句连接的查询),可以使用 Q 对象。一个 Q 对象 (django.db.models.Q) 用于压缩关键字参数集合。这些关键字参数由前文 "Field lookups" 指定。

# 该 Q 对象压缩了一个 LIKE 查询:
from django.db.models import Q
Q(question__startswith="What")

        可以使用 &、| 和 ^ 运算符组合 Q 对象。 当对两个 Q 对象使用运算符时,它会生成一个新的 Q 对象。 

# 该语句生成一个 Q 对象,表示两个 "question_startswith" 查询语句之间的 "OR" 关系
Q(question__startswith="Who") | Q(question__startswith="What")
# 这相当于以下 SQL WHERE 子句
WHERE question LIKE 'Who%' OR question LIKE 'What%'

        可以通过将 Q 对象与 &、| 和 ^ 运算符组合并使用括号分组来组成任意复杂度的语句。 此外,可以使用 ~ 运算符对Q 对象进行否定,从而允许将普通查询和否定 (NOT) 查询结合起来进行组合查找: 

Q(question__startswith="Who") | ~Q(pub_date__year=2005)

        每个接受关键字参数的查询函数 (例如 filter(), exclude(), get()) 也同时接受一个或多个 Q 对象作为位置(未命名的)参数。若为查询函数提供了多个 Q 对象参数,这些参数会通过 "AND" 连接。例子: 

Poll.objects.get(
    Q(question__startswith="Who"),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
# 粗略地转为 SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

        查询函数能混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q 对象)均通过 "AND" 连接。然而,若提供了 Q 对象,那么它必须位于所有关键字参数之前。例子: 

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith="Who",
)
……会是一个有效的查询,等效于前文的例子;但是:

# INVALID QUERY
Poll.objects.get(
    question__startswith="Who",
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
……却是无效的。

四,关联关系

        在 Django 的模型中,关联关系(Relationships)是用来描述模型之间如何相互连接和关联的机制。它们用于建立不同模型之间的关联,以便在数据库中存储和检索相关数据。Django 支持多种类型的关联关系。相当于数据库中的外键关联。

4.1 一对一关系

        一对一关系(One-to-One Relationship) :用于创建一对一关系。这种关系表示两个模型之间的唯一一对一关联。每个模型实例与另一个模型实例相关联。例如,一个人可能只有一个护照号,而每个护照号也只对应一个人。

        OneToOneField字段: 用于创建一对一关系。

        假设我们有两个模型:Person 和 Passport,其中 Passport 模型与 Person 模型存在一对一关系,表示每个人都有一个唯一的护照。

from django.db import models

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

class Passport(models.Model):
    passport_number = models.CharField(max_length=20)
    owner = models.OneToOneField(Person, on_delete=models.CASCADE)


        在这个示例中,Passport 模型包含一个外键字段 owner,它与 Person 模型建立了一对一关系。每个护照只能属于一个人,每个人也只能有一个护照。

4.2 一对多关系

        一对多关系(One-to-Many Relationship): 这种关系表示一个模型与另一个模型之间的一对多关联。一个模型实例可以与多个另一个模型的实例相关联,但每个另一个模型的实例只能与一个模型实例相关联。例如,一篇博客文章可以有多个评论,但每个评论只属于一篇文章。

        ForeignKey字段: 用于创建一对多关系。

        假设我们有两个模型:Author 和 Book,其中 Author 模型与 Book 模型存在一对多关系,表示每个作者可以有多本书。

from django.db import models

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)


        在这个示例中,Book 模型包含一个外键字段 author,它与 Author 模型建立了一对多关系。每本书只能有一个作者,但每个作者可以有多本书。

4.3 多对多关系

        多对多关系(Many-to-Many Relationship): 这种关系表示两个模型之间的多对多关联。每个模型实例可以与多个另一个模型的实例相关联,反之亦然。例如,一个学生可以参加多个课程,而每门课程也可以有多名学生。

        ManyToManyField字段: 用于创建多对多关系。

        假设我们有两个模型:Student 和 Course,其中 Student 模型与 Course 模型存在多对多关系,表示每个学生可以参加多门课程,同时每门课程也可以有多名学生。

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField('Course')

class Course(models.Model):
    title = models.CharField(max_length=100)


        在这个示例中,Student 模型包含一个多对多字段 courses,它与 Course 模型建立了多对多关系。这表示每个学生可以参加多门课程,同时每门课程也可以有多名学生。多对多关系由 Django 自动处理,你可以轻松地为学生和课程建立关联。

4.4 外键

        ForeignKey 字段是 Django 中用于创建一对多和多对一关系的字段类型。它允许你在一个模型中创建对另一个模型的外键引用,以建立模型之间的关联关系。ForeignKey 字段通常用于表示多个模型实例与一个模型实例之间的关系。

        对于一对一关系、一对多关系和多对多关系,Django 提供了专门的字段类型来简化和明确地表示这些关系。虽然在某些情况下可以使用 ForeignKey 字段来模拟这些关系,但最佳实践是使用相应的字段类型以提高代码的可读性和维护性。

4.4.1 外键与一对一关系

        虽然可以使用 ForeignKey 字段来创建一对一关系,但使用 OneToOneField 字段更加明确和常见,它专门表示一对一关系。OneToOneField 更容易理解,并且在数据库层面上会创建唯一约束,确保了一对一的关联。

4.4.1 外键与一对多关系

        使用 ForeignKey 字段来模拟一对多关系也是可以的,但使用 ForeignKey 字段通常更适合表示多对一关系。为了表示一对多关系,最好使用 ForeignKey 字段的逆向关系,或者使用 ManyToManyField 字段。

4.4.1 外键与多对多关系

        多对多关系只能使用 ManyToManyField 字段来表示,因为它们是一种特殊类型的关系。ManyToManyField 字段允许在两个模型之间创建多对多关联,Django 会自动创建关联表来处理这种关系。

        总之,虽然可以使用 ForeignKey 字段来模拟一些关系,但最佳实践是使用专门的字段类型来表示一对一、一对多和多对多关系,以提高代码的可读性和明确性。这有助于使模型更容易理解,并减少错误的可能性。

4.5 关联查询

        当在模型中定义了关联关系(如 ForeignKey, OneToOneField, 或 ManyToManyField),该模型的实例将会自动获取一套 API,能快捷地访问关联对象。

4.5.1 一对多关联

正向关联

        如果模型有一个ForeignKey,该模型的实例可以通过其属性访问关联(外部的)对象。

# 将Entry中id为2的对象赋予变量e
>>> e = Entry.objects.get(id=2)
# 返回相关联的 Blog 对象
>>> e.blog  

        可以通过外键属性获取和设置。但是在调用save()之前,对外键的更改不会保存到数据库中。

# 将id为2的实例赋予变量e
>>> e = Entry.objects.get(id=2)
# 修改外键对象,some_blog也必须是Blog中的对象
>>> e.blog = some_blog
# 保存修改内容到数据库
>>> e.save()

        如果某个ForeignKey字段已设置null=True(即,它允许NULL值),可以分配None以删除关系。

>>> e = Entry.objects.get(id=2)
# 将关联的Blog外键设置为空
>>> e.blog = None
# 保存
>>> e.save()  # "UPDATE blog_entry SET blog_id = NULL ...;"

        对于一对多关系的前向访问将在第一次访问相关对象时进行缓存。对同一对象实例上的外键的后续访问将被缓存。

>>> e = Entry.objects.get(id=2)
# 使用数据库以检索关联的Blog
>>> print(e.blog)  
# 不触及数据库,使用缓存版本
>>> print(e.blog)  

        请注意,select_related()方法会提前递归地预填充所有一对多关系的缓存。

>>> e = Entry.objects.select_related().get(id=2)
# 不触及数据库,使用缓存版本。
>>> print(e.blog)  
# 不触及数据库,使用缓存版本。
>>> print(e.blog)  

反向关联

        若模型有 ForeignKey,外键关联的模型实例将能访问 Manager,后者会返回第一个模型的所有实例。默认情况下,该 Manager 名为 FOO_set, FOO 即模型名的小写形式。 Manager 返回 QuerySets,可以使用检索遍历等方式进行筛选和操作。

        使用关联管理器(Related Manager)来执行与外键关联的查询操作。假设我们有两个模型:BlogEntry,其中 Blog 模型包含一个外键字段,表示博客与其相关的多篇文章(Entry 对象)。

>>> b = Blog.objects.get(id=1)  # 获取博客对象(假设博客的ID为1)
>>> b.entry_set.all()  # 返回与博客相关的所有 Entry 对象。
# b.entry_set 是一个管理器(Manager),它返回 QuerySets。
>>> b.entry_set.filter(headline__contains="Lennon")
# 使用 filter() 方法过滤标题包含 "Lennon" 的 Entry 对象。
>>> b.entry_set.count()
# 使用 count() 方法计算与博客相关的 Entry 对象的数量。

        总之,关联管理器(例如 entry_set)可以轻松地执行与外键关联的查询操作,而不需要编写复杂的 SQL 查询。这使得在 Django 中处理关联数据变得非常方便。

        也可以通过在 ForeignKey 字段的定义中设置 related_name 参数来自定义 FOO_set 的名称。例如:

# 如果将 Entry 模型的字段定义修改为 
blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
>>> b = Blog.objects.get(id=1)  # 获取博客对象(假设博客的ID为1)
>>> b.entries.all() 
# 使用 b.entries.all() 获取与博客相关的所有 Entry 对象。
# b.entries 是一个管理器(Manager),它返回 QuerySets。
>>> b.entries.filter(headline__contains="Lennon")
# 使用 filter() 方法过滤标题包含 "Lennon" 的 Entry 对象。
>>> b.entries.count()
# 使用 count() 方法计算与博客相关的 Entry 对象的数量。

使用自定义反向管理器

        RelatedManager 反向关联的默认实现是该模型 默认管理器 一个实例。若想为某个查询指定一个不同的管理器,可以使用如下语法:

from django.db import models

# 定义 Entry 模型
class Entry(models.Model):
    # 默认的管理器,通常称为 'objects'
    objects = models.Manager()
    
    # 自定义的管理器,可以用于定制查询
    entries = EntryManager()

# 获取博客对象
b = Blog.objects.get(id=1)

# 使用自定义的管理器 'entries' 来获取与博客相关的所有文章
entries = b.entry_set(manager="entries").all()

        若 EntryManager 在其 get_queryset() 方法执行了默认过滤行为,该行为会应用到 all() 调用中。指定一个自定义反向管理也允许调用模型自定义方法:

b.entry_set(manager="entries").is_published()

管理关联对象的额外方法

ForeignKey Manager 还有方法能处理关联对象集合。除了“检索对象”中定义的 QuerySet 方法以外,以下是每项的简要介绍

add(obj1, obj2, ...):将特定的模型对象加入关联对象集合。
create(**kwargs):创建一个新对象,保存,并将其放入关联对象集合中。返回新创建的对象。
remove(obj1, obj2, ...):从关联对象集合删除指定模型对象。
clear():从关联对象集合删除所有对象。
set(objs):替换关联对象集合

要指定关联集合的成员,调用 set() 方法,并传入可迭代的对象实例集合。例如,若 e1 和 e2 都是 Entry 实例:

b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])

所有 “反向” 操作对数据库都是立刻生效的。每次的增加,创建和删除都是及时自动地保存至数据库。

4.5.2 多对多关联

        多对多关联的两端均自动获取访问另一端的 API。该 API 的工作方式类似上面的 “反向” 一对多关联。不同点在为属性命名上:定义了 ManyToManyField 的模型使用字段名作为属性名,而 “反向” 模型使用源模型名的小写形式,加上 '_set' (就像反向一对多关联一样)。

        如果一个模型(假设是模型 A)定义了一个多对多关联字段(ManyToManyField),那么这个字段会被添加到模型 A 中,并使用字段名作为属性名。这个字段允许模型 A 的实例直接访问与之相关联的模型(假设是模型 B)的对象集合,就像直接访问一个属性一样。另一方面,与模型 A 关联的模型 B 会自动获得一个名为模型名的小写形式,然后加上 _set 的属性,用于访问与模型 B 相关联的模型 A 的对象集合。这就是 "反向" 多对多关联的属性命名规则。

        假设我们有两个模型:AuthorBook,它们之间存在多对多关联关系。Author 模型定义了多对多字段 books,而 Book 模型被关联到多个作者。

class Author(models.Model):
    name = models.CharField(max_length=100)
    books = models.ManyToManyField(Book)

class Book(models.Model):
    title = models.CharField(max_length=100)

# 从 Author 实例访问其相关的书籍(多对多字段):
author = Author.objects.get(id=1)  # 获取一个作者对象
books_by_author = author.books.all()  # 获取该作者的所有书籍

# 从 Book 实例访问其相关的作者集合(反向关联,使用模型名的小写形式加上 _set):
book = Book.objects.get(id=1)  # 获取一本书的对象
authors_of_book = book.author_set.all()  # 获取所有与该书相关的作者
# 获取ID为3的Entry对象
e = Entry.objects.get(id=3)
# 返回与这个Entry对象相关联的所有Author对象
e.authors.all()
# 返回与这个Entry对象相关联的Author对象的数量
e.authors.count()
# 返回与这个Entry对象相关联的所有名字包含'John'的Author对象
e.authors.filter(name__contains="John")
# 获取ID为5的Author对象
a = Author.objects.get(id=5)
# 返回与这个Author对象相关联的所有Entry对象
a.entry_set.all() 

        和 ForeignKey 一样, ManyToManyField 能指定 related_name。在上面的例子中,若 Entry 中的 ManyToManyField 已指定了 related_name='entries',随后每个 Author 实例会拥有一个 entries 属性,而不是 entry_set。

        与 ForeignKey 字段一样,ManyToManyField 字段也可以通过指定 related_name 参数来自定义反向关联的名称。在示例中,如果在 Entry 模型中的 ManyToManyField 字段定义中指定了 related_name='entries',那么之后每个 Author 实例都将拥有一个名为 entries 的属性,而不是默认的 entry_set。

# 假设我们有两个模型:Author 和 Entry,
# 它们之间存在多对多关系。Entry 模型中包含一个多对多字段 authors,
# 它指定了 related_name='entries'。

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

class Entry(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author, related_name='entries')
# 现在,如果我们获取一个 Author 实例
# 并且该实例与多个 Entry 相关联
# 那么我们可以使用 entries 属性来访问这些关联的条目
# 而不是使用默认的 entry_set。

author = Author.objects.get(id=1)  # 获取一个作者对象
entry_objects = author.entries.all()  # 获取该作者的所有相关条目

# 在示例中,我们使用了自定义的 related_name='entries'
# 因此我们可以通过 author.entries.all() 来访问作者的所有相关条目
# 而不是使用默认的 author.entry_set.all()。
from django.db import models
# 定义 Entry 模型
class Entry(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 使用related_name属性
    authors = models.ManyToManyField('Author', related_name='entry_set')
    def __str__(self):
        return self.title
# 定义 Author 模型
class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

# 获取某个作者实例
author = Author.objects.get(name='John')

# 通过 related_name 访问该作者的 Entry 实例
entries = author.entry_set.all()

# 打印获取的 Entry 实例
for entry in entries:
    print(entry.title)

        另一个与一对多关联不同的地方是,除了模型实例以外,多对多关联中的 add(), set() 和 remove() 方法能接收主键值。例如,若 e 和 e2 是 Entry 的实例,以下两种 set() 调用结果一致:

# 从Author模型中获取id为5的实例
a = Author.objects.get(id=5)
# 设置Author实例a的entry_set,也就是为a关联的Entry实例
a.entry_set.set([e1, e2])
# 设置Author实例a的entry_set,但是这次它传递的是两个Entry实例的id(通过pk属性获得)
a.entry_set.set([e1.pk, e2.pk])

4.5.3 一对一关联

        一对一关联与多对一关联非常类似。若在模型中定义了 OneToOneField,该模型的实例只需通过其属性就能访问关联对象。

class EntryDetail(models.Model):
    """
    EntryDetail模型。
    属性:
        entry:关联的条目对象。
        details:存储条目详情的文本字段。
    """
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()
def __str__(self):
    """
    返回 EntryDetail 对象的字符串表示形式。
    """
    return f"EntryDetail 对象,对应条目 ID:{self.entry_id}"
# 获取 ID 为 2 的 EntryDetail 对象。
ed = EntryDetail.objects.get(id=2)



ed.entry  # 返回关联的条目对象。
"""
返回关联的条目对象。`entry` 属性是一个 OneToOneField,
这意味着每个 EntryDetail 对象都关联到一个单独的条目对象。
"""

        不同点在于 “反向” 查询。一对一关联所关联的对象也能访问 Manager 对象,但这个 Manager 仅代表一个对象,而不是对象的集合:

e = Entry.objects.get(id=2)
# 返回关联的EntryDetail对象
e.entrydetail 

        如果未为关联关系指定对象,Django 会抛出 DoesNotExist 异常。

ed = EntryDetail.objects.get(id=2)
ed.entry  # 会抛出 DoesNotExist 异常

        实例可以通过与为正向关联指定关联对象相同的方式指定给反向关联。

# 以下代码会将 ed 对象指定给 e 对象的 entrydetail 属性:
e = Entry.objects.get(id=1)
ed = EntryDetail.objects.get(id=2)
e.entrydetail = ed

        Django 会在反向关联的 manager 中查找关联对象。如果找到了,Django 会将该对象设置为当前实例的关联对象。如果找不到,Django 会抛出 DoesNotExist 异常。

class Entry(models.Model):
    title = models.CharField(max_length=255)
    entrydetail = models.OneToOneField(EntryDetail, on_delete=models.CASCADE)


class EntryDetail(models.Model):
    details = models.TextField()


e = Entry.objects.get(id=1)
ed = EntryDetail.objects.get(id=2)

# 以下代码会成功
e.entrydetail = ed

# 以下代码会抛出 DoesNotExist 异常
e.entrydetail = EntryDetail.objects.get(id=3)

4.5.4 反向关联如何实现

创建一个模型类,模型类并不知道是否有其它模型类关联它,直到其它模型类被加载?
        答案位于应用注册。Django 启动时,它会导入 INSTALLED_APPS 列出的每个应用,和每个应用中的 model 模块。无论何时创建了一个新模型类,Django 为每个关联模型添加反向关联。若被关联的模型未被导入,Django 会持续追踪这些关联,并在关联模型被导入时添加关联关系。出于这个原因,包含所使用的所有模型的应用必须列在 INSTALLED_APPS 中。否则,反向关联可能不会正常工作。        

4.5.5 查询关联对象

        涉及关联对象的查询与涉及普通字段的查询遵守同样的规则。未查询条件指定值时,可以使用对象实例,或该实例的主键。例如,若有个博客对象 b,其 id=5,以下三种查询是一样的:

class Blog(models.Model):
    title = models.CharField(max_length=255)


class Entry(models.Model):
    title = models.CharField(max_length=255)
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)


b = Blog.objects.create(title="我的博客")
e = Entry.objects.create(title="我的第一篇文章", blog=b)

# 以下代码会成功(实例查询)
Entry.objects.filter(blog=b)

# 以下代码也会成功(id属性查询)
Entry.objects.filter(blog=b.id)

# 以下代码也会成功(主键查询)
Entry.objects.filter(blog=5)

        Django 会在 blog 关联的 manager 中查找关联对象。如果使用对象实例,Django 会将该对象作为查询条件。如果使用主键,Django 会将主键作为查询条件。

五,对象操作

5.1 比较对象

        要比较两个模型实例,使用标准的 Python 比较操作符,两个等号: ==。实际上,这比较了两个模型实例的主键值。   

# 以下两个语句是等效的
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

# 如果模型的主键不称为 id,没问题。 比较将始终使用主键,
# 无论它叫什么。例如,如果模型的主键字段名为 name,则这两个语句是等效的
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

5.2 删除对象

        为了方便起见,删除方法被命名为delete()。 此方法立即删除对象并返回已删除对象的数量以及包含每种对象类型的删除次数的字典。 

>>> e.delete()
(1, {'blog.Entry': 1})
# 也能批量删除对象。所有 QuerySet 都有个 delete() 方法,它会删除 QuerySet 中的所有成员。

# 例如,这将删除 pub_date 年份为 2005 年的所有 Entry 对象:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

        请记住,只要有机会的话,这会通过纯 SQL 语句执行,所以就无需在过程中调用每个对象的删除方法了。若为模型类提供了自定义的 delete() 方法,且希望确保调用了该方法,需要 “手动” 删除该模型的实例(例如,如,遍历 QuerySet,在每个对象上分别调用 delete() 方法),而不是使用 QuerySet 的批量删除方法 delete()。 

        当 Django 删除某个对象时,默认会模仿 SQL 约束 ON DELETE CASCADE 的行为——换而言之,某个对象被删除时,关联对象也会被删除。例子:

b = Blog.objects.get(pk=1)
# 这将删除博客及其所有 Entry 对象。
b.delete()
# 这种约束行为由 ForeignKey 的 on_delete 参数指定。
注意 delete() 是唯一未在 Manager 上暴漏的 QuerySet 方法。这是一种安全机制,避免你不小心调用了Entry.objects.delete(),删除了所有的条目。若确实想要删除所有对象,必须显示请求完整结果集合:
Entry.objects.all().delete()

5.3 复制模型实例

        尽管没有用于复制模型实例的内置方法,但可以轻松创建复制所有字段值的新实例。 在最简单的情况下,可以将 pk 设置为 None 并将 _state.adding 设置为 True。 使用博客示例:

blog = Blog(name="My blog", tagline="Blogging is easy")
blog.save()  # blog.pk == 1

blog.pk = None
blog._state.adding = True
blog.save()  # blog.pk == 2
# 如果使用了集成,事情会更复杂。考虑下 Blog 的一个子类:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)


django_blog = ThemeBlog(name="Django", tagline="Django is easy", theme="python")
django_blog.save()  # django_blog.pk == 3

         由于继承的工作原理,必须将 pk 和 id 设置为 None,并将 _state.adding 设置为 True:

django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save()  # django_blog.pk == 4

        该方法不会拷贝不是模型数据表中的关联关系。例如, Entry 有一个对 Author 的 ManyToManyField 关联关系。在复制条目后,必须为新条目设置多对多关联关系。 

entry = Entry.objects.all()[0]  # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

        对于 OneToOneField 关联,必须拷贝关联对象,并将其指定给新对象的关联字段,避免违反一对一唯一性约束。例如,指定前文复制的 entry:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail._state.adding = True
detail.entry = entry
detail.save()

5.4 一次修改多个对象 

        有时候,如果想统一设置 QuerySet 中的所有对象的某个字段。可以通过 update() 达到目的。例子:

# 更新所有 pub_date 为 2007 年的头条新闻。
Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

        使用此方法只能设置非关系字段和ForeignKey字段。 要更新非关系字段,请提供新值作为常量。 要更新foreignkey字段,请将新值设置为您想要指向的新模型实例。 例如: 

>>> b = Blog.objects.get(pk=1)
# 更改每个条目,使其属于此博客。
>>> Entry.objects.update(blog=b)

        update() 方法立即应用并返回查询匹配的行数(如果某些行已经具有新值,则可能不等于更新的行数)。 对更新的 QuerySet 的唯一限制是它只能访问一个数据库表:模型的主表。 可以根据相关字段进行过滤,但只能更新模型主表中的列。 例子:

>>> b = Blog.objects.get(pk=1)
# 更新属于此博客的所有标题。
>>> Entry.objects.filter(blog=b).update(headline="Everything is the same")

        要认识到 update() 方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的 save() 方法,或发射 pre_save 或 post_save 信号(调用 save() 会触发信号),或使用 auto_now 字段选项。若想保存 QuerySet 中的每项,并确保调用了每个实例的 save() 方法,并不需要任何特殊的函数来处理此问题。迭代它们,并调用它们的 save() 方法:

for item in my_queryset:
    item.save()

        对 update 的调用还可以使用 F 表达式根据模型中另一个字段的值来更新一个字段。 这对于根据当前值递增计数器特别有用。 例如,要增加博客中每个条目的 pingback 计数:

>>> Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)

        但是,与过滤器和排除子句中的 F() 对象不同,在更新中使用 F() 对象时不能引入联接 - 只能引用正在更新的模型的本地字段。 如果尝试引入与 F() 对象的连接,则会引发 FieldError:

# 这将会引发一个 FieldError 错误
>>> Entry.objects.update(headline=F("blog__name"))

 

        

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来回答您的问题。 首先,Django 是一个流行的 Python Web 框架,它提供了许多强大的功能,其中之一就是 ORM(对象关系映射)。 ORM 允许您使用 Python 对象来处理数据库操作,而不是直接编写 SQL 语句。这样做有很多好处,比如更容易维护和更易于理解。 下面是一些基本的 ORM 操作,用于在 Django 中执行增删改操作。 首先,要使用 ORM,您需要在您的 Django 项目中创建一个模型。模型是一个 Python 类,它代表了数据库中的一个表。 例如,这是一个简单的模型类,它代表了一个名为 `Book` 的表: ``` from django.db import models class Book(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=100) published_date = models.DateField() ``` 接下来,我们可以使用以下代码将一个新的 `Book` 对象插入到数据库中: ``` book = Book(title='Django for Beginners', author='William Vincent', published_date='2021-01-01') book.save() ``` 我们可以使用以下代码来更新一个现有的 `Book` 对象: ``` book = Book.objects.get(title='Django for Beginners') book.author = 'William S. Vincent' book.save() ``` 最后,我们可以使用以下代码来删除一个 `Book` 对象: ``` book = Book.objects.get(title='Django for Beginners') book.delete() ``` 这些是最基本的 ORM 操作,但是 Django ORM 还提供了许多其他的高级功能,比如过滤、排序和聚合查询等。如果您想了解更多信息,请查看 Django 的官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值