一旦你创建了你的 data models ,django自动为您提供一个数据库抽象API,允许您创建、检索、更新和删除对象。本文档解释如何使用此API。参考 data model reference 有关所有各种模型查找选项的完整详细信息。
在本指南(以及参考资料)中,我们将引用以下模型,它们构成了一个博客应用程序:
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
为了在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()
.
给出了一个 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
通过A Manager
在你的模型课上。
A QuerySet
表示数据库中的对象集合。它可以有零个,一个或多个 过滤器 . 过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,A QuerySet
等同于 SELECT
语句,过滤器是限制子句,例如 WHERE
或 LIMIT
.
你会得到一个 QuerySet
通过使用您的模型的 Manager
。每种型号至少有一个 Manager
,它被称为 objects
默认情况下。直接通过模型类访问它,如下所示:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name="Foo", tagline="Bar")
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
备注
Managers
只能通过模型类而不是模型实例来访问,以在“表级”操作和“记录级”操作之间实现分离。
这个 Manager
是 QuerySets
对于一个模型。例如, Blog.objects.all()
返回A QuerySet
包含所有 Blog
数据库中的对象。
从表中检索对象的最简单方法是获取所有对象。为此,请使用 all()
对象上的方法 Manager
:
>>> all_entries = Entry.objects.all()
这个 QuerySet
返回的 all()
描述数据库表中的所有对象。不过,通常只需要选择完整对象集的一个子集。
要创建这样一个子集,需要细化初始值 QuerySet
,添加筛选条件。两种最常用的方法来改进 QuerySet
是:
filter(**kwargs)
返回新的 QuerySet
包含与给定查找参数匹配的对象。
exclude(**kwargs)
返回新的 QuerySet
包含执行此操作的对象 not 匹配给定的查找参数。
查找参数 (**kwargs
在上述函数定义中)应采用 Field lookups 下面。
例如,要获得 QuerySet
在2006年的博客条目中,使用 filter()
像这样::
Entry.objects.filter(pub_date__year=2006)
对于默认的管理器类,它与以下内容相同:
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日至当日发布的标题以“what”开头的所有条目。
QuerySet
是独一无二的¶每次你精炼 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
)不受精化过程的影响。
QuerySet
是懒惰的¶QuerySets
是懒惰的--创造一个 QuerySet
不涉及任何数据库活动。您可以将过滤器堆叠在一起一整天,Django实际上不会运行查询,直到 QuerySet
是 evaluated 。看一下这个例子:
>>> 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 s被评估 .
get()
¶filter()
总会给你一个 QuerySet
,即使只有一个对象与查询匹配-在本例中,它将是 QuerySet
包含单个元素。
如果知道只有一个对象与查询匹配,则可以使用 get()
对象上的方法 Manager
它直接返回对象:
>>> one_entry = Entry.objects.get(pk=1)
可以将任何查询表达式用于 get()
就像和 filter()
再次看到 Field lookups 下面。
注意,使用 get()
并使用 filter()
用一片 [0]
. 如果没有与查询匹配的结果, get()
将提高 DoesNotExist
例外。这个异常是正在执行查询的模型类的一个属性,所以在上面的代码中,如果没有 Entry
主键为1的对象,Django将提升 Entry.DoesNotExist
.
同样,如果多个项目与 get()
查询。在这种情况下,它会上升 MultipleObjectsReturned
这也是模型类本身的一个属性。
QuerySet
方法¶大部分时间你会用到 all()
, get()
, filter()
和 exclude()
当需要从数据库中查找对象时。然而,这还远远不够;看 QuerySet API Reference 要获得所有 QuerySet
方法。
QuerySet
的S¶使用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
--它不对查询求值。一个例外情况是,如果您使用的是PythonSlice语法的“Step”参数。例如,这实际上会执行查询,以便返回每个 second 前10名的对象:
>>> Entry.objects.all()[:10:2]
由于切片查询集的工作方式不明确,因此禁止对其进行进一步筛选或排序。
要检索一个 single 对象而不是列表(例如 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能够定义接受名称和值在运行时计算的任意名称值参数的函数。有关详细信息,请参阅 Keyword Arguments 在官方的python教程中。
查找中指定的字段必须是模型字段的名称。但是有一个例外,在 ForeignKey
可以指定后缀为 _id
. 在这种情况下,value参数应该包含外部模型主键的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果传递的关键字参数无效,则查找函数将引发 TypeError
.
数据库API支持大约24种查找类型;可以在 field lookup reference . 为了让您了解可用的功能,下面是一些您可能会使用的常见查找:
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
.
同样,这只会划伤表面。完整的参考资料可在 field lookup reference .
Django提供了一种强大且直观的方式来“跟踪”查找中的关系,并照顾SQL JOIN
r在幕后自动为您服务。要跨越关系,请使用模型中相关字段的字段名称,用双引号隔开,直到到达所需的字段。
此示例检索所有 Entry
具有 Blog
谁的 name
是 'Beatles Blog'
:
>>> Entry.objects.filter(blog__name="Beatles Blog")
这个跨度可以像你想的那样深。
它也向后工作。虽然 can be customized
,默认情况下,您会使用模型的收件箱名称在查找中引用“反向”关系。
此示例检索所有 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
),对多个属性进行过滤会引发是否要求每个属性在同一相关对象中重合的问题。我们可能会搜索包含2008年词条的博客 “Lennon” 在它的标题中,或者我们可能会搜索只有2008年的条目以及一些较新或较旧的条目的博客 “Lennon” 在它的标题中。
选择包含至少一个2008年条目的所有博客 "Lennon" 在标题中(满足这两个条件的同一条目),我们将写道:
Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)
否则,要执行更有权限的查询,只需使用 some 输入方式为 "Lennon" 在它的标题和 some 从2008年开始,我们会这样写:
Blog.objects.filter(entry__headline__contains="Lennon").filter(
entry__pub_date__year=2008
)
假设只有一个博客的两个条目都包含 "Lennon" 和2008年的条目,但2008年的条目中没有一个包含 "Lennon" 。第一个查询不会返回任何博客,但第二个查询将返回那个博客。(这是因为由第二过滤器选择的条目可能与第一过滤器中的条目相同,也可能不同。我们正在过滤 Blog
项,而不是 Entry
项目。)简而言之,如果每个条件都需要匹配相同的相关对象,那么每个条件都应该包含在一个 filter()
打电话。
备注
当第二个(更允许的)查询链接多个筛选器时,它会对主要模型执行多个联接,从而可能会产生重复项。
>>> from datetime import date
>>> beatles = Blog.objects.create(name="Beatles Blog")
>>> pop = Blog.objects.create(name="Pop Music Blog")
>>> Entry.objects.create(
... blog=beatles,
... headline="New Lennon Biography",
... pub_date=date(2008, 6, 1),
... )
<Entry: New Lennon Biography>
>>> Entry.objects.create(
... blog=beatles,
... headline="New Lennon Biography in Paperback",
... pub_date=date(2009, 6, 1),
... )
<Entry: New Lennon Biography in Paperback>
>>> Entry.objects.create(
... blog=pop,
... headline="Best Albums of 2008",
... pub_date=date(2008, 12, 15),
... )
<Entry: Best Albums of 2008>
>>> Entry.objects.create(
... blog=pop,
... headline="Lennon Would Have Loved Hip Hop",
... pub_date=date(2020, 4, 1),
... )
<Entry: Lennon Would Have Loved Hip Hop>
>>> Blog.objects.filter(
... entry__headline__contains="Lennon",
... entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>]>
>>> Blog.objects.filter(
... entry__headline__contains="Lennon",
... ).filter(
... entry__pub_date__year=2008,
... )
<QuerySet [<Blog: Beatles Blog>, <Blog: Beatles Blog>, <Blog: Pop Music Blog]>
备注
行为 filter()
对于跨越多值关系的查询,如上所述,其实现方式与 exclude()
. 相反,条件是 exclude()
调用不一定引用同一项。
例如,以下查询将排除包含 both 条目与 “列侬” 在标题中 and 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()
对象引用ping back计数,并使用该 F()
查询中的对象:
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))
Django支持使用加法、减法、乘法、除法、模运算和幂运算 F()
对象,既有常量又有其他 F()
物体。要查找所有包含超过 twice 与Pingback一样多的评论,我们修改查询:
>>> Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)
要查找条目的评级小于ping back计数和评论计数之和的所有条目,我们将发出查询:
>>> 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)
甲骨文
Oracle不支持按位异或操作。
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"),
... )
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
声明¶相当于 LIKE
SQL语句 (iexact
, contains
, icontains
, startswith
, istartswith
, endswith
和 iendswith
)将自动转义中使用的两个特殊字符 LIKE
语句——百分号和下划线。(在A中) LIKE
语句中,百分号表示多字符通配符,下划线表示单字符通配符。)
这意味着事情应该凭直觉工作,这样抽象就不会泄露。例如,要检索包含百分号的所有条目,请将百分号用作任何其他字符:
>>> Entry.objects.filter(headline__contains="%")
Django负责为您报价;得到的SQL如下所示:
SELECT ... WHERE headline LIKE '%\%%';
下划线也是如此。百分比符号和下划线都是透明地为您处理的。
QuerySet
的S¶各 QuerySet
包含用于最小化数据库访问的缓存。了解它的工作方式将使您能够编写最有效的代码。
在新创建的 QuerySet
,缓存为空。第一次A QuerySet
被计算——因此,会发生数据库查询——django将查询结果保存在 QuerySet
的缓存并返回已显式请求的结果(例如,下一个元素,如果 QuerySet
正在迭代)。后续评估 QuerySet
重用缓存的结果。
请记住这种缓存行为,因为如果您不使用 QuerySet
S说得对。例如,以下代码将创建两个 QuerySet
S,评估一下,然后扔掉:
>>> 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]) # Reuse the cache from the evaluation.
QuerySet
S未缓存¶查询集并不总是缓存它们的结果。仅在评估时 part 对于查询集,将检查缓存,但如果未填充缓存,则不会缓存后续查询返回的项。具体来说,这意味着 limiting the 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__()
只返回整个查询集的一个切片。
如果您正在编写异步视图或代码,则不能以我们上面描述的方式使用ORM进行查询,因为您不能调用 blocking 来自异步代码的同步代码--它会阻塞事件循环(或者,更有可能的是,Django会注意到并引发 SynchronousOnlyOperation
以阻止这种情况发生)。
幸运的是,您可以使用Django的异步查询API执行许多查询。所有可能被阻止的方法,例如 get()
或 delete()
-有一个异步变量 (aget()
或 adelete()
),当您迭代结果时,您可以使用异步迭代 (async for
)相反。
迭代查询的默认方式-使用 for
-当Django在迭代时加载结果时,将导致在后台阻止数据库查询。要解决此问题,您可以切换到 async for
**
async for entry in Authors.objects.filter(name__startswith="A"):
...
请注意,您还不能执行其他可能遍历查询集的操作,例如包装 list()
以强制其求值(您可以使用 async for
在理解中,如果你想要的话)。
因为 QuerySet
方法,如 filter()
和 exclude()
不要实际运行查询-他们将查询集设置为在迭代时运行-您可以在异步代码中自由使用这些查询集。有关哪些方法可以像这样继续使用以及哪些方法具有异步版本的指南,请阅读下一节。
QuerySet
和管理器方法¶关于管理器和类查询集的几种方法 get()
和 first()
-强制执行查询集,并且正在阻塞。有些,比如 filter()
和 exclude()
,不强制执行,因此可以安全地从异步代码运行。但你该如何区分这两者呢?
你可以四处看看,看看有没有 a
-方法的前缀版本(例如,我们有 aget()
但不是 afilter()
),有一种更符合逻辑的方法--在 QuerySet reference 。
在那里,您将发现QuerySet上的方法分为两个部分:
Methods that return new querysets :这些是非阻塞的,没有异步版本。您可以在任何情况下自由使用这些工具,但请阅读 defer()
和 only()
在你使用它们之前。
Methods that do not return querysets :这些是阻塞版本,并且具有异步版本--每个版本的异步名称都在其文档中注明,尽管我们的标准模式是添加一个 a
前缀。
使用这种区别,您可以计算出何时需要使用异步版本,以及何时不需要。例如,下面是一个有效的异步查询:
user = await User.objects.filter(username=my_input).afirst()
filter()
返回一个查询集,所以在一个异步环境中继续链接它是很好的,而 first()
计算并返回模型实例-因此,我们更改为 afirst()
,并使用 await
在整个表达式的前面,以便以异步友好的方式调用它。
备注
如果您忘记将 await
部分内容,您可能会看到如下错误 "coroutine object has no attribute x" 或 "<coroutine …>" 字符串代替您的模型实例。如果你曾经看到过这些,你就会错过一个 await
某处可以将这种协同程序转化为真正的价值。
交易记录为 not 当前支持异步查询和更新。你会发现尝试使用它会提高 SynchronousOnlyOperation
。
如果您希望使用事务,我们建议您在单独的同步函数中编写ORM代码,然后使用 sync_to_async
-请参见 异步支持 想要更多。
JSONField
¶查找实现不同, 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
None
¶与其他字段一样,存储 None
因为该字段的值会将其存储为SQL NULL
。虽然不推荐,但可以存储JSON标量 null
而不是SQL NULL
通过使用 Value(None, JSONField())
。
无论存储哪种值,当从数据库中检索时,都将使用Python表示法来描述SON纯量 null
与SQL相同 NULL
,即 None
.因此,很难区分它们。
这仅适用于 None
作为该字段的顶级值。如果 None
是在一个 list
或 dict
,它永远会被解释为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>]>
除非您确定您希望使用SQL NULL
价值观,考虑设置 null=False
并为空值提供合适的默认值,例如 default=dict
。
备注
存储SON纯量 null
不违反 null=False
。
要基于给定的字典关键字进行查询,请使用该关键字作为查找名称:
>>> 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>]>
备注
上面给出的查找示例隐式使用 exact
lookup.键、索引和路径转换也可以通过以下链接: icontains
, endswith
, iendswith
, iexact
, regex
, iregex
, startswith
, istartswith
, lt
, lte
, gt
,以及 gte
,以及 遏制和关键查找 。
KT()
表达式¶的键、索引或路径转换的文本值。 JSONField
。中可以使用双下划线表示法 lookup
链接字典键和索引转换。
例如:
>>> from django.db.models.fields.json import KT
>>> Dog.objects.create(
... name="Shep",
... data={
... "owner": {"name": "Bob"},
... "breed": ["collie", "lhasa apso"],
... },
... )
<Dog: Shep>
>>> Dogs.objects.annotate(
... first_breed=KT("data__breed__1"), owner_name=KT("data__owner__name")
... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
<QuerySet [<Dog: Shep>]>
警告
由于任何字符串都可能是SON对象中的键,因此除了下面列出的查找之外的任何查找都将被解释为键查找。没有出现任何错误。要格外小心输入错误,并始终按您的意愿检查您的查询是否有效。
MariaDB和Oracle用户
vbl.使用 order_by()
键、索引或路径转换将使用值的字符串表示对对象进行排序。这是因为MariaDB和Oracle数据库不提供将SON值转换为等效SQL值的函数。
Oracle用户
在Oracle数据库上,使用 None
作为中的查找值 exclude()
查询将返回不具有 null
作为给定路径上的值,包括没有该路径的对象。在其他数据库后台,查询将返回具有该路径而值没有的对象 null
。
PostgreSQL用户
在PostgreSQL上,如果仅使用一个键或索引,则SQL操作符 ->
采用了如果使用多个操作符,则 #>
使用操作员。
SQLite用户
在SQLite上, "true"
, "false"
,以及 "null"
字符串值将始终被解释为 True
, False
和JSON null
分别进行了分析。
contains
¶这个 contains
上的查找被覆盖 JSONField
。返回的对象是那些给定的 dict
的键-值对都包含在该字段的顶级中。例如:
>>> 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>]>
Oracle和SQLite
contains
Oracle和SQLite不支持。
contained_by
¶这是与 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__contained_by={"breed": "collie", "owner": "Bob"})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={"breed": "collie"})
<QuerySet [<Dog: Fred>]>
Oracle和SQLite
contained_by
Oracle和SQLite不支持。
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>]>
Q
对象¶关键字参数查询--在 filter()
等等——是“和”合在一起的。如果需要执行更复杂的查询(例如, OR
语句),您可以使用 Q objects
.
A Q object
(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
对象可以使用 ~
运算符,允许组合查询以组合正常查询和否定查询 (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 QUERY
Poll.objects.get(
question__startswith="Who",
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
…无效。
参见
这个 OR lookups examples 在Django的单元测试中显示了 Q
.
要比较两个模型实例,请使用标准的Python比较运算符(双等于号): ==
.在幕后,这比较了两种模型的主要关键价值观。
使用 Entry
上面的示例中,以下两个语句是等价的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
如果没有调用模型的主键 id
,没问题。比较将始终使用主键,不管它叫什么。例如,如果模型的主键字段被调用 name
,这两种说法是等价的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
为了方便起见,将删除方法命名为 delete()
。此方法立即删除对象,并返回删除的对象数和每种对象类型的删除数的字典。示例:
>>> e.delete()
(1, {'blog.Entry': 1})
还可以批量删除对象。每个 QuerySet
有一个 delete()
方法,删除该方法的所有成员 QuerySet
.
例如,这将删除所有 Entry
具有 pub_date
2005年:
>>> 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()
删除 all 参赛作品。如果你 do 要删除所有对象,则必须显式请求完整的查询集::
Entry.objects.all().delete()
尽管没有用于复制模型实例的内置方法,但可以轻松地创建复制了所有字段值的新实例。在最简单的情况下,您可以设置 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
有一个 ManyToManyField
到 Author
. 复制条目后,必须为新条目设置多对多关系:
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()
有时,您希望为 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.update(blog=b)
这个 update()
方法立即应用,并返回查询匹配的行数(如果某些行已经具有新值,则可能不等于更新的行数)。唯一的限制是 QuerySet
更新的是,它只能访问一个数据库表:模型的主表。您可以基于相关字段进行筛选,但只能更新模型主表中的列。示例:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> 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()
对更新的调用还可以使用 F expressions
若要基于模型中另一个字段的值更新一个字段,请执行以下操作。这对于根据计数器的当前值递增计数器特别有用。例如,要增加博客中每个条目的ping back计数,请执行以下操作:
>>> Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)
然而,不同于 F()
对象,则不能在使用 F()
更新中的对象--您只能引用要更新的模型的本地字段。如果您尝试引入与 F()
对象,则为 FieldError
将被提出:
# This will raise a FieldError
>>> Entry.objects.update(headline=F("blog__name"))
如果您发现自己需要编写一个对Django的数据库映射器来说太复杂的SQL查询,那么您可以手工编写SQL。Django有几个用于编写原始SQL查询的选项;请参见 执行原始SQL查询 .
最后,需要注意的是,django数据库层只是到数据库的接口。您可以通过其他工具、编程语言或数据库框架访问数据库;您的数据库没有任何Django特定的内容。
7月 22, 2024