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 в шаблоне, один раз за клиента. Ничего здесь не кричит «это баг».
что я на самом деле нашёл когда пошёл искать
Я слышал о Django Debug Toolbar но никогда не беспокоился об установке пока эта страница не заставила меня действительно интересоваться. Она показала что-то в низу страницы вроде этого:
SQL queries: 201 (198 similar)Двести один запрос, для рендера двухсот клиентов. Один запрос получить список клиентов, и потом один больше запрос за клиента, запущен тем невинным выглядящим customer.notes.count в шаблоне. Каждая итерация цикла бьёт базу данных снова, отдельно, потому что ORM Django ленив: 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 соединение. Нет ошибки, нет краша, просто соединение что никогда не обновляется. Тот сервер WSGI разработки Django не знает что делать с запросом обновления протокола вообще, это построено для модели запроса-ответа и ничего более.
# 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Как только я имел правильный сервер действительно говорящий правильный протокол, потребитель что выглядел сломанным на целый вечер работал на первом реальном тесте. Я не сломал ничего фундаментального о фреймворке. Я просто был жалея это через тот один сервер что был никогда идёт работу для этого.
проект меню ресторана научил меня админ панель недооценена
Ничего сложного здесь, просто пункты меню, категории, цены, общественная страница. Интересная часть была понимание построенной админ панели означало я никогда не должен был строить CMS для клиента обновлять цены себя.
# admin.py
from django.contrib import admin
from .models import MenuItem, Category
admin.site.register(Category)
admin.site.register(MenuItem)Две строки, и внезапно есть работающий, разрешенный админ интерфейс для редактирования каждого пункта меню, нет отдельной необходимости строить CMS. Ощущалось как обман первый раз, хорошим способом.
демонстрационные данные почти вышли реальному человеку
Маленькая, слегка смущающая заметка: поддельные данные CRM я использовал для тестирования включал клиента по имени «Test Testerson» с полем заметки что просто сказал «это поддельное, игнорируй.» Я почти отправил запись экрана CRM кому-то как демонстрацию без проверки какой клиент запись была на экране. Поймал это перед отправкой, но теперь это постоянная привычка действительно сканировать видимые строки перед записью чего-то, не просто доверять что тестовые данные остаются содержащимися к моему собственному экрану.
что я сказал бы кому-то начинающему Django после Flask
Flask, что это где мои более ранние CLI и GUI приложения задач большинство жили, делает тебя строить всё, что учит тебя много. Django предполагает ты хочешь батареи включены, быстрее для реального проекта и медленнее действительно понять первоначально, так как есть больше фреймворка стоящего между тобой и запросом. Оба верны для разных причин. Я просто нужен все три CRM, приложение чата, и сайт меню, плюс один действительно плохой вечер неожиданной медлительности, перед версией Django «быстрее» начала чувствовать заработанной вместо как магия я не заплатил за ещё.
что я буду делать по-другому
Установи Django Debug Toolbar на день один любого нового проекта Django, не после страница уже заставила меня подозрительным достаточно идти искать это. Проблема N+1 была сидя там с самой первой версии того вида. Я просто не имел способ видеть это пока я не пошёл и не получил один.