【Django文档转译】第2章:模型层——第2节:迁移(模块6: 迁移概述)

网友投稿 883 2022-11-06

【Django文档转译】第2章:模型层——第2节:迁移(模块6: 迁移概述)

【Django文档转译】第2章:模型层——第2节:迁移(模块6: 迁移概述)

进行查询¶

一旦你创造了你的数据模型,Django自动为您提供一个数据库抽象API,允许您创建、检索、更新和删除对象。本文档解释如何使用此API。参考数据模型参考有关所有模型查找选项的详细信息。

通过本指南(以及参考资料),我们将参考以下模型,其中包括一个Weblog应用程序:

from django.db import modelsclass Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.nameclass Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField() def __str__(self): return self.nameclass 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() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): return self.headline

创建对象¶

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

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

假设模型存在于文件中mysite/blog/models.py,下面是一个例子:

>>> from blog.models import Blog>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')>>> b.save()

这将执行一个INSERT后台的SQL语句。Django在显式调用之前不会访问数据库save().

这个save()方法没有返回值。

参见

save()采取了许多高级选项,这里没有描述。请参阅save()获得完整的细节。

若要在单个步骤中创建和保存对象,请使用create()方法。

保存对对象的更改¶

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

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

>>> b5.name = 'New name'>>> b5.save()

这将执行一个UPDATE后台的SQL语句。Django在显式调用之前不会访问数据库save().

储蓄ForeignKey和ManyToManyField¶

更新ForeignKey字段的工作方式与保存普通字段的方式完全相同–只需将正确类型的对象分配给所讨论的字段。此示例更新blog属性Entry实例entry的适当实例Entry和Blog已经保存到数据库中(因此我们可以在下面检索它们):

>>> from blog.models import Blog, Entry>>> entry = Entry.objects.get(pk=1)>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")>>> entry.blog = cheese_blog>>> entry.save()

更新ManyToManyField工作方式有点不同–使用add()方法将记录添加到关系中。此示例添加Author实例joe到entry目的:

>>> from blog.models import Author>>> joe = Author.objects.create(name="Joe")>>> 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")>>> entry.authors.add(john, paul, george, ringo)

如果您试图分配或添加错误类型的对象,Django将发出抱怨。

检索对象¶

若要从数据库中检索对象,请构造QuerySet通过Manager在你的模特课上。

A QuerySet表示数据库中对象的集合。它可以有零,一个或多个滤光片…过滤器根据给定的参数缩小查询结果。在SQL术语中,QuerySet等于SELECT语句,筛选器是一个限制子句,如WHERE或LIMIT.

你得到一个QuerySet通过使用你的模型Manager…每个型号至少有一个Manager,它叫做objects默认情况下。通过模型类直接访问它,如下所示:

>>> Blog.objects>>> b = Blog(name='Foo', tagline='Bar')>>> b.objects

Traceback: ...AttributeError: "Manager isn't accessible via Blog instances."

注解

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

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

检索所有对象¶

从表中检索对象的最简单方法是获取所有对象。若要执行此操作,请使用all()方法的Manager:

>>> all_entries = Entry.objects.all()这个all()方法返回QuerySet数据库中的所有对象。

使用筛选器检索特定对象¶

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

要创建这样一个子集,您需要对初始的QuerySet,添加过滤条件。优化QuerySet是:

filter(**kwargs)返回一个新的QuerySet包含匹配给定查找参数的对象。

exclude(**kwargs)返回一个新的QuerySet包含执行以下操作的对象不匹配给定的查找参数。

查找参数(**kwargs在上述函数定义中)应采用字段查找下面。

例如,要获得一个QuerySet2006年的博客条目,请使用filter()就像这样:

Entry.objects.filter(pub_date__year=2006)

对于默认的Manager类,它与以下内容相同:

Entry.objects.all().filter(pub_date__year=2006)

链式过滤器¶

精炼的结果QuerySet本身是一个QuerySet,所以有可能把精细化环节联系在一起。例如:

>>> Entry.objects.filter(... headline__startswith='What'... ).exclude(... pub_date__gte=datetime.date.today()... ).filter(... pub_date__gte=datetime.date(2005, 1, 30)... )

这是QuerySet在数据库中的所有条目中,添加一个筛选器,然后添加一个排除项,然后添加另一个筛选器。最终结果是QuerySet包含以“什么”开头的标题的所有条目,于2005年1月30日至今天发布。

滤过QuerySets是独一无二的¶

每次您细化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包含包含以“什么”开头的标题的所有条目。第二个是第一个的子集,有一个附加的标准,它排除了pub_date无论是今天还是将来。第三个是第一个的子集,有一个附加的条件,它只选择其记录pub_date无论是今天还是将来。初值QuerySet (q1)不受细化过程的影响。

QuerySetS很懒¶

QuerySets是懒惰的–创建一个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被评估时.

检索单个对象get()¶

filter()总会给你一个QuerySet,即使只有一个对象与查询匹配–在本例中,它将是QuerySet包含单一元素的。

如果您知道只有一个对象与查询匹配,则可以使用get()方法的Manager它直接返回对象:

>>> one_entry = Entry.objects.get(pk=1)可以使用任何查询表达式get(),就像filter()-再一次,见字段查找下面。

注意,​​使用get(),并使用filter()带着一片[0]...如果没有匹配查询的结果,get()将引发DoesNotExist例外​​。此异常是正在执行查询的模型类的一个属性-因此,在上面的代码中,如果没有Entry对象的主键为1,Django将引发Entry.DoesNotExist.

类似地,如果多个项匹配get()查询。在这种情况下,它将引发MultipleObjectsReturned,它还是模型类本身的一个属性。

其他QuerySet方法¶ 大部分时间你会用all(

), get(), filter()和exclude()当您需要从数据库中查找对象时。然而,这还远远不够,请参阅QuerySet API引用的完整列表QuerySet方法。

限幅QuerySets¶

使用Python数组切片语法的子集来限制QuerySet一定数量的结果。这相当于SQL的LIMIT和OFFSET条款。

例如,返回前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()更多细节。

字段查找¶

字段查找是如何指定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';

这是怎么可能的

Python能够定义函数,这些函数接受在运行时计算其名称和值的任意名称-值参数。有关更多信息,请参见关键字参数在官方Python教程中。

查找中指定的字段必须是模型字段的名称。但是有一个例外,如果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.

例如,以下两个语句是等价的:

>>> Blog.objects.get(id__exact=14) # Explicit form>>> Blog.objects.get(id=14) # __exact is implied

这是为了方便,因为exact查找是常见的情况。

iexact 不区分大小写的匹配。因此,查询:

>>> Blog.objects.get(name__iexact="beatles blog")

会匹配一个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 分别以搜索开始和结束。还有一些不区分大小写的版本称为istartswith和iendswith. 再说一次,这只会触及表面。完整的引用可以在字段查找参考.

跨越关系的查找¶

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与条目相关联,它将被视为也不存在。name附件,而不是因为失踪而引发错误。author…通常这正是你想要发生的。唯一可能令人困惑的情况是如果您正在使用isnull…因此:

Blog.objects.filter(entry__authors__name__isnull=True)

会回来Blog对象具有空name在author还有那些空荡荡的author在entry…如果你不想要后一个对象,你可以写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值关系¶

当您根据ManyToManyField或者相反ForeignKey,您可能对两种不同类型的过滤器感兴趣。考虑Blog/Entry关系(Blog到Entry是一对多的关系)。我们可能有兴趣找到有两个条目的博客“列侬”并于2008年出版。或者我们可能希望找到有一个条目的博客“列侬”以及2008年出版的一篇文章。因为有多个条目与单个Blog,这两个查询都是可能的,在某些情况下也是有意义的。

同样的情况出现在ManyToManyField…例如,如果Entry有一个ManyToManyField叫tags,我们可能需要找到链接到标记的条目“音乐”和“乐队”或者,我们可能需要一个包含名称为“音乐”和地位“公众”.

为了处理这两种情况,Django有一种一致的处理方法。filter()打电话。一切都在一个单一的filter()调用同时应用于筛选出符合所有这些要求的项。逐次filter()调用进一步限制了对象的集合,但对于多值关系,它们适用于任何链接到主模型的对象,而不一定适用于先前由某个对象选择的对象。filter()打电话。

这可能听起来有点混乱,所以希望有一个例子会澄清。若要选择所有包含这两个条目的博客,请执行以下操作“列侬”在2008年发布的标题(满足这两种条件的相同条目)中,我们会写道:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

若要选择包含以下条目的所有博客,请执行以下操作“列侬”在标题中以及我们在2008年发表的一篇文章中写道:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

假设只有一个博客的两个条目都包含“列侬”以及2008年的条目,但2008年的条目中没有一个包含“列侬”…第一个查询不会返回任何博客,但是第二个查询将返回该博客。

在第二个示例中,第一个筛选器将queryset限制为所有链接到“列侬”在标题里。第二个过滤器限制博客集。进一步也与2008年发布的条目相关联。第二过滤器选择的条目可能与第一过滤器中的条目相同,也可能不相同。我们正在过滤Blog每个筛选器语句的项,而不是Entry物品。

注解

.的行为filter()对于跨多值关系的查询(如上文所述),不能等效地实现exclude()…相反,单个exclude()调用不一定指同一项。

例如,以下查询将排除包含双管齐下条目“列侬”在标题中和2008年发表的条目:

Blog.objects.exclude( entry__headline__contains='Lennon', entry__pub_date__year=2008,)

但是,与使用filter(),这不会限制基于满足这两种条件的条目的博客。为了做到这一点,即选择所有不包含“列侬”在2008年发布的信息中,您需要进行两个查询:

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

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

在给出的例子中,我们构造了滤波器,将模型字段的值与常量进行比较。但是,如果您想将模型字段的值与同一模型上的另一个字段进行比较,怎么办?

Django提供F expressions允许这样的比较。.的实例F()充当查询中模型字段的引用。然后,可以在查询过滤器中使用这些引用来比较同一模型实例中两个不同字段的值。

例如,为了找到比pingback有更多评论的所有博客条目的列表,我们构建了一个​​F()对象引用pingback计数,​​并使用F()对象在查询中:

>>> from django.db.models import F>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django支持使用加法、减法、乘法、除法、模运算和幂运算。F()对象,包括常量和其他F()物品。查找所有包含更多的博客条目两次与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天以上修改的所有条目:

>>> from datetime import timedelta>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

这个F()对象支持按位操作。.bitand(), .bitor(), .bitrightshift(),和.bitleftshift()…例如:

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

这个pk查找捷径¶

为了方便,Django提供了一个pk查找快捷方式,它代表“主键”。

在这个例子中Blog模型,主键是id字段,因此这三个语句是等价的:

>>> Blog.objects.get(id__exact=14) # Explicit form>>> Blog.objects.get(id=14) # __exact is implied>>> Blog.objects.get(pk=14) # pk implies id__exact

使用pk不限于__exact查询–任何查询术语都可以与pk若要对模型的主键执行查询,请执行以下操作:

# Get blogs entries with id 1, 4 and 7>>> Blog.objects.filter(pk__in=[1,4,7])# Get all blog entries with 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

中的符号和下划线。

LIKE陈述¶

字段查找等同于LIKESQL语句(iexact, contains, icontains, startswith, istartswith, endswith和iendswith)将自动转义LIKE语句–百分比符号和下划线。(在LIKE语句中,百分比符号表示多字符通配符,下划线表示单个字符通配符.)

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

>>> Entry.objects.filter(headline__contains='%')

Django为您处理引用;结果的SQL如下所示:

SELECT ... WHERE headline LIKE '%\%%';

下划线也是如此。百分比符号和下划线都是透明地为您处理的。

缓存和QuerySets¶

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

在一个新创造的QuerySet,缓存是空的。第一次QuerySet-Django将查询结果保存在QuerySet的缓存并返回显式请求的结果(例如,下一个元素,如果QuerySet正在被迭代)。的后续评价QuerySet重用缓存的结果。

请记住这种缓存行为,因为如果不使用QuerySet是对的。例如,下面将创建两个QuerySetS,评估它们,然后扔掉它们:

>>> 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]) # Evaluate the query set.>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

什么时候QuerySets不缓存。¶

Queryset并不总是缓存它们的结果。只进行评估时部分在查询集中,将检查缓存,但如果未填充缓存,则不会缓存后续查询返回的项。具体来说,这意味着限制查询集使用数组片或索引不会填充缓存。

例如,每次在queryset对象中重复获取某个索引将查询数据库:

>>> queryset = Entry.objects.all()>>> print(queryset[5]) # Queries the database>>> print(queryset[5]) # Queries the database again

但是,如果已经计算了整个查询集,则将检查缓存:

>>> queryset = Entry.objects.all()>>> [entry for entry in queryset] # Queries the database>>> print(queryset[5]) # Uses cache>>> print(queryset[5]) # Uses cache

下面是一些其他操作的示例,这些操作将导致计算整个查询集,从而填充缓存:

>>> [entry for entry in queryset]>>> bool(queryset)>>> entry in queryset>>> list(queryset)

注解

简单地打印查询集不会填充缓存。这是因为__repr__()只返回整个查询集的一部分。

复杂查找Q对象¶

关键字参数查询–infilter()等等–“是”和“是”在一起的。如果需要执行更复杂的查询(例如,使用OR语句),您可以使用Q objects.

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

例如,这个Q对象封装单个LIKE查询:

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

Q对象可以使用&和|操作员。当一个操作符在两个上使用时Q对象,则生成一个新的Q对象。

例如,该语句生成一个Q对象,该对象表示2的“OR”。"question__startswith"查询:

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

这相当于以下SQLWHERE条款:

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对象参数到查找函数时,参数将被“和”编辑在一起。例如:

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对象)是“和”编辑在一起的。但是,如果Q对象,则必须在定义任何关键字参数之前。例如:

Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who',)

…将是一个有效的查询,相当于前面的示例;但是:

# INVALID QUERYPoll.objects.get( question__startswith='Who', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))...都是无效的。

参见

这个或查找示例在Django单元测试中,显示了Q.

比较对象¶

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

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

>>> some_entry == other_entry>>> some_entry.id == other_entry.id

>>> some_obj == other_obj>>> some_obj.name == other_obj.name

删除对象¶

删除方法被方便地命名为delete()…此方法立即删除该对象,并返回已删除对象的数量和每个对象类型删除次数的字典。例子:

>>> e.delete()(1, {'weblog.Entry': 1})还可以批量删除对象。每个QuerySet有一个delete()方法,删除该方法的所有成员。QuerySet.

例如,这将删除所有Entry对象的pub_date2005年:

>>> Entry.objects.filter(pub_date__year=2005).delete()(5, {'webapp.Entry': 5})

请记住,只要有可能,这将完全在SQL中执行,因此delete()在处理过程中,不一定会调用单个对象实例的方法。如果你提供了一个自定义delete()方法,并希望确保调用它,则需要“手动”删除该模型的实例(例如,通过迭代QuerySet打电话delete()),而不是使用大容量delete()a的方法QuerySet.

当Django删除对象时,默认情况下它模拟sql约束的行为。ON DELETE CASCADE-换言之,任何具有指向要删除对象的外键的对象都将与其一起删除。例如:

b = Blog.objects.get(pk=1)# This will delete the Blog and all of its Entry objects.

b.delete() 此级联行为可通过on_delete对ForeignKey.

请注意delete()是唯一QuerySet方法中未公开的Manager本身。这是防止您意外请求的安全机制。Entry.objects.delete(),以及删除全条目。如果你做要删除所有对象,则必须显式请求完整的查询集:

Entry.objects.all().delete()

复制模型实例¶

尽管没有复制模型实例的内置方法,但是可以轻松地创建所有字段的值复制的新实例。在最简单的情况下,您只需设置pk到None…使用我们的博客示例:

blog = Blog(name='My blog', tagline='Blogging is easy')blog.save() # blog.pk == 1blog.pk = Noneblog.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对任何人而言:

django_blog.pk = Nonedjango_blog.id = Nonedjango_blog.save() # django_blog.pk == 4

此过程不复制不属于模型数据库表一部分的关系。例如,Entry有一个ManyToManyField到Author…复制条目后,必须为新条目设置多到多的关系:

entry = Entry.objects.all()[0] # some previous entryold_authors = entry.authors.all()entry.pk = Noneentry.save()entry.authors.set(old_authors)

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

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

一次更新多个对象¶

有时,您希望将字段设置为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 expressions根据模型中另一个字段的值更新一个字段。这对于根据计数器的当前值递增计数器尤其有用。例如,要增加博客中每个条目的pingback计数:

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

然而,不像F()对象在筛选和排除子句中,不能在使用F()对象–您只能引用正在更新的模型的本地字段。如果尝试使用F()对象,aFieldError将提出:

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

相关对象¶

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

使用此页面顶部的模型,例如,Entry对象e可以得到它的关联Blog对象,通过访问blog属性:e.blog.

(在幕后,此功能由Python实现。描述符…这对你来说并不重要,但我们在这里为好奇的人指出这一点。)

Django还为关系的“另一面”创建API访问器–从相关模型到定义关系的模型的链接。例如,Blog对象b可以访问所有相关的列表。Entry对象通过entry_set属性:​​b.entry_set.all().​​

本节中的所有示例都使用以下示例Blog, Author和Entry在此页面顶部定义的模型。

一对多的关系¶

前移¶ 如果模型具有ForeignKey,该模型的实例将通过模型的一个简单属性访问相关(外部)对象。

举例:

>>> e = Entry.objects.get(id=2)>>> e.blog # Returns the related Blog object.

您可以通过外键属性获取和设置。正如您可能预期的那样,对外键的更改在调用之前不会保存到数据库中。save()…例子:

>>> e = Entry.objects.get(id=2)>>> e.blog = some_blog>>> e.save()

如果ForeignKey字段null=TrueSET(也就是说,它允许NULL),您可以指定None移除关系。例子:

>>> e = Entry.objects.get(id=2)>>> e.blog = None>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

第一次访问相关对象时,将缓存对一对多关系的前向访问。缓存对同一对象实例上外键的后续访问。例子:

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

注意,select_related() QuerySet方法提前递归地预先填充所有一对多关系的缓存。例子:

>>> e = Entry.objects.select_related().get(id=2)>>> print(e.blog) # Doesn't hit the database; uses cached version.>>> print(e.blog) # Doesn't hit the database; uses cached version.

以下关系“向后”¶

如果模型具有ForeignKey,外键模型的实例将访问Manager返回第一个模型的所有实例。默认情况下,此Manager名FOO_set,在哪里FOO源模型名(小写)。这Manager回报QuerySets,如上面的“检索对象”部分所述,可以对其进行过滤和操作。

举例:

>>> b = Blog.objects.get(id=1)>>> b.entry_set.all() # Returns all Entry objects related to Blog.# b.entry_set is a Manager that returns QuerySets.>>> b.entry_set.filter(headline__contains='Lennon')>>> b.entry_set.count()

您可以覆盖FOO_set通过设置related_name参数中的ForeignKey定义。例如,如果Entry模型被更改为blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name=‘entries’),上面的示例代码如下所示:

>>> b = Blog.objects.get(id=1)>>> b.entries.all() # Returns all Entry objects related to Blog.# b.entries is a Manager that returns QuerySets.>>> b.entries.filter(headline__contains='Lennon')>>> b.entries.count()

使用自定义反向管理器¶

默认情况下,RelatedManager用于反向关系的是默认管理器为了那个模型。如果您想为给定的查询指定不同的管理器,可以使用以下语法:

from django.db import modelsclass Entry(models.Model): #... objects = models.Manager() # Default Manager entries = EntryManager() # Custom Managerb = Blog.objects.get(id=1)b.entry_set(manager='entries').all()

如果EntryManager在其get_queryset()方法,则该筛选将应用于all()打电话。

当然,指定自定义反向管理器还使您能够调用其自定义方法:

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

处理相关对象的其他方法¶

除了QuerySet在上面的“检索对象”中定义的方法,ForeignKey Manager具有用于处理相关对象集的其他方法。下面是每一个的概要,完整的详细信息可以在相关对象引用.

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])

如果clear()方法时,任何已存在的对象都将从entry_set在迭代中的所有对象(在本例中是一个列表)之前,都会添加到集合中。如果clear()方法是不可用时,迭代中的所有对象都将在不删除任何现有元素的情况下被添加。

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

多到多的关系¶

多到多关系的两端都可以自动访问另一端的API.API的工作原理类似于上面的“向后”一对多的关系。

一个不同之处在于属性命名:定义ManyToManyField使用该字段本身的属性名,而“反向”模型使用原始模型的低胁迫模型名称,加上’_set’(就像一对多的关系倒转一样)。

一个例子使这一点更容易理解:

e = Entry.objects.get(id=3)e.authors.all() # Returns all Author objects for this Entry.e.authors.count()e.authors.filter(name__contains='John')a = Author.objects.get(id=5)a.entry_set.all() # Returns all Entry objects for this Author.

喜欢ForeignKey, ManyToManyField可以指定related_name…在上面的示例中,如果ManyToManyField在……里面Entry已指明related_name=‘entries’,然后每个Author实例将有一个function entries() { [native code] }属性而不是entry_set.

与一个与多个关系的另一个不同之处在于,除了模型实例之外,add(), set(),和remove()多到多关系上的方法接受主键值。例如,如果e1和e2是Entry实例,那么这些set()以同样的方式称呼工作:

a = Author.objects.get(id=5)a.entry_set.set([e1, e2])a.entry_set.set([e1.pk, e2.pk])

一对一关系¶

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

例如:

class EntryDetail(models.Model): entry = models.OneToOneField(Entry, on_delete=models.CASCADE) details = models.TextField()ed = EntryDetail.objects.get(id=2)ed.entry # Returns the related Entry object.

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

e = Entry.objects.get(id=2)e.entrydetail # returns the related EntryDetail object

如果没有为此关系分配对象,Django将引发DoesNotExist例外。

实例可以按照与前向关系相同的方式分配给反向关系:

e.entrydetail = ed

向后的关系是如何可能的?¶

其他对象-关系映射程序要求您定义双方的关系。Django开发人员认为这违反了Dry(不要重复自己)原则,因此Django只需要在一端定义关系。

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

答案在于app registry…当Django启动时,它将导入INSTALLED_APPS,然后models模块在每个应用程序中。每当创建新的模型类时,Django都会向任何相关模型添加反向关系。如果尚未导入相关模型,Django将跟踪这些关系,并在最终导入相关模型时添加它们。

由于这个原因,在下面列出的应用程序中定义所使用的所有模型尤为重要。INSTALLED_APPS…否则,反向关系可能无法正常运作。

对相关对象的查询¶

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

例如,如果您有一个blog对象b带着id=5,以下三个查询将是相同的:

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

回到原始SQL¶

如果您发现自己需要编写一个对Django的数据库映射程序来说过于复杂的SQL查询,则可以手动编写SQL。Django有几个用于编写原始SQL查询的选项;请参见执行原始SQL查询.

最后,需要注意的是Django数据库层只是数据库的一个接口。您可以通过其他工具、编程语言或数据库框架访问数据库;数据库没有Django特定的内容。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:C# 4.0 新特性之并行运算(Parallel)
下一篇:【Django文档转译】第2章:模型层——第2节:迁移(模块7: 操作参考)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~