Django admin是一个非常强大的工具。我们用它来进行日常操作,浏览数据和做技术支持。当我们一些项目的用户量从零增加到100K +,我们就开始体验到Django admin的一些痛点了——响应时间长且数据库负载重。
在这篇简短的文章中,我将分享一些我们在项目中使用的简单技术,以便当项目用户量和复杂度都大幅增加的情况下,Django Admin也能很好地完成任务。
我们使用Django 1.8、Python 3.4和PostgreSQL 9.4。这些代码示例适用于Python 3.4,但也可以被轻松修改以运行在2.7和其他Django版本上。
开始之前
下面是一个Django Admin列表视图中的主要组件:
示例admin列表视图包括本文中讨论的某些组件
日志记录
Django的大部分工作是执行SQL查询,因此我们的主要重点将放在最小化查询数量上。要跟踪查询的执行情况,你可以使用以下方法之一:
N+1 问题
N+1问题是ORM中一个众所周知的问题。为了说明这个问题,我们来假设我们有以下模式:
通过实现__str__我们告诉Django我们希望将类别名称用作对象的默认描述。每当我们打印一个类别对象时,Django都会获取这个类别的名称。
我们的Product模型的一个简单的admin页面可能看起来像这样:
这似乎很正常,但是SQL日志揭示其恐怖的地方:
Django首先对对象进行计数(稍后再介绍),然后获取实际对象(限制为默认页面大小100),接着将数据传递到模板进行渲染。我们使用类别名称作为Category对象的描述,因此对于每个product,Django都必须获取其类别名称。这将会导致100个额外的查询。
为了告诉Django我们想要执行一个连接而不是一个接一个地获取类别名称,我们可以使用list_select_related:
现在,SQL日志看起来好很多了。我们只有1个查询而不是101个:
要了解此设置的实际影响,请考虑以下内容。Django默认页面大小为100个对象。如果你有一个相关字段,你就有约101个查询。如果你在列表视图中显示了两个相关的对象,你就有约201个查询,依此类推。
在一个连接(join)中获取相关字段仅适用于ForeignKey关系。如果你想显示ManyToMany关系,则要复杂一些(大多数时候是错误的,但请继续阅读)。
相关的字段
有时在对象之间快速导航可能会很有用。在尝试了教支持人员使用URL参数进行过滤一段时间后,我们最终选择放弃并创建了两个简单的装饰器。
admin_link
创建一个指向相关模型的详细信息页面的链接:
该装饰器会为每一个相关模型渲染一个链接(...),在列表视图和详细信息视图中都有展示。例如,如果我们想在产品的详情页,增加一个到类别页面的链接,那我们可以使用如下装饰器:
admin_changelist_link
更复杂的链接(例如“一个类别的所有产品”)需要不同的实现。我们创建了一个接受一个查询字符串的装饰器,并将其链接到相关模型的列表视图:
要添加一个从类别到其产品的链接,我们需要在CategoryAdmin中这样做:
要小心products参数。它很容易就会做这样的事情:
上面的示例将会导致额外的查询。
readonly_fields
在详细信息页面中,Django会为每个字段创建一个可编辑的元素。文本和数字字段将被作为常规输入字段进行渲染。选择字段和外键字段将被作为一个
获取选项——相关模型的整张表数据及其描述(还记得N+1问题吗?)
渲染选项列表——每个相关模型实例对应一个选项。
一个经常被忽略的常见场景是User模型的外键。当你有100个用户时,你可能不会注意到负载,但是当你突然有10万个用户时,会发生什么呢?详细信息页面将获取整个用户表,而选项列表将使生成的HTML变得非常大。我们付出了两次代价,第一次是全表扫描,然后是下载html文件。更不用说一开始生成html文件所需的内存了。
拥有一个包含10万个选项的select元素不是特别有用。防止Django将一个字段渲染为一个
这将渲染相关模型的描述,但我们不能在admin中更改它。
另一个阻止Django渲染选择框的方法是将该字段标记为raw_id_fields。
使用raw_id_fields, Django将渲染一个显示id的特殊小部件,以及一个在弹出窗口中打开所有值列表的选项。当你想要编辑一个外键值时,此选项就非常有用。
过滤器
我们经常使用admin接口作为日常支持的工具。我们发现,大多数情况下我们使用了相同的过滤器:仅活跃用户、过去一个月注册的用户、成功的交易等等。一旦我们意识到这一点,我们就问自己,如果我们有极大的可能能立即对整个数据集应用一个过滤器,那我们为什么还要获取整个数据集呢?我们开始寻找在进入模型列表视图时应用默认过滤器的方法。
DefaultFilterMixin
有许多应用默认过滤器的方法。有些方法涉及到自定义过滤器或向请求注入特殊的查询参数。我们想避免这些。
我们发现以下方法要简单明了:
如果列表视图是从另一个视图被访问的,并且没有指定查询参数,我们会生成一个默认查询并进行重定向。
让我们对产品页面应用一个默认过滤器来显示近一个月创建的产品:
如果我们在页面内部添加下钻过滤,或者如果我们使用查询参数访问页面,则默认的过滤器将不会被应用。
快速提示
我们随时间收集的一些小技巧。
show_full_result_count
阻止Django在列表视图中显示总行数。设置show_full_result_count=False会在每个页面加载的查询集上省去一个count(*)查询。
defer
当执行一个查询时,整个结果集会被放入内存中进行处理。如果你的模型中有大量的列,比如JSON或文本字段,那么将它们进行延迟直到你真正需要使用它们的时候可能是一个好主意。要延迟字段,请重写get_queryset。
更改Admin默认URL路由
这绝对不是你应该采取的唯一的保护你的管理页面的预防措施,但它会使“好奇”的用户更难到达登录页面。
在你的主要urls.py中重写默认的admin路由:
请参阅
我写的一连串关于如何使Django admin更安全的提示。(地址:https://hakibenita.com/5-ways-to-make-django-admin-safer )
date_hierarchy
我们发现这个索引可以被用来改善在PostgresSQL 9.4中使用日期层次结构谓语生成的查询:
请确保更改表名、索引名、日期层次结构列和时区。
请参阅
我写的《扩展Django admin date_hierarchy》一文。
结论
即使你的数据库中没有10万用户和数百万条记录,保持admin整洁也很重要。错误的代码可能会在你意想不到的时候使你陷入困境。
英文原文:https://hakibenita.com/things-you-must-know-about-django-admin-as-your-app-gets-bigger
译者:测试