Django 2.1.3 文档-模型层-执行查询


- - 译自官方文档+自己的理解 --

一旦你创建了 数据模型,Django就会自动为你提供一个数据库抽象API,让你可以创建,检索,更新和删除对象。本文档介绍了如何使用此API。有关所有各种模型查找选项的完整详细信息,请参阅 数据模型参考

在本指南(以及参考文献)中,我们将使用以下模型,包含于Weblog应用程序中:
在这里插入图片描述
译者注
1.译者使用自己的4个模型来提供额外补充,模型定义如下,其中Book和Author是多对多关系,Book和Publish是多对一关系,Author和AuthorDetail是一对一关系:

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

    def __str__(self):
        return self.name

class AuthorDetail(models.Model):
    age = models.IntegerField()
    address = models.CharField(max_length=32,default="china")

    author = models.OneToOneField(to="Author",on_delete=models.CASCADE)
    def __str__(self):
        return self.author.name

class Publish(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField()
    address = models.CharField(max_length=32, default="china-pub")
    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=6,decimal_places=2)
    cate_list = ((1,"叙事作品"),(2,"小说文学"),(3,"科幻文学"),(4,"随笔"))
    category = models.IntegerField(choices=cate_list)

    authors = models.ManyToManyField(to="Author")
    publishs = models.ForeignKey(to="Publish",on_delete=models.CASCADE)

    def __str__(self):
        return "<"+self.name+">"

2.模型对应表中的数据如下:
(1)Author:
在这里插入图片描述
(2)AuthorDetail:
在这里插入图片描述
(3)Book:
在这里插入图片描述
(4)Publish:
在这里插入图片描述

1.创建对象

为了在Python对象中表示数据库表数据,Django使用直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。

要创建对象,请使用模型类的关键字参数对其进行实例化,然后调用save()以将其保存到数据库中。

这里有一个例子:

def fun01_create_obj():
    p = Publish(name='机械',email='[email protected]',address='china')
    p.save()
if __name__ == "__main__":
    fun01_create_obj()

数据库publish表中增加了一条内容
在这里插入图片描述
这会在幕后执行INSERTSQL语句。在您明确调用save()之前,Django不会访问数据库。

save()方法没有返回值。

参见
save()采用了许多此处未描述的高级选项。有关save()完整的详细信息,请参阅文档 。
要在一条语句中创建和保存对象,请使用 create()方法。↑

2.保存对对象的修改

要保存对已存在于数据库中的对象的更改,请使用 save()。

给定已保存到数据库的一个Blog实例 b5,此示例更改其名称并更新其在数据库中的记录:

def fun01_create_obj():
    p = Publish(name='机械',email='[email protected]',address='china')
    p.name = p.name+"append"
    p.save()
    print(p.name)
# 输出结果
机械append

这会在幕后执行UPDATESQL语句。在您明确调用save()之前,Django不会访问数据库。

2.1 保存ForeignKey 和 ManyToManyField 字段

更新ForeignKey字段的手段与保存普通字段的方式完全相同 - 只需将正确类型的对象分配给相关字段即可。假设适当的Entry和Blog实例已经保存到数据库中,这个例子更新了一个Entry实例entry的blog属性:
在这里插入图片描述
更新ManyToManyField方式略有不同 - 使用 add()字段上的方法向关系添加记录。此示例将Author实例 添加joe到entry对象:
在这里插入图片描述
要一次性添加多个ManyToManyField记录,请在调用add()中包含多个参数 ,如下所示:
在这里插入图片描述
如果您尝试分配或添加错误类型的对象,Django会提醒你。

3.检索对象

要从数据库中检索对象,请在模型类上使用Manager构建一个 QuerySet

QuerySet表示数据库中的对象集合。它可以有零个,一个或多个过滤器。过滤器根据给定的参数缩小查询结果范围。在SQL术语中,一个QuerySet就是SELECT语句,过滤器是限制子句,如WHERELIMIT

你可以使用模型中的Manager获得QuerySet。每个模型至少有一个 Manager,默认情况下调用 objects。通过模型类直接访问它,如下所示:在这里插入图片描述

注解
Managers 只能通过模型​​类访问而不是模型实例访问,以强制“表级”操作和“记录级”操作之间的分离。↑

这Manager模型是QuerySets的主要来源。例如,Blog.objects.all()返回的QuerySet包含Blog数据库中所有的对象。

3.1 检索所有对象

从表中检索对象的最简单方法是获取所有这些对象。为此,请使用 Manager中的all()方法:
在这里插入图片描述
all()方法返回数据库中所有Entry对象的一个 QuerySet集合​​。

3.2 使用过滤器检索特定对象

通过 all()返回的QuerySet描述数据库中的表中的所有对象。但是,通常,您只需要选择整个对象集的子集。

要创建此类子集,请优化初始的QuerySet,为其添加过滤条件。两种最常见的改进方法QuerySet是:

  • **filter(kwargs)
    返回QuerySet包含与给定查找参数匹配的新对象。
  • **exclude(kwargs)
    返回QuerySet包含与给定查找参数不匹配的新对象。
    查找参数(**kwargs在上面的函数定义中)应采用下面的字段查找中描述的格式。

例如,要获取2006年的博客条目的QuerySet,请使用下面的filter():
在这里插入图片描述
使用默认的manager类,它与以下内容相同:
在这里插入图片描述

3.2.1 过滤器链

QuerySet经过过滤器细化后还是一个QuerySet,因此可以将过滤器链接在一起。例如:
在这里插入图片描述
初始的QuerySet是数据库中所有Entry对象,添加filter,然后exclude,然后是另一个filter。最终结果是QuerySet包含标题以“What”开头的所有条目,这些条目是在2005年1月30日和当天之间发布的。

3.2.2 使用过滤器的QuerySets是唯一的

每当你细化一个QuerySet,你就会得到一个全新的QuerySet,与以前的QuerySet没有任何关系。每个细化都会创建一个独立且不同的QuerySet,可以存储,使用和重用。
在这里插入图片描述
这三者QuerySets是分开的。第一个是QuerySet包含所有条目的, 其中包含以“What”开头的标题。第二个是第一个的子集,exclude排除了pub_date为今天以后的记录。第三个是第一个的子集,filter选择了pub_date为今天以后的记录。初始的QuerySet(q1)不受细化过程的影响。

3.2.3 QuerySets是惰性的

QuerySets是懒惰的 - 创建一个 QuerySet不涉及任何数据库活动的行为。可以一直叠加过滤器,Django将不实际运行查询,直到QuerySet被 计算。看看这个例子:
在这里插入图片描述
虽然这看起来像访问数据库3次,但事实上它访问数据库中一次,在最后一行(print(q))。通常,在QuerySet在“询问”它们之前,不会从数据库中获取结果 。执行此操作时,QuerySet通过访问数据库来计算 。有关何时进行计算的更多详细信息,请参阅 何时QuerySe进行计算

3.3 用get()检索单个对象

filter()总会给你一个QuerySet,即使只有一个对象与查询匹配 - 在这种情况下,QuerySet包含一个元素。

如果您知道只有一个对象与您的查询匹配,则可以使用 Manager直接返回对象的 get()方法:
在这里插入图片描述
您可以在get()中使用任何查询表达式 ,就像使用 filter()一样- ,请参阅下面的字段查找部分。

请注意,使用get()和使用 filter()对于切片 [0]之间存在差异。如果没有与查询匹配的结果, get()则会引发DoesNotExist异常。此异常是正在执行查询的模型类的属性 - 因此在上面的代码中,如果没有Entry主键为1的对象,Django将引发Entry.DoesNotExist错误。
在这里插入图片描述
同样,如果多个项目与get()查询匹配,Django会报错 。在这种情况下,它会引发 MultipleObjectsReturned错误,这也是模型类本身的属性。
在这里插入图片描述

3.4 其他QuerySet方法

在大多数情况下,你会使用all(), get(), filter()和 exclude()从数据库中查找对象。然而,这远非一切; 有关所有QuerySet各种方法的完整列表,请参阅 QuerySet API参考

3.5 limit QuerySet

使用Python的数组切片语法的子集将限制 QuerySet为一定数量的结果。这相当于SQL中的LIMITOFFSET子句。

例如,这将返回前5个对象(等价于 LIMIT 5):
Entry.objects.all()[:5]
这将返回第六个到第十个对象(OFFSET 5 LIMIT 5):
Entry.objects.all()[5:10]

不支持负下标(例如 Entry.objects.all()[-1])。

通常,切片QuerySet返回一个新的 QuerySet- 它不会再次查询。例外情况是,如果您使用Python切片语法的“step”参数。例如,这实际上会执行查询以返回前10 个中每隔一个对象的列表:
Entry.objects.all()[:10:2]
禁止对切片查询集进行进一步过滤或排序,因为它可能有多种模糊性。

要检索单个对象而不是列表(例如SELECT foo FROM bar LIMIT 1),请使用简单索引而不是切片。例如,在按标题字母顺序排序条目之后,这将返回数据库中的第一个Entry:
Entry.objects.order_by('headline')[0]
这大致相当于:
Entry.objects.order_by('headline')[0:1].get()

但请注意,如果没有对象符合给定条件,则第一个将报IndexError错误,而第二个将报DoesNotExist错误。请参阅有关get()详细信息。

3.6 字段查找

字段查找就等同于指定SQL中的WHERE子句。它们被指定为QuerySet 方法filter(), exclude()和 get()的关键字参数。

基本查找关键字参数采用这种形式field__lookuptype=value。(这是一个双重下划线)。例如:
Entry.objects.filter(pub_date__lte='2006-01-01')
将(粗略地)翻译成以下SQL语句:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

查找中指定的字段必须是模型字段的名称。但有一个例外,如果是ForeignKey你可以为字段名称指定后缀的_id。在这种情况下,value参数应包含外部模型主键的原始值。例如:
Entry.objects.filter(blog_id=4)

如果传递无效的关键字参数,则会引发查找函数 TypeError。

数据库API支持大约24种查找类型; 可以在 字段查找参考 中找到完整的参考。为了让您了解可用的内容,以下是您可能会使用的一些常见查找:

exact
“精确”匹配。例如:
Entry.objects.get(headline__exact="Cat bites dog")
将生成这样的SQL:
SELECT ... WHERE headline = 'Cat bites dog';

如果您不提供查找类型 - 也就是说,如果您的关键字参数不包含双下划线 - 则假定查找类型为 exact。
例如,以下两个语句是等效的:
在这里插入图片描述
这是为了方便,因为exact查找是常见的情况。

iexact
不区分大小写的匹配项。所以,查询:
Blog.objects.get(name__iexact="beatles blog")
将会匹配标题为Beatles Blog, beatles blog 甚至 BeAtlES blOG.

contains
区分大小写的模糊匹配。例如:
Entry.objects.get(headline__contains='Lennon')
SQL大概是这样的:
SELECT ... WHERE headline LIKE '%Lennon%';

请注意,这将与标题Today Lennon honored匹配,但不与today lennon honored匹配。

还有一个不区分大小写的版本icontains

startswith endswith
分别以xx开始和以xx结束。还有一些不区分大小写的版本叫做istartswithiendswith
这几个方法只是冰山一角。可以在 字段查找参考 中找到完整的 参考。

3.7 跨关系查找

Django提供了一种强大而直观的方式来“跟踪”查找中的关系,在后台自动为您处理SQL 中的JOIN。要跨越关系,只需使用跨模型的相关字段的字段名称,用双下划线分隔,直到到达所需的字段。

这个例子检索所有Entry与对象Blog,其name 为:‘Beatles Blog’
Entry.objects.filter(blog__name='Beatles Blog')

这种跨越可以像你想的那样深。
它也可以反向。要引用“反向”关系,只需使用模型的小写名称即可。

这个例子检索所有Blog具有至少一个对象Entry ,其headline包含’Lennon’:
Blog.objects.filter(entry__headline__contains='Lennon')

如果您要跨多个关系进行过滤,并且其中一个中间模型没有满足过滤条件的值,Django会将其视为存在空(所有值都是NULL),但有效,对象存在。所有这些意味着不会引发任何错误。例如,在此过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果存在相关Author模型),如果没有author 与entry相关联,则将其视为没有name ,而不是因为缺失author而引发错误。通常这正是你想要发生的事情。可能令人困惑的唯一情况是你正在使用 isnull。例如:
Blog.objects.filter(entry__authors__name__isnull=True)

将返回有一个空的Blog对象,其中entry的author为空或者 author上的name为空。如果你不想要author为空,你可以写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

3.7.1 跨越多值关系

当您基于ManyToManyField过滤或反向ForeignKey,您可能会对两种不同类型的过滤器感兴趣。请考虑Blog/ Entry(Blog与Entry一对多关系)。我们可能有兴趣找到一个博客,其一个entry的标题中包含“Lennon”并于2008年发布。或者我们可能希望找到一个entry的标题中带有“Lennon”的博客 以及一个entry的发布日期是2008年,这些查询都是可能的,并且在某些情况下有意义。

同样的情况出现于 ManyToManyField。例如,如果Entry有一个tags属性是ManyToManyField类型,我们可能想要找到一个entry链接到名为“music”和“bands”的tag,或者我们可能想要一个包含名称为“music”且状态为“public”的entry。

为了处理这两种情况,Django有一种一致的处理 filter()调用方式。单个filter()中的所有内容同时应用以过滤掉符合所有这些要求的项目。连续 filter()调用进一步限制了对象集,但对于多值关系,它们适用于链接到主模型的任何对象,而不一定是先前filter()调用选择的那些对象。

这可能听起来有点令人困惑,所以希望一个例子可以澄清。要选择所有entry标题中包含“Lennon”且2008年发布的条目的博客(同一entry满足两个条件),我们会写:
在这里插入图片描述
要选择entry的标题 中包含“Lennon”的所有博客以及 2008年发布的entry,我们会写:
在这里插入图片描述
假设只有一个博客的两个条目都包含“Lennon”和2008年的条目,但是2008年的条目都没有包含“Lennon”。第一个查询不会返回任何博客,但第二个查询将返回该博客。

在第二个示例中,第一个过滤器将查询集限制为链接到标题中带有“Lennon”的条目的所有博客。第二个过滤器将博客集进一步限制为也链接到2008年发布的条目的博客。第二个过滤器选择的条目可能与第一个过滤器中的条目相同或不同。我们Blog使用每个过滤器语句过滤 项目,而不是Entry项目。

注解
如上所述,跨越多值关系的filter()查询的行为不是等效实现的exclude()。相反,单个exclude() 调用中的条件不一定是指同一个项目。

例如,下面的查询将排除博客包含“Lennon”的标题,并在2008年出版的entry:
在这里插入图片描述
但是,与使用filter()时的行为不同 ,这不会限制基于满足这两个条件的条目的博客。为了做到这一点,即选择所有不包含 2008年发布的“Lennon”条目的博客,您需要进行两次查询:
在这里插入图片描述

3.8 过滤器可以引用模型上的字段

在到目前为止给出的例子中,我们构造了过滤器,用于比较模型字段的值和常量。但是,如果要将模型字段的值与同一模型上的另一个字段进行比较,该怎么办?

Django 允许使用 F函数 进行这样的比较。作为查询中模型字段的引用的F()实例。然后,可以在查询过滤器中使用这些引用来比较同一模型实例上的两个不同字段的值。

例如,要查找comment比pingback多的所有博客条目的列表,我们构造一个F()对象来引用pingback的计数,并F()在查询中使用该对象:
在这里插入图片描述
Django支持对对象使用F()进行加法,减法,乘法,除法,模运算和幂运算,包括常量和其他F()对象。要查找所有博客条目的comment数量是pingback的两倍以上 ,我们会修改查询:
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

要查找条目评级小于pingback计数和评论计数总和的所有条目,我们将发出查询:
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

您还可以在F()中使用双下划线表示法来跨越对象查询。F()具有双下划线的对象将引入访问相关对象所需的任何连接。例如,要检索作者姓名与博客名称相同的所有条目,我们可以发出查询:
Entry.objects.filter(authors__name=F('blog__name')

对于日期和日期/时间字段,您可以添加或减去 timedelta对象。以下内容将返回在发布后超过3天内修改的所有条目:
在这里插入图片描述
F()对象通过.bitand() .bitor() .bitrightshift() .bitleftshift()这些方法支持支持位运算。例如:
F('somefield').bitand(16)

3.9 使用pk快速查找

为方便起见,Django提供了一个pk快捷查找方式,代表“主键”。
在示例Blog模型中,主键是id字段,因此这三个语句是等效的:
在这里插入图片描述
使用pk不仅限于__exact查询 - 可以组合任何查询术语以pk对模型的主键执行查询:
在这里插入图片描述
pk查找也适用于连接。例如,这三个语句是等价的:
在这里插入图片描述

3.10 在LIKE语句中转义百分号和下划线

类似于SQL语句中的LIKE(如 iexact, contains,icontains,startswith,istartswith,endswith 和iendswith)将自动转义LIKE语句使用的两个特殊字符 -百分号和下划线。(在一个LIKE 语句中,百分号表示多字符通配符,下划线表示单字符通配符。)

例如,要检索包含百分号的所有条目,只需像使用任何其他字符那样使用百分号:
在这里插入图片描述
Django负责为你转义; 生成的SQL看起来像这样:
在这里插入图片描述
下划线也是如此。百分号和下划线都是透明处理的。

3.11 缓存和QuerySets

每个QuerySet都包含一个缓存,以最小化数据库访问。 了解它的工作原理将允许您编写最有效的代码。

在新创建的QuerySet中,缓存为空。第一次QuerySet被计算 - 发生一次数据库查询 - Django将查询结果保存在QuerySet缓存中并返回已明确请求的结果(例如,QuerySet正在迭代的下一个元素)。随后对QuerySet重用缓存结果的评估。

记住这种缓存行为,因为如果你没有正确使用QuerySet ,它可能会扰乱你。例如,以下将创建两个QuerySets,对它们进行计算,并抛弃它们:
在这里插入图片描述
这意味着相同的数据库查询将执行两次,使数据库负载加倍。此外,两个列表可能不包含相同的数据库记录,因为Entry可能已在两个请求之间的时间内中添加或删除了。

要避免此问题,只需保存 QuerySet并重复使用它:
在这里插入图片描述

3.11.1 什么时候QuerySets不缓存

查询集并不总是缓存其结果。仅计算部分查询集时,将检查缓存,但如果未填充,则不会缓存后续查询返回的项目。具体而言,这意味着, 限制所述查询集使用阵列切片或索引不会填充缓存。

例如,在queryset对象中重复获取某个索引将每次查询数据库:
在这里插入图片描述
但是,如果已经计算了整个查询集,则将检查缓存:
在这里插入图片描述
以下是一些其他操作的示例,这些操作将导致整个查询集被计算,从而填充缓存:
在这里插入图片描述

4.使用Q对象进行复杂查找

多个关键字查找参数 - 例如在filter()方法中 - 是用“AND关系”连接在一起。如果需要执行更复杂的查询(例如,带OR语句的查询),则可以使用Q 对象

Q 对象(django.db.models.Q)是用于封装关键字参数集合的对象。这些关键字参数在上面的“字段查找”中指定。

例如,此Q对象封装了一个LIKE查询:

from django.db.models import Q
Q(question__startswith='What')

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

例如,此语句生成一个Q表示两个"question__startswith"查询的或:

Q(question__startswith='Who') | Q(question__startswith='What')

这相当于以下SQL WHERE子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

您可以通过&|操作符合并组成任意复杂的Q对象,并使用括号分组。此外,Q 可以使用~运算符排除对象,允许组合同时结合普通查询和~查询:

def fun01_q():
    from django.db.models import Q
    all_book = Book.objects.all()
    print(all_book)
    print(all_book.filter(~Q(name__contains='喊')))
    print(all_book.filter(Q(name__contains='喊')|~Q(price__gt=20)))

if __name__ ==  "__main__":
    fun01_q()
# 输出结果
<QuerySet [<Book: <呐喊>>, <Book: <哈利波特>>, <Book: <灭亡>>]>
<QuerySet [<Book: <哈利波特>>, <Book: <灭亡>>]>
<QuerySet [<Book: <呐喊>>, <Book: <灭亡>>]>

每个查找函数(例如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对象,则它必须位于任何关键字参数的定义之前。例如:

print(all_book.filter(
Q(name__contains='喊') | ~Q(price__gt=20),
name__contains='亡')
))
# 输出结果
<QuerySet [<Book: <灭亡>>]>

但是,调换位置之后,会报错:

print(all_book.filter(
name__contains='亡',
Q(name__contains='喊') | ~Q(price__gt=20)
))
# 输出结果
 File "F:/zzzzz-wang/dj-proj/leanproj/orm.py", line 18
 print(all_book.filter(name__contains='亡',Q(name__contains='喊') | ~Q(price__gt=20)))
SyntaxError: positional argument follows keyword argument

Django单元测试中的OR查找示例显示了一些可能的用法Q。

5. 比较对象

要比较两个模型实例,只需使用标准的Python比较运算符,即双等号:==。在幕后,比较两个模型的主键值。

使用Entry上面的示例,以下两个print中的语句是等效的:

def fun02_compare_obj():
    b1 = Book.objects.filter(name__contains='喊').first()
    b2 = Book.objects.filter(name__contains='呐').first()
    print(b1 == b2)
    print(b1.id == b2.id)
if __name__ ==  "__main__":
    fun02_compare_obj()
# 输出结果
True
True

如果未调用模型的主键id,也没问题,因为无论你使用什么来比较,将始终使用主键。例如,如果调用模型的主键字段name,这两个语句是等效的:

b1 == b2
b1.name == b2.name

6. 删除对象

删除方法为 delete()。此方法立即删除对象并返回已删除的对象数量以及包含每个对象类型的删除数的字典。例如(删掉书籍《呐喊》):

>>>b1 = Book.objects.filter(name="呐喊")
>>>b1.delete()
(1, {'model_layer.Book_authors': 0, 'model_layer.Book': 1})

您也可以批量删除对象。每个QuerySet都有一个 delete()方法,用于删除QuerySet中的所有成员。

例如,这会删除价格少于25块钱的书籍:

def fun03_delete_obj():
    all_book = Book.objects.all()
    print(all_book)
    print(all_book.filter(price__lt=25).delete())

if __name__ ==  "__main__":
    fun03_delete_obj()
# 输出结果
<QuerySet [<Book: <呐喊>>, <Book: <哈利波特>>, <Book: <灭亡>>]>
(2, {'model_layer.Book_authors': 0, 'model_layer.Book': 2})

请记住,只要有可能,这将完全在SQL中执行,所以在此过程中不一定会调用单个对象实例的delete()方法。如果您在模型类上提供了自定义delete()方法并希望确保调用它,则需要“手动”删除该模型的实例(例如,通过迭代 QuerySet并单独调用每个对象的delete())而不是使用 QuerySet的批量 delete()方法。

当Django删除一个对象时,默认情况下它会模拟SQL约束的行为ON DELETE CASCADE (级联删除)- 换句话说,任何具有指向要删除的对象的外键的对象都将随之删除。例如,我们在Book表中定义它的一个外键是Publish,属性是on_delete=models.CASCADE这意味着,如果一个出版社对象被删除,与之关联的书籍就会全部删除:

def fun03_delete_obj_cascade():
    p = Publish.objects.get(pk=1)
    print(p.delete())

if __name__ ==  "__main__":
    fun03_delete_obj_cascade()
# 输出结果
(3, {'model_layer.Book_authors': 0, 'model_layer.Book': 2, 'model_layer.Publish': 1})

这种级联行为可以通过 ForeignKey字段中的on_delete参数自定义(不一定要删除,也可能什么都不做)。

请注意,delete()是唯一没有在Manager上公开的QuerySet方法 。这是一种安全机制,可以防止您意外请求Entry.objects.delete()删除所有条目。如果您确实要删除所有对象,则必须显式请求完整的查询集,例如:
Entry.objects.all().delete()

7. 复制模型实例

虽然没有用于复制模型实例的内置方法,但可以轻松创建复制了所有字段值的新实例。在最简单的情况下,您可以设置pk为None。使用我们的博客示例:

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

blog.pk = None
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

由于继承的工作原理,您必须将both pk和都设置id为None:

django_blog.pk = None
django_blog.id = None
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.save()
entry.authors.set(old_authors)

对于OneToOneField字段,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。例如,假设entry已经如上所述:

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

8. 一次更新多个对象

有时您希望将QuerySet中所有对象字段设置为的特定值。您可以使用update()方法执行此操作 。例如:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

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

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

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

>>> b = Blog.objects.get(pk=1)

# Update all the headlines belonging to this Blog.
>>> Entry.objects.select_related().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()

调用更新方法还可以F 对象。这对于根据计数器的当前值递增计数器特别有用。例如,要增加博客中每个条目的pingback计数:

Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

但是,与filter和exclude子句中的F()对象不同,在更新中使用F()对象时不能引入连接- 您只能引用要更新的模型的本地字段。如果您尝试引入与F()对象的连接,则将引发FieldError:

# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))

9.关联对象

当您在模型中定义关系(即 ForeignKey, OneToOneField或 ManyToManyField)时,该模型的实例将具有方便API来访问关联对象。

例如,一个书籍Book对象b可以获得和它关联的出版社Publish的对象p。

def fun04_rel():
    b = Book.objects.filter(name="呐喊").first()
    print(b.publishs)
# 输出结果
机械工业

(在幕后,这个功能是由Python 描述符实现的。这对你来说并不重要,但我们在这里指出。)

Django还为关系的“另一方”方创建API方法 - 从被关联模型到定义关系的模型的链接。例如,Publish对象p可以通过以下book_set属性访问所有相关书籍对象的列表 :p.book_set.all()。

>>>p = Publish.objects.get(pk=1)
>>>print(p.book_set.all())
# 输出结果
<QuerySet [<Book: <呐喊>>, <Book: <灭亡>>]>

9.1 一对多关系

9.1.1 正向

如果模型具有ForeignKey字段,则该模型的实例将通过模型的简单属性访问相关(外部)对象。

举例:

def fun04_rel():
    b = Book.objects.filter(name="呐喊").first()
    print(b.publishs)

您可以获取和设置外键属性。正如您所料,在您调用save()之前,外键的更改不会保存到数据库中 。例:

def fun04_rel_fk_f():
    p2 = Publish.objects.get(pk=2)
    b = Book.objects.filter(name="呐喊").first()
    print(b.publishs)
    b.publishs = p2
	b.save()
    print(b.publishs)

# 输出结果
机械工业
南方出版社

如果ForeignKey字段已设置null=True(即,它允许存储NULL值),则可以设置None来删除关系。例:

def fun04_rel_fk_f():
    b = Book.objects.filter(name="呐喊").first()
    print(b.publishs)
    b.publishs = None
    b.save()
    print(b.publishs)
# 输出结果
南方出版社
None

在这里插入图片描述
对于一对多关系的正向查询,在第一次访问关联对象时,将缓存结果。对同一关联对象实例上的后续访问将使用缓存。例:

>>> b = Book.objects.get(id=2)
>>> print(b.publishs)  # Hits the database to retrieve the associated Blog.
>>> print(b.publishs)  # Doesn't hit the database; uses cached version.

请注意,QuerySet的select_related()方法提前填充所有一对多关系的缓存。例:

>>> b = Book.objects.select_related().get(id=2)
>>> print(b.publishs)  # Doesn't hit the database; uses cached version.
>>> print(b.publishs)  # Doesn't hit the database; uses cached version.

9.1.2 反向

从Publish对象p查询所有的Book对象,这个过程与正向查询相反,所以叫反向,查询的方法是通过一个Manager对象FOO_set,其中FOO是Book名称的小写book。
举例:

>>>p = Publish.objects.get(pk=1)
>>>print(p.book_set.all())
>>>>print(p.book_set.filter(name__contains="喊"))
>>>print(p.book_set.count())
# 输出结果
<QuerySet [<Book: <呐喊>>, <Book: <灭亡>>]>
<QuerySet [<Book: <呐喊>>]>
2

您可以通过在ForeignKey定义中设置related_name参数 来覆盖FOO_set名称 。例如,如果Book 模型被更改为

publishs = models.ForeignKey(to="Publish",on_delete=models.CASCADE,null=True,related_name="books")

上面的示例代码将可以按如下改写:

>>>p = Publish.objects.get(pk=1)
>>>print(p.books.all())
>>>>print(p.books.filter(name__contains="喊"))
>>>print(p.books.count())
# 输出结果
<QuerySet [<Book: <呐喊>>, <Book: <灭亡>>]>
<QuerySet [<Book: <呐喊>>]>
2

9.1.3 使用自定义的反向管理器

默认情况下,RelatedManager用于反向关系,它是默认管理器的子类。如果要为给定查询指定其他管理器,可以使用以下语法:

from django.db import models
class Entry(models.Model):
    objects = models.Manager()  # Default Manager
    entries = EntryManager()    # Custom Manager

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

如果EntryManager在其get_queryset() 方法中执行默认过滤,则该过滤将应用于all()的调用。
当然,指定自定义反向管理器还可以调用其自定义方法:

class BookManager(models.Manager):
    def is_all_price_gt_20(self):
        flag = False
        for b in self.all():
            if b.price < 20:
                return False
        return True
        
class Book(models.Model):
	# ...
    objects = models.Manager()
    bm = BookManager()
    
def fun04_rel_fk_manager_f():
    p = Publish.objects.get(pk=1)
    print(p.books(manager='bm').is_all_price_gt_20())
# 输出结果
True

9.1.4 处理关联对象的其他方法

除了上面“检索对象”中定义的QuerySet方法之外,ForeignKey Manager还有用于处理关联对象集的其他方法。每个方法的概要如下,完整的细节可以在关联对象参考中找到。

add(obj1, obj2, …)
将特定的模型对象加入关联对象集合。
**create(kwargs)
创建一个新对象,保存它并将其放入关联的对象集中。返回新创建的对象。
remove(obj1, obj2, …)
从关联对象集中删除指定的模型对象。
clear()
从关联对象集中删除所有对象。
set(objs)
替换关联对象集。
要分配关联集的成员,请将set()方法与可迭代的对象实例一起使用。例如,如果 b1和b2是Book实例:

p = Publish.objects.get(id=1)
p.book_set.set([b1, b2])

如果clear()方法可用,在迭代器中所有对象(在本例中为列表)添加到集合之前将从book_set中删除任何预先存在的对象。如果clear()方法不可 用,则将添加迭代器中的所有对象,而不删除任何现有元素。(不太懂

本节中描述的每个“反向”操作都会立即对数据库产生影响。每次添加,创建和删除都会立即自动保存到数据库中。

9.2 多对多关系

多对多关系的两端都可以通过API方法访问另一端。API的工作方式类似于上面一对多关系的反向查询。

一个区别在于属性命名:定义ManyToManyField的模型 使用该字段本身的属性名称,而“反向”模型使用原始模型的小写模型名称,加上’_set’(就像反向一对多关系一样) 。

一个例子使这更容易理解:
首先,我在Book表中增加一本书,其作者为鲁迅和巴金:
在这里插入图片描述
其次,我们在Book模型中定义了ManyToManyField字段指向Author,
针对两端的查询:

def fun04_rel_mtm():
    b = Book.objects.get(id=4)
    print(b.authors.all())
    print(b.authors.count())
    author = b.authors.filter(name__contains='鲁').first()
    print(author)
    print(author.book_set.all())
# 输出结果 
<QuerySet [<Author: 鲁迅>, <Author: 巴金>]>
2
鲁迅
<QuerySet [<Book: <呐喊>>, <Book: <中国文人集>>]>

像ForeignKey一样, ManyToManyField可以指定 related_name。
和一对多的关系的另一个区别是,除了模型实例,多对多关系中的add(),set()和remove()方法接受主键值。例如,如果b1和b2是 Book实例,那么这些set()调用相同的工作:

a = Author.objects.get(id=1)
a.book_set.set([eb1, b2])
a.book_set.set([b1.pk, b2.pk])

9.3 一对一关系

一对一关系与多对一关系非常相似。如果在模型上定义了OneToOneField ,则该模型的实例将通过模型的简单属性访问相关对象。

例如:

def fun04_rel_oto():
    a = AuthorDetail.objects.get(id=1)
    print(a.author)
# 输出结果
鲁迅

不同之处在于“反向”查询。被关联的模型也可以访问Manager对象,但Manager表示单个对象,而不是对象的集合:

al = Author.objects.get(id=1)
print(al.authordetail.address)
# 输出结果
china

如果没有为此关系分配任何对象,将引发DoesNotExist异常。
可以使用与分配正向关系相同的方式将实例分配给反向关系:
e.entrydetail = ed

10. 反向关系是如何实现的

其他对象-关系映射器要求您在两侧定义关系。Django开发人员认为这违反了DRY(不要重复自己)原则,所以Django只要求你在一端定义关系。

但是,如果模型类在加载其他模型类之前不知道哪些其他模型类与之相关,那怎么可能呢?

答案在于app registry。当Django启动时,它会导入INSTALLED_APPS列出的每个应用程序,然后导入每个应用程序中的模块。每当创建新的模型类时,Django都会向任何相关模型添加向后关系。如果还没有导入相关模型,Django会记录关系并在最终导入相关模型时添加它们。

因此,特别重要的是,您使用的所有模型都要在INSTALLED_APPS列出的应用程序中定义。否则,反向关系可能无法正常工作。

11. 对相关对象的查询

涉及相关对象的查询遵循与涉及正常值字段的查询相同的规则。指定要匹配的查询的值时,可以使用对象实例本身或对象的主键值。

举例来说,如果你有一个博客的对象b有id=5,以下三种查询是相同的:

Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly

12. 使用原始SQL

如果你发现自己需要编写一个对于Django的数据库映射器来说太复杂的SQL查询来处理,你可以依靠手工编写SQL。Django有几个编写原始SQL查询的选项; 请参阅 执行原始SQL查询

最后,重要的是要注意Django数据库层只是数据库的接口。您可以通过其他工具,编程语言或数据库框架访问您的数据库。

猜你喜欢

转载自blog.csdn.net/lengfengyuyu/article/details/84199739