1.5 使用查询集(QuerySets) 和 管理器(manag
1. 使用查询集(QuerySets) 和 管理器(managers)
现在你有一个功能比较完备的管理员站点来管理你的博客内容,接下来的时间将去学习如何从数据库中检索信息并且和它交互。Django 附带了一个功能强大并且简单的的数据库抽象 API 去让你 创建、检索、更新、删除对象。Django 的对象关系模型 (ORM) 兼容 MySQL, PostgreSQL, SQLite, Oracle, MariaDB。你可以在项目的 settings.py
文件中 DATABASES
参数定义你要使用的数据库。Django 可以在同一时间使用多个数据库,并且你可以对数据库路由进行编程去自定义一个路由模式。
一旦你创建了一个数据模型,Django 就提供一个免费的API来与它们交互。你可以在这个官方文档中找到有关数据模型的参考。
Django 的 ORM 是基于 查询集(QuerySets) 的。查询集是从数据库检索对象的数据库查询的集合。你可以在查询集上应用过滤器,以根据给定的参数缩小查询的结果。
2. 创建对象
在终端中运行下面的命令打开一个 Python shell:
python manage.py shell
输入下面的命令:
Snipaste_2020-07-17_15-14-23.jpg让我们来分析以下这些代码,首先,检索用户名为 admin
的user
对象:
user = User.objects.get(username='admin')
get()
方法允许你从数据库中检索出单个对象,注意,该方法期望得到与查询匹配的结果。如果数据库没有返回任何结果,这个方法将会抛出一个 DoesNotExist
异常,如果数据库返回了多个结果,它将抛出一个 MultipleObjectsReturned
异常。这两个异常都是执行查询的模型类的属性。
现在通过自定义 title, slug, body,并将之间检索到的用户设置为该 文章的作者。
post = Post(title=' Another post' , slug=' another-post' , body=' Post body. ' , author=user)
此对象在内存中,没有持久化到数据库中。
最后,你通过使用 save()
方法把 Post
对象保存到数据库中:
post.save()
前面的操作在后台执行 INSERT SQL
语句,你已经了解了如何 首先在内存中创建对象,然后将其持久化到数据库中,但是也可以使用 create()
方法在单个操作中创建对象并将其持久化到数据库中,如下所示:
Post.objects.create(title=' Another post' , slug=' another-post' , body=' Post body. ' , author=user)
3. 更新对象
现在,将文章标题更改为不同的内容并再次保存对象:
post.title = 'new title'
post.save()
这是,这个save()
方法将执行一个 UPDATE SQL
语句。
在调用
save()
方法之前,对对象所做的更改不会持久化到数据库中。
4. 检索对象
你已经知道如何使用 get()
方法从数据库中检索出一个对象。你使用 Post.objects.get()
方法进行访问。每个 Django 模型至少有一个管理器,默认的管理器叫做 objects
。你可以使用模型管理器获得 QuerySet
对象。从表中检索所有对象,只需在默认对象管理器上使用all()
方法,就像这样:
all_posts = Post.objects.all()
这就是创建返回数据库中所有对象的QuerySet的方法,注意,这个时候查询集还没有执行。Django 的查询集是 延迟查询,也就是说,他们只有在被迫的时候才会被执行。这种方式可以使得QuerySet很有效率。如果没有将QuerySet设置为变量,而是直接在Python shell上编写它,则执行QuerySet的SQL语句,因为您强制它输出结果:
>>> all_posts
4.1. 使用 filter() 方法
要过滤一个 QuerySets,你可以在管理器上中 filter()
方法。例如,您可以使用下面的QuerySet检索在2020年发布的所有文章:
>>> Post.objects.filter(publish__year=2020)
你也可以通过多个字段过滤,例如,你可以通过发布时间为 2020 年,作者为 admin
检索所有的文章。
Post.objects.filter(publish__year=2020, author__username='admin' )
使用字段查找方法的查询是使用两个下划线构建的,例如 publish__year
, 但同样的符号也用于访问相关模型的字段,如 author__username。
4.2. 使用 exclude()
可以使用管理器的exclude()
方法从QuerySet中排除某些结果。例如,您可以检索所有在2020年发布的文章,但标题不以Why
开头:
Post.objects.filter(publish__year=2020).exclude(title__startswith='Why')
4.3. 使用 order_by()
可以使用管理器的order_by()
方法按不同的字段对结果进行排序。例如,您可以检索所有按标题排序的对象,如下:
Post.objects.order_by('title')
默认为升序,你可以使用 负号前缀 表明通过降序排序。如下所示:
Post.objects.order_by('-title')
5. 删除对象
如果你想删除一个对象,你可以在实例对象上使用 delete()
方法。
post = Post.objects.get(id=1)
post.delete()
注意,删除对象还将删除on_delete设置为
CASCADE
的外键对象的依赖关系。
6. 在评估查询集时
在对查询集进行计算之前,创建查询集不会涉及任何数据库活动。查询集通常返回一个没有计算的查询集,您可以将任意多的过滤连接到一个QuerySet,并且在QuerySet计算完成之前,您不会触及数据库,当计算一个查询集,它将转换为对数据库的 SQL 查询。
查询集仅仅会在下面这些情况下被执行:
- 第一次迭代它们的时候
- 在对实例进行切片的时候。
Post.objects.all()[:3]
- 当你pickle或缓存它们时。 (译者注: pickle 是序列化的意思)
- 对他们调用
repr()
或者是len()
方法时。 - 对他们显式调用
list()
方法时。 - 当您在语句(如
bool()
、and
,or
,if
)中测试它们时。
7. 创建模型管理器
正如前面说到的,objects是每个模型的默认管理器,它检索数据库中的所有对象,然而你可以可以为你的模型自定义一个管理器,您将创建一个自定义管理器来检索所有具有已发布状态的文章。
有两种方法可以为你的模型添加或者定制一个管理器:1. 你可以对一个已存在的管理器添加额外的管理方法。2. 通过修改管理器返回状态来创建新的管理器。
第一个方法为您提供了一个QuerySet API,比如 Post.objects.my_manager()
, 第二种为您提供了 Post.my_manager.all()。这个管理器允许你通过使用 Post.published.all()
方法检索文章。
在你的 blog
应用程序里面编写 models.py
文件去定制一个 管理器。
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager, self).get_queryset().filter(status='published')
class Post(models.Model):
# .... 表示之前的代码
objects = models.Manager() # 默认管理器
published = PublishedManager() # 我们定制的管理器
模型中声明的第一个管理器成为默认管理器。你可以使用 Meta
属性 的 default_manager_name
来指定一个不同的默认理器。如果在Django模型中没有定义管理器。Django 会为它创建一个默认的管理器 objects
。如果你为你的模型声明任何管理器,但是你想要保留 objects
管理器,你必须显式地将它添加到你的模型中。在前面的代码中,你添加了一个默认管理器objects
并且 还未 Post
模型定制了一个 published
管理器。
管理器的 get_queryset()
方法返回将要执行的查询集。你可以重写此方法将您自定义的 filter 包含在最后的查询集中。
现在您已经修改了自定义管理其并将其添加到 Post
模型中,你可以使用它执行查询。让我们来测试一下。
使用以下的命令再次启动开发服务器:
python manage.py shell
现在你可以导入 Post
模型,并且检索所有标题前面是包含Who
发布的文章,执行下面的查询集:
from blog.models import Post
Post.published.filter(title__startswith='Who')
要获得这个查询集的结果,确保 在 Post
对象的 published
字段设置为 True
, 并且 title
的开头是 Who.