В этом руководстве мы рассмотрим основные ошибки Django-разработчиков и узнаем, как их избежать. Статья может быть полезна даже опытным разработчикам, потому что и они совершают такие ошибки, как поддержка неподъёмно больших настроек или конфликтов имён в статических ресурсах.
Django — бесплатный сетевой open source Python-фреймворк, помогающий решать распространённые в разработке проблемы. Он позволяет создавать гибкие, хорошо структурированные приложения. В Django уже из коробки есть много современных возможностей. Например, для меня такие фичи, как Admin, инструмент Object Relational Mapping (ORM), Routing и Templating, делают Django первым кандидатом при выборе инструментария для разработки. Создание приложения требует много сил, и, наслаждаясь своим делом, как и любой разработчик, я хочу тратить как можно меньше времени на рутинные задачи. Django сильно в этом помогает, не заставляя жертвовать гибкостью приложения.
Киллер-фича Django — мощный конфигурируемый админский интерфейс, который автоматически (автомагически?) генерируется на основе схемы вашей модели и моделей админки. Чувствуешь себя прямо-таки волшебником. С помощью интерфейса Admin пользователь может конфигурировать много вещей, в их числе — список управления доступом (access control list, ACL), разрешения и действия на уровне строк (row-level), фильтры, порядки сортировки (orders), виджеты, формы, дополнительные URL-хелперы и многое другое. Я считаю, что админка нужна каждому приложению. Это лишь вопрос времени, когда такая панель понадобится вашему основному приложению. В Django она создаётся быстро и удобно.
Также в Django есть мощная ORM, из коробки работающая со всеми главными базами данных. Она «ленива»: в отличие от других ORM, обращается к БД только по мере необходимости. В ней есть поддержка основных SQL-инструкций (и функций), которые вы можете использовать из своего исходного Python-кода наряду со всеми остальными возможностями языка.
В Django очень гибкий и мощный шаблонизатор (templating engine). Доступны многие стандартные фильтры и метки (tags), также можно создавать свои собственные. Django поддерживает другие движки как собственные шаблоны, предоставляет API для лёгкой интеграции с другими движками посредством стандартных shortcut-функций для обработки шаблонов.
Фреймворк имеет и много других важных возможностей вроде URL-роутера, который парсит входящие запросы и генерирует новые URL на основе схемы роутинга. В целом Django приятен в работе, и, когда вам понадобится помощь, просто почитайте документацию.
Ошибка № 1. Использование для проектных зависимостей глобального окружения Python
Не используйте глобальное окружение Python для зависимостей вашего проекта, потому что это может привести к возникновению конфликтов зависимостей. Python не умеет работать с несколькими версиями пакетов одновременно. Это станет проблемой, если разным проектам нужны разные, несовместимые версии одного пакета.
Обычно такую ошибку допускают новички в Python- и Django-разработке, не знающие об особенностях изоляции окружения Python.
Есть много способов изолировать окружение, наиболее часто встречаются такие:
- virtualenv: пакет Python, генерирующий папку с окружением. Содержит скрипт для (де)активации окружения и управления установленными в нём пакетами. Это мой любимый и самый простой метод. Обычно я создаю окружение поближе к папке проекта.
- virtualenvwrapper: пакет Python, глобально устанавливающий набор инструментов для создания/удаления/активации и т. д. виртуальных окружений и предоставляющий доступ к этому набору. Все окружения хранятся в одной папке (её можно переписать с помощью переменной WORKON_HOME). Я не вижу преимуществ в использовании
virtualenvwrapper
вместоvirtualenv
. - Виртуальные машины: нет лучшей изоляции, чем целая виртуальная машина, выделенная под ваше приложение. Есть масса доступных инструментов, например VirtualBox (бесплатный), VMware, Parallels и Proxmox (мой фаворит, есть бесплатная версия). В сочетании с инструментом автоматизации виртуальных машин вроде Vagrant это может оказаться очень мощным решением.
- Контейнеры: в последние годы я почти в каждом проекте использую Docker, особенно в новых проектах, начинаемых с нуля. Docker — невероятный инструмент с множеством возможностей. Для его автоматизации доступна куча сторонних инструментов. В Docker есть кеширование уровней (layer caching), позволяющее крайне быстро пересоздавать контейнеры. В них я использую глобальное окружение Python, потому что каждый контейнер имеет собственную файловую систему и проекты изолируются на высоком уровне. Docker позволяет новым членам команды быстрее начинать работу над проектом, особенно если у них есть опыт работы с этой технологией.
Ошибка № 2. Отсутствие привязки зависимостей в файле requirements.txt
Каждый новый проект Python должен начинаться с файла requirements.txt и нового изолированного окружения. Обычно вы с помощью pip/easy_install
устанавливаете все пакеты, не забывая о requirements.txt
. Обычно проще (возможно, правильнее) развёртывать проекты на серверах или на машинах членов команды.
Также важно в файле requirements.txt выполнять привязку (pin) конкретных версий ваших зависимостей. Обычно разные версии пакета предоставляют разные модули, функции и параметры функций. Даже в младших версиях изменения зависимостей могут оказаться такими, что это сломает ваш пакет. Это очень серьёзная проблема, если у вас живой проект и вы планируете регулярно его развёртывать, так как без системы управления версиями ваша сборочная система всегда будет устанавливать последнюю доступную версию пакета.
В production всегда выполняйте привязку пакетов! Я для этого использую очень хороший инструмент pip-tools. Он предоставляет набор команд, помогающих управлять зависимостями. Инструмент автоматически генерирует requirements.txt
, в котором привязаны не просто ваши зависимости, а вообще всё дерево, т. е. и зависимости ваших зависимостей.
Иногда нужно обновить какие-то пакеты в списке зависимостей (например, только фреймворк или утилиту). Если вы прибегаете к pip freeze, то не знаете, какие зависимости используются какими пакетами, и поэтому не можете их обновить. Инструмент pip-tools автоматически привязывает пакеты в соответствии с привязанными вами зависимостями, и поэтому он автоматически решает, какие пакеты нужно обновить. А благодаря используемым комментариям в requirements.txt
вы всегда знаете, какой пакет пришёл из какой зависимости.
Если быть ещё более осторожным, то можно делать бекап исходных файлов ваших зависимостей. Храните копию в своей файловой системе, Git-папке, S3-папке, FTP, SFTP — где угодно, лишь бы под рукой. Бывают ситуации, когда исключение из списка относительно небольшого пакета ломает большое количество пакетов в npm. Pip позволяет скачивать все необходимые зависимости в виде исходных файлов. Почитайте об этом подробнее, выполнив команду pip help download
.
Ошибка № 3. Использование старомодных Python-функций вместо представлений-классов (Class-based Views)
Иногда целесообразно использовать в файле приложения views.py
маленькие Python-функции, особенно для тестовых или утилитарных представлений. Но обычно в приложениях нужно использовать представления на основе классов (CBV).
CBV — это представления общего назначения, предоставляющие абстрактные классы, реализующие распространённые задачи веб-разработки. CBV созданы профессионалами и покрывают большинство востребованных моделей поведения. У них есть прекрасно структурированный API, и CBV подарят вам возможность наслаждаться всеми преимуществами ООП. Ваш код будет чище и читабельнее. Забудьте о трудностях использования стандартных функций представления (view functions) Django для создания списков, CRUD-операций, обработки форм и т. д. Можно просто расширять подходящий CBV под ваше представление и переопределять (override) функции или свойства класса, конфигурирующие поведение представления (обычно функция возвращает свойство, и вы можете добавить в неё любую логику, которая способна превратить ваш код в спагетти, если вместо CBV вы прибегнете к функциям представления).
Например, можно использовать в проекте разные миксины, которые переопределяют основные модели поведения CBV: создание контекстов представлений, проверка авторизации на уровне строк (on the row level), автосоздание путей шаблонов на основе структур приложения, интегрирование умного кеширования и многое другое.
Я создал пакет Django Template Names, который стандартизирует имена шаблонов для ваших представлений на основе имени приложения и имени класса представления. Я пользуюсь им каждый день и экономлю кучу времени при выборе имён. Просто вставьте миксин в свой CBV — class Detail(TemplateNames, DetailView):
— и он начнёт работать! Конечно, можете переопределить мои функции и добавить мобильные адаптивные шаблоны, другие шаблоны для user-agent’ов или что-нибудь ещё.
Ошибка № 4. Написание «толстых» (fat) представлений и «тонких» (skinny) моделей
Если у вас логика приложения перенесена из модели в представления, это означает, что в представлениях находится код, принадлежащий модели. То есть представления становятся «толстыми», а модель — «тонкой».
А нужно писать «толстые» модели и «тонкие» представления.
Разбейте логику по маленьким методам в модели. Это позволит использовать их многократно и из многочисленных источников (админский пользовательский интерфейс, пользовательский интерфейс фронтенда, конечные точки API, многочисленные представления). Это займёт всего несколько строк кода, и вам не придётся копипастить кучу строк. Когда в следующий раз будете писать функциональность отправки письма пользователю, расширьте модель с помощью email-функции, а не пишите логику в контроллере.
Это сделает ваш код более удобным для модульного тестирования, потому что вы сможете протестировать логику электронной почты в одном месте, а не делать это в каждом контроллере. Подробнее об этой проблеме почитайте в Django Best Practices. Решение простое: пишите «толстые» модели и «тонкие» представления. Начните это делать уже в следующем проекте или рефакторьте текущий.
Ошибка № 5. Огромный, неповоротливый файл настроек
Даже в новом файле настроек Django-проекта этих настроек содержится множество. А в реальных проектах файл разрастается до 700+ строк, которые трудно сопровождать, особенно когда окружениям разработки, продакшена и стейджинга нужны разные конфигурации.
Вы можете вручную разделить конфигурационный файл и создать отдельные загрузчики, но я хочу порекомендовать отличный, хорошо протестированный Python-пакет Django Split Settings, соавтором которого я являюсь.
Пакет предоставляет две функции — optional
и include
, которые поддерживают подстановки (wildcards) для путей и импортируют ваши конфигурационные файлы в тот же контекст. Благодаря этому можно просто создавать конфигурации с помощью объявления конфигурационных записей в ранее загруженных файлах. Пакет никак не влияет на производительность Django и может применяться в любых проектах.
Вот пример минимальной конфигурации:
from split_settings.tools import optional, include
include(
'components/base.py',
'components/database.py',
'components/*.py',
# the project different envs settings
optional('envs/devel/*.py'),
optional('envs/production/*.py'),
optional('envs/staging/*.py'),
# for any local settings
optional(‘local_settings.py'),
)
Ошибка № 6. Приложение всё-в-одном, плохая структура приложения и некорректное размещение ресурсов
Любой Django-проект состоит из нескольких приложений. В терминологии Django приложение — это Python-проект, содержащий как минимум файлы __init__.py
и models.py
. В последних версиях Django models.py
больше не нужен, достаточно только __init__.py
.
Django-приложения могут содержать Python-модули, характерные для Django модули (представления, URL’ы, модели, админскую панель, формы, метки шаблонов и т. д.), статичные файлы, шаблоны, миграции базы данных, команды управления, модульные тесты и пр. Нужно разбивать своё монолитное приложение на маленькие многократно используемые приложения с простой логикой.
У вас должна быть возможность полностью описать назначение приложения одним-двумя короткими предложениями. Например: «Позволяет пользователю зарегистрировать и активировать по почте свой аккаунт».
Рекомендуется назвать папку проекта project и положить приложения в project/apps/
. Затем положить все зависимости приложений в собственные подпапки.
Примеры:
- Статичные файлы:
project/apps/appname/static/appname/
- Метки шаблона:
project/apps/appname/templatetags/appname.py
- Файлы шаблона:
project/apps/appname/templates/appname/
Всегда добавляйте имена приложений в виде префиксов в названия подпапок, потому что все статические папки объединяются в одну. И если два или более приложений имеют файл js/core.js
, то последнее приложение в settings.INSTALLED_APPLICATIONS
переопределит все предыдущие. Однажды я столкнулся с таким багом в своём проекте и потратил около шести часов на отладку, пока не сообразил, что другой разработчик переопределил мой static/admin/js/core.js
, потому члены команды реализовали кастомную админскую SPA-панель и дали своим файлам такие же имена.
Вот пример структуры для портального приложения, содержащего много ресурсов и Python-модулей.
root@c5b96c395cfb:/test# tree project/apps/portal/
project/apps/portal/
├── __init__.py
├── admin.py
├── apps.py
├── management
│ ├── __init__.py
│ └── commands
│ ├── __init__.py
│ └── update_portal_feeds.py
├── migrations
│ └── __init__.py
├── models.py
├── static
│ └── portal
│ ├── css
│ ├── img
│ └── js
├── templates
│ └── portal
│ └── index.html
├── templatetags
│ ├── __init__.py
│ └── portal.py
├── tests.py
├── urls.py
└── views.py
11 directories, 14 files
Благодаря такой структуре вы можете в любой момент экспортировать приложение в другой Python-пакет и снова его использовать. Можете даже опубликовать его в PyPi в качестве open source пакета или переместить в другую папку. У вас получится примерно такая структура проекта:
root@c5b96c395cfb:/test# tree -L 3
.
├── deploy
│ ├── chef
│ └── docker
│ ├── devel
│ └── production
├── docs
├── logs
├── manage.py
├── media
├── project
│ ├── __init__.py
│ ├── apps
│ │ ├── auth
│ │ ├── blog
│ │ ├── faq
│ │ ├── pages
│ │ ├── portal
│ │ └── users
│ ├── conf
│ ├── settings.py
│ ├── static
│ ├── templates
│ ├── urls.py
│ └── wsgi.py
└── static
└── admin
├── css
├── fonts
├── img
└── js
25 directories, 5 files
Конечно, реальный проект будет сложнее, но такая структура упрощает и делает более прозрачными многие аспекты.
Ошибка № 7. STATICFILES_DIRS и STATIC_ROOT смущают новичков в Django-разработке
Статичные файлы — это ресурсы (assets), которые не меняются по мере использования приложения. Например, JavaScript, CSS, изображения, шрифты и т. д. В Django они «накапливаются» в публичной директории в ходе развёртывания.
В режиме разработки — python manage.py runserver
— Django ищет статичные файлы с помощью настройки STATICFILES_FINDERS. По умолчанию он пытается найти запрошенный файл в папках, перечисленных в STATICFILES_DIRS. Если не находит, то ищет с помощью django.contrib.staticfiles.finders.AppDirectoriesFinder
, которая проверяет папку static
каждого установленного в проекте приложения. Такая схема позволяет писать многократно используемые приложения, поставляемые со своими собственными статичными файлами.
В production вы раздаёте статичные данные посредством отдельного веб-сервера, например nginx. Он ничего не знает о структуре приложений проекта Django или о том, по каким папкам распределены ваши статичные файлы. К счастью, Django предоставляет нам команду управления сбором статичных данных (collect static management command) — python manage.py collectstatic
, которая проходит по STATICFILES_FINDERS
и копирует все статичные файлы из папок static
приложений, а также из папок, перечисленных в STATICFILES_DIRS
, в заданную вами в STATIC_ROOT директорию. Это позволяет разрешать (resolution) ресурсы в виде статичных данных с помощью той же логики, что и у Django-сервера в режиме разработки, и собирать в одном месте для веб-сервера все статичные файлы.
Не забудьте выполнить collectstatic
в вашем production-окружении!
Ошибка № 8. Использование в production STATICFILES_STORAGE по умолчанию и загрузчиков Django-шаблонов
Давайте поговорим об управлении ресурсами (asset) production-окружения. Мы можем обеспечить наилучший UX, если воспользуемся политикой «у ресурсов не истекает срок действия» (assets never expire) (подробнее о ней можно почитать здесь). Это означает, что все наши статичные файлы должны быть закешированы браузерами на недели, месяцы или даже годы. Иными словами, пользователи должны лишь единожды скачивать ресурсы!
Классная идея, и её можно реализовать всего в несколько строк в nginx-конфигурации для нашей папки со статичными файлами. Но что насчёт проверки актуальности кеша? Если пользователь лишь один раз скачивает наш ресурс, то что делать в том случае, если вы обновите логотип, шрифты, JavaScript или цвет текста в меню? Для решения этой задачи вам нужно при каждом развёртывании генерировать уникальные URL’ы и имена для каждого статичного файла!
Для этого можно использовать ManifestStaticFilesStorage в качестве STATICFILES_STORAGE
(будьте осторожны, хеширование включается только в режиме DEBUG=false
) и выполнить команду collectstatic
. Это приведёт к снижению количества запросов ресурсов у вашего production-сайта и сделает его отрисовку гораздо быстрее.
Клёвая фича Django — закешированный загрузчик шаблона. Он не перезагружается и парсит файлы шаблона при каждой его отрисовке. Парсинг шаблона — очень дорогая операция, она требует много вычислительных ресурсов. По умолчанию Django-шаблоны парсятся при каждом запросе, а это плохо, особенно в production, где за короткий промежуток времени могут обрабатываться тысячи запросов.
В разделе конфигурации cached.Loader можно найти хороший пример и подробности решения проблемы. Не используйте загрузчик в режиме разработки, потому что он не перезагружает отпарсенные шаблоны из файловой системы. Вам понадобится перезапускать свой проект, используя python manage.py startapp
, при каждом изменении шаблона. При разработке это может раздражать, зато идеально для production-окружения.
Ошибка № 9. Чистый Python для утилит или скриптов
У Django есть отличная фича — команды управления. Используйте их вместо изобретения велосипеда в виде написания скриптов на чистом Python для утилит вашего проекта.
Также обратите внимание на пакет Django Extensions, представляющий собой коллекцию кастомных расширений для Django. Возможно, кто-то уже реализовал ваши команды! Существует много распространённых целевых команд.
Ошибка № 10. Велосипедостроение
Для Django и Python есть тысячи готовых решений. Обратитесь к поисковикам, прежде чем писать что-то, что вовсе не уникально. Вероятно, уже есть подходящее решение.
Не надо усложнять. Сначала — гуглим! Установите найденный качественный пакет, сконфигурируйте, расширьте и интегрируйте в свой проект. И если есть возможность, внесите свой вклад в open source.
Вот вам для начала список моих собственных пакетов для Django:
- Django Macros URL: с помощью макросов облегчает написание (и чтение) URL-паттернов в Django-приложениях.
- Django Templates Names: маленький миксин, помогает легко стандартизировать имена ваших CBV-шаблонов.
- Django Split Settings: позволяет распределить Django-настройки по нескольким файлам и директориям. Легко переопределяет и модифицирует настройки. Использует подстановки (wildcards) в путях файлов и помечает файлы настроек как опциональные.
Don’t repeat yourself (DRY)!
Я сторонник DRY-концепции, поэтому создал Django skeleton — удобный инструмент с рядом приятных функций уже из коробки:
- Docker-образы для разработки/production, управляемые docker-compose, что позволяет легко оркестрировать списком контейнеров.
- Простой Fabric-скрипт для развёртывания в production.
- Конфигурация для пакета Django Split Settings с настройками базы и локальных источников.
- Интегрированный в проект Webpack — при выполнении команды
collectstatic
Django соберёт только папку dist. - Сконфигурированы все основные Django-настройки и фичи вроде кешируемых в production Django-шаблонов, хешированных статичных файлов, интегрированного тулбара для отладки, журналирования и т. д.
Это готовый к использованию Django-скелет для вашего следующего проекта, создаваемого с нуля. Надеюсь, он сэкономит вам кучу времени. Webpack имеет минимальную базовую конфигурацию, но также в него с помощью SASS установлены заранее сконфигурированные для обработки файлы .scss.