Django应用程序中查询执行的显式控制。
项目描述
django-zen-queries
让您控制代码中哪些部分可以运行查询,哪些不可以。
在Python 3.6 - 3.9上测试了Django 2.2 - 3.1。
动机
明确优于隐晦
(《Python之禅》)
Django ORM的最强之处也是其最大弱点。通过让开发者从必须考虑何时执行数据库查询中解脱出来,ORM鼓励开发者 不思考何时执行数据库查询!这对于快速开发周转来说往往有很大的好处,但除了最简单系统外,都可能带来重大的性能影响。
Django的ORM使查询 隐晦。Python之禅告诉我们,明确优于隐晦,所以让我们明确哪些代码部分允许运行查询,哪些不允许。
查看 这篇博客文章 了解更多背景信息。
示例
想象一个披萨餐厅网站,有如下模型
class Topping(models.Model):
name = models.CharField(max_length=100)
class Pizza(models.Model):
name = models.CharField(max_length=100)
toppings = models.ManyToManyField(Topping)
这是菜单视图
def menu(request):
pizzas = Pizza.objects.all()
context = {'pizzas': pizzas}
return render(request, 'pizzas/menu.html', context)
最后,这是模板
<h1>Pizza Menu</h1>
<ul>
{% for pizza in pizzas %}
<li>{{ pizza.name }}</li>
{% endfor %}
</ul>
这里运行了多少个查询?答案很简单:只有一个!由 Pizza.objects.all()
发出的查询就足以获取显示在菜单上的信息。
现在:想象客户要求菜单上的每个披萨都要包含披萨上配料数量的计数。很简单!只需更改模板
<h1>Pizza Menu</h1>
<ul>
{% for pizza in pizzas %}
<li>{{ pizza.name }} ({{ pizza.toppings.count }})</li>
{% endfor %}
</ul>
但当前运行了多少个查询呢?嗯,这是一个经典的n查询问题。我们现在有一个查询来获取所有的披萨,然后对于每个披萨再有一个查询来获取配料数量。披萨越多,应用程序运行越慢。 我们可能只有在网站投入生产后才会发现这个问题。
如果你在阅读Django性能教程,接下来的步骤会告诉你如何解决这个问题(.annotate
和Count
等)。但这不是重点。上面的例子只是说明了代码库中不同部分的代码,在抽象层次不同,甚至在较大的项目中可能由不同的开发者负责,它们如何相互作用导致性能低下。面向对象设计鼓励黑盒实现隐藏,但如果你的目标是构建高性能的Web应用程序,隐藏查询执行的点是最糟糕的事情。那么我们如何在不破坏所有抽象的情况下解决这个问题呢?
这里有两个技巧
- 防止开发者在不意识到的情况下意外运行查询。
- 鼓励设计代码,将获取数据与渲染数据分开。
这个包提供了三件非常简单的事情
- 一个上下文管理器,允许开发者明确查询运行的位置。
- 一个工具,使查询集不那么懒惰。
- 一些工具,使得使用Django模板和Django REST框架序列化器与上下文管理器结合使用变得简单。
要绝对清楚:这个包没有提供任何实际改善你的查询模式的工具。它只是告诉你何时需要这样做!
说明
为了演示如何使用django-zen-queries
,让我们回到我们的例子。我们希望确保对模板的更改不会触发查询。所以,我们按照以下方式更改我们的视图
def menu(request):
pizzas = Pizza.objects.all()
context = {'pizzas': pizzas}
with queries_disabled():
return render(request, 'pizzas/menu.html', context)
这里的queries_disabled
上下文管理器做了一件事:它阻止任何代码在它里面运行数据库查询。一点都没有。如果它们尝试运行一个查询,应用程序将引发一个QueriesDisabledError
异常并崩溃。
这几乎已经足够给我们我们需要的,但还不够。上面的代码将始终引发一个QueriesDisabledError
,因为查询集(Pizza.objects.all()
)是懒惰的。数据库查询实际上只有在查询集迭代时才会运行——这发生在模板中!所以,django-zen-queries
提供了一个小小的辅助函数,fetch
,它强制评估查询集
def menu(request):
pizzas = Pizza.objects.all()
context = {'pizzas': fetch(pizzas)}
with queries_disabled():
return render(request, 'pizzas/menu.html', context)
现在我们正好需要这样的东西:当开发人员来到模板中并添加{{ pizza.toppings.count }}
时,它根本不会工作。他们被迫找出如何使用annotate
和Count
来获取他们所需的数据,而不是在将来某个时候,当客户抱怨网站越来越慢时!
装饰器
您还可以将queries_disabled
用作装饰器,以禁止函数或方法与数据库交互
@queries_disabled()
def validate_xyz(pizzas):
...
这也与Django的method_decorator
实用工具一起工作。
额外工具
除了上下文管理器外,该包还提供了一些工具,使得在常见情况下使用更加容易
渲染快捷方式
如果您正在使用Django的render
快捷方式(如上面的例子),为了避免在每一个视图中都添加上下文管理器,您可以将导入从from django.shortcuts import render
更改为from zen_queries import render
。该文件中的所有视图将自动禁止在模板渲染期间运行查询。
TemplateResponse子类
TemplateResponse
(以及SimpleTemplateResponse
)对象是懒加载的,这意味着模板渲染是在Django栈的“出口”过程中发生的。zen_queries.TemplateResponse
和zen_queries.SimpleTemplateResponse
是这些对象的子类,并在render
方法上应用了queries_disabled
。
您可以通过在视图中将response_class
属性设置为zen_queries.TemplateResponse
,告诉Django的基于类的视图使用这些子类而不是默认的TemplateResponse
。
Django REST框架序列化和视图混合
Django REST框架序列化器是意外查询的另一个主要来源。向序列化器中添加一个字段(可能在嵌套序列化器的树中很深)可以非常容易地导致您的应用程序突然开始发出数百个查询。zen_queries.rest_framework.QueriesDisabledSerializerMixin
可以添加到任何序列化器中,以便将queries_disabled
应用于.data
属性,这意味着序列化阶段不允许执行任何查询。
您可以通过以下方式将此混合添加到现有的序列化器实例中:serializer = disable_serializer_queries(serializer)
。
如果您正在使用REST框架通用视图,也可以添加一个视图混合,zen_queries.rest_framework.QueriesDisabledViewMixin
,该混合覆盖了get_serializer
以将QueriesDisabledSerializerMixin
混合到现有的序列化器中。这很有用,因为您可能希望在多个视图中使用相同的序列化器类,但在某些上下文中禁用查询,例如在列表视图中。请记住,Python MRO是左到右的,因此混合必须放在任何实现get_serializer
的基类之前。视图混合仅在GET
请求上禁用查询,因此可以安全地与ListCreateAPIView
和类似视图一起使用。
逃生门
如果您绝对肯定不能避免在执行在queries_disabled
块下的代码库的一部分时运行查询,还有一个名为queries_dangerously_enabled
的上下文管理器,它允许您暂时重新启用数据库查询。
模板标签
Django模板系统提供了块标签,允许您直接在模板中启用或禁用查询执行。
重要提示:为了使用模板库,您必须将"zen_queries"
添加到您的INSTALLED_APPS
设置中。然后,在模板顶部使用{% load zen_queries %}
来加载标签库。
{% queries_disabled}
标签在您希望将django-zen-queries
模式应用于通过覆盖模板提供定制的第三方库(如Django管理后台)时非常有用。
{% load zen_queries %}
{% queries_disabled %}
<ul>
{% for pizza in pizzas %}
<li>{{ pizza.name }}</li>
{% endfor %}
</ul>
{% end_queries_disabled %}
{% queries_dangerously_enabled %}
标签在您使用render
快捷方式或TemplateResponse
子类(见上文)时非常有用,但希望允许模板的特定部分执行查询。这应该谨慎使用,并且您应该仅包装模板中最小可能的部分:需要执行查询的确切行或几行。
{% load zen_queries %}
{% queries_dangerously_enabled %}
There are {{ pizzas.count }} pizzas.
{% end_queries_dangerously_enabled %}
权限陷阱
通过模板变量{{ perms }}
访问权限在模板渲染时可能会引起查询。幸运的是,Django的权限检查被ModelBackend
缓存,这可以通过在视图中调用request.user.get_all_permissions()
来预先填充,在渲染模板之前。
它是如何工作的?
它使用Django 2.0中引入的数据库监控功能。
安装
从PyPI安装
pip install django-zen-queries
行为准则
有关向此存储库贡献时的行为准则指南,请参阅https://www.dabapps.com/open-source/code-of-conduct/
项目详情
下载文件
下载适合您平台的文件。如果您不确定该选择哪一个,请了解有关安装包的更多信息。
源代码分发
构建分发
django-zen-queries-2.1.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 624dad6a10f04df4dc188ff4f27a875943c1708738bd9863388dd39861ac9614 |
|
MD5 | 5bc8930d8a5425a44c7ed78a1cbbd35c |
|
BLAKE2b-256 | 888703c327804113144d88cfa76bd4c7deebe565af656dddf0e29682bca4ecbd |
django_zen_queries-2.1.0-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 255f6a1efd94974c5752af2ef60d2d1fa66466d74e7faf32a6ff46141e947e55 |
|
MD5 | b3a333a7f1db665a3b94e7ebc5f9de93 |
|
BLAKE2b-256 | 4f51b611dce1db2315c92cfc9958d9440063c3fab0c732e89035798d29a67f8b |