Django是第一个框架,其中教程项目变成了我之后继续构建的东西,而不是删除文件夹。
我做过经典的「用Django构建博客」教程像每个人一样,关闭文件夹,继续。真正坚持的后来来了:一个CRM我构建来管理一些虚假的客户数据(客户,笔记,跟进),一个实时聊天应用程序,以及一个餐厅菜单网站。三个分开的项目,三个不同的原因Django的电池包含的方法停止感觉像过度设计,并开始感觉像我没有必要为第四次构建自己的认证系统的原因。
CRM列表页面,无缘无故花了四秒钟
CRM的客户列表页面显示每个客户旁边他们的文件有多少笔记。关于两百个虚假的客户在测试数据库中,那页花了超过四秒钟来加载,这感觉荒唐对于本应是一个单独的数据库查询离开的即时东西。
# views.py, the version that was slow
def customer_list(request):
customers = Customer.objects.all()
return render(request, "crm/customer_list.html", {"customers": customers}){% for customer in customers %}
<tr>
<td>{{ customer.name }}</td>
<td>{{ customer.notes.count }}</td>
</tr>
{% endfor %}看起来完全合理。模板中的customer.notes.count,一次每个客户。这里没有什么尖叫「这是bug」。
当我去寻找时我真正发现的
我听说过Django Debug Toolbar但从未费力安装它直到这页让我真的好奇。它显示了在页面底部这样的东西:
SQL queries: 201 (198 similar)两百零一个查询,为了渲染两百个客户。一个查询来获取客户列表,然后一个更多的查询每个客户,由那个无害的模板中的customer.notes.count触发。每个循环迭代再次打了数据库,分开,因为Django的ORM是懒惰的:customer.notes直到有什么真正问了它才被获取,而模板问了它在一个循环内意味着分开问两百次。
我谷歌的东西,以及我不知道我需要的术语
「django template loop slow multiple database queries」几乎立刻得到我到实际名字对于这个:N+1查询问题。一个查询来获得N行,然后N更多查询来获得每行的相关数据,一次一个,而不是一个查询来提前获得一切。一旦我有了名字,修复是一行。
# views.py, the fixed version
from django.db.models import Count
def customer_list(request):
customers = Customer.objects.annotate(note_count=Count("notes"))
return render(request, "crm/customer_list.html", {"customers": customers}){% for customer in customers %}
<tr>
<td>{{ customer.name }}</td>
<td>{{ customer.note_count }}</td>
</tr>
{% endfor %}annotate与Count在单个数据库查询内做计数,作为一个GROUP BY,而不是一次每行在Python之后。同样两百个客户,一个查询总数,页面加载从四秒跌落到某些我实际上无法感知为花费任何时间的东西。
ORM停止是一个黑盒在这里
来自在较小的脚本中写原始SQL,Django的ORM最初感觉像它在做某些魔法和略微可疑,直到时刻我真的需要向CRM的客户模型添加一个字段在已经有真实数据在数据库中之后,迁移做了它们应该做的事而无需我手写一个ALTER TABLE。
python manage.py makemigrationsMigrations for 'crm':
crm/migrations/0004_add_customer_notes.pypython manage.py migrate
那是时刻Django停止了「我还不理解的很多文件」并开始是我信任的工具不会悄悄摧毁我的数据,在顶上是那个已经悄悄花了我两百个额外查询的事一次页面加载前面。
实时聊天应用为一整个晚上打破了我的精神模型
Django的正常请求响应循环假设一个请求进来,你做某些东西,你发送响应,完成。实时聊天需要一个连接那留下打开,这意味着学习Channels和异步消费者。第一个尝试,我只是用常规开发服务器运行它:
python manage.py runserver这乐意开始,乐意服务正常的页面,然后做绝对没用处的东西一旦一个客户试图打开一个WebSocket连接。没有错误,没有崩溃,只是一个连接那从未升级。Django的默认WSGI开发服务器不知道该用一个协议升级请求做什么,它为请求响应模型构建以及什么都没有。
# consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.channel_layer.group_add("chat", self.channel_name)
await self.accept()
async def receive(self, text_data):
await self.channel_layer.group_send(
"chat", {"type": "chat.message", "message": text_data}
)
async def chat_message(self, event):
await self.send(text_data=event["message"])消费者代码本身是罚款,或多或少第一次真实尝试。实际修复是认识我需要通过Daphne运行它,一个ASGI服务器,而不是正常的WSGI开发服务器,既然ASGI是协议那实际上理解长生命连接在首先。
daphne -p 8001 myproject.asgi:application一旦我有了正确的服务器实际上说正确的协议,消费者那看起来被打破了一整个晚上工作了第一次真实测试。我没有打破什么基本关于框架。我只是一直通过那个服务器运行它那从未会对此工作。
餐厅菜单项目教会我admin面板被低估了
这里没什么复杂的,只是菜单项,类别,价格,一个公共页面。有趣的部分是认识构建的admin面板意味着我从不必为一个客户更新价格为自己建立CMS。
# admin.py
from django.contrib import admin
from .models import MenuItem, Category
admin.site.register(Category)
admin.site.register(MenuItem)两行,然后突然有一个工作,许可的admin接口为编辑每个菜单项,不分开CMS构建需要。第一次感觉像作弊,以好的方式。
演示数据几乎出去给一个真实的人
小的,略微令人尴尬的旁白:虚假的CRM数据我用于测试包括一个顾客叫「Test Testerson」与一个笔记字段那只说「这是假的,忽略。」我曾经几乎发送了一个CRM的屏幕录制给某人作为演示没有检查哪个客户记录在屏幕上。在发送之前赶上它,但现在它是一个永久的习惯来真的扫描可见的行在录制任何东西之前,不只是信任测试数据保持包含到我自己的屏幕。
我会告诉某人开始Django在Flask之后
Flask,那是我早期CLI和GUI待办应用大多数住,让你构建一切,这教会你很多。Django假设你想电池包括,一个真实项目快速而更慢实际理解最初,既然有更多框架站在你和请求之间。两者对不同的原因都是对的。我只需要CRM的所有三个,聊天应用,以及菜单网站,加上一个真正坏的下午的意外缓慢,在Django的版本之前「更快」开始感觉赚了而不是像魔法我还没付过。
什么我会做不同
在任何新的Django项目的第一天安装Django Debug Toolbar,不在一页已经让我怀疑足够去寻找它之后。N+1问题坐在那边从那个视图的非常第一个版本。我只是没有一个方式来看到它直到我去并得到一个。