Создаем свое первое веб-приложение при помощи Django. Разворачиваем Django приложение в production на примере Telegram бота

  • Перевод

В этом руководстве мы рассмотрим основные ошибки 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/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.

Теги:

  • python
  • django
  • никто не читает теги
Добавить метки

Данная заметка это продолжение статьи про написание telegram бота , в ней я постараюсь максимально подробно осветить тему разворачивания (deploy) полноценного, хотя и маленького, Django приложения в production среде на ОС Linux, Ubuntu 14.04 LTS. К концу статьи у нас будет полноценный telegram бот, крутящийся в вебе и принимающий команды от пользователей этого мессенджера.

Чему вы научитесь после прочтения заметки:

  • Разворачивать Django приложение (да и любое WSGI приложение) на хостинге Digital Ocean в среде Linux
  • Работать с веб-серверами nginx и gunicorn
  • Управлять процессами, используя утилиту supervisord
  • Настраивать virtualenv с помощью pyenv
  • Автоматически запускать веб-приложение даже после перезагрузки сервера

В сентябре 2015 года мы проводили Python митап в Алматы на котором я выступал с . Во время выступления я вкратце описал веб эко-систему Python и сделал краткий обзор популярного инструментария. К сожалению, формат митапа не предусматривал детальный разбор темы, поэтому новичкам в этой области обычно приходится дальше копаться самостоятельно. Сегодня я постараюсь восполнить этот пробел и немного углубиться в "горячую" тему деплоя веб приложений на Python. Несмотря на то, что в статье речь будет идти о Django приложении, описываемые рецепты будут актуальны и для других веб-проектов, разработанных на Python с использованием WSGI-совместимых фреймворков (Flask, Bottle, Pyramid, Falcon, web2py и так далее).

В заметке я буду делать деплой на виртуальном хостинге от Digital Ocean. Если вы зарегистрируетесь по этой ссылке , то после подтверждения платёжных данных, счёт вашего аккаунта сразу пополнится на $10, которые можно потратить на создание маленьких дроплетов (виртуальных серверов) и потренироваться в разворачивании веб проектов на Python. Сразу скажу, что вам необязательно всё делать на удалённой машине и вообще использовать хостинг-провайдер, можно обойтись и локальной виртуалкой, например, используя VirtualBox и (но в таком случае невозможно будет установить webhook).

Создание виртуального сервера

Как я ранее уже упоминал, деплой мы будет производить на одном из виртуальных серверов DigitalOcean с его мощным API:)

Создаём дроплет, нажимая на "Create droplet" в правом верхнем углу панели управления:

Выбираем самый минимальный тариф за 5 долларов в месяц с операционной системой Ubuntu 14.04.4 LTS на борту будущей виртуальной машины.

В качестве дата-центра я практически всегда выбираю Frankfurt, так как до него у меня самый лучший пинг. После заполнения всех необходимых полей, нажимаем кнопку "Create". Дроплет создаётся в течение 60 секунд после которых на почту поступает вся необходимая для доступа информация о новой виртуальной машине: IP адрес, логин и пароль.

Настройка сервера

Обновляем пакеты:

# apt-get update # apt-get -y upgrade

# adduser django # adduser django sudo

Заходим под новым юзером django на сервер, и все остальные команды выполняем из под данного юзера.
Устанавливаем необходимый арсенал для настройки виртуального окружения через Pyenv и сборки самой последней версии Python (2.7.11).

$ sudo apt-get install -y build-essential $ sudo apt-get install -y python-dev libreadline-dev libbz2-dev libssl-dev libsqlite3-dev libxslt1-dev libxml2-dev $ sudo apt-get install -y git

После этого ставим сам Pyenv. Подробнее о том что такое Pyenv и как его настроить можно прочитать :
Устанавливаем Python самой последней версии (Python 2.7.11):

$ pyenv install 2.7.11 Downloading Python-2.7.11.tgz... -> https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tgz Installing Python-2.7.11... Installed Python-2.7.11 to /home/django/.pyenv/versions/2.7.11

Выполнение команды займёт некоторое время (скрипт скачает Python и скомпилирует его из исходников). Устанавливая отдельный интерпретатор питона мы тем самым никак не влияем на работу системного, более того, в последней LTS версии Ubuntu (14.04) используется версия 2.7.6, в которой существует ряд серьёзных уязвимостей, включая баг с SSL, а также отсутствует поддержка TLS 1.2

Клонируем репозиторий с Django проектом:

$ cd ~ $ git clone https://github.com/adilkhash/planetpython_telegrambot.git $ cd planetpython_telegrambot/

$ pyenv virtualenv 2.7.11 telegram_bot $ pyenv local telegram_bot

Ставим зависимости через менеджер пакетов pip.

Pip install -r requirements.txt

Django приложение, написанное в первой части, претерпело незначительные изменения. В частности я перенёс изменяемые части кода в специальный.env файл, используя библиотеку django-environ. Ознакомиться с изменениями можно по этой ссылке .

Создаём.env файл из шаблона и заполняем необходимые настройки.

$ cd blog_telegram && mv .env-template .env && vi .env

В частности необходимо изменить режим DEBUG на False, прописать токен для Telegram бота и указать дополнительный хост через запятую в ALLOWED_HOSTS. В моём случае ALLOWED_HOSTS выглядит вот так:

ALLOWED_HOSTS=127.0.0.1,bot.сайт

То есть я завёл дополнительный поддомен на котором и будет крутиться Telegram бот.

Настройка SSL сертификата

В прошлой статье я писал о том, что в случае использования API вызова setWehook, хосту необходимо иметь валидный SSL сертификатом (Telegram позволяет использовать также самоподписанные сертификаты). Сертификат мы будет создавать через бесплатный сервис выдачи SSL сертификатов Let"s Encrypt .

$ cd ~ && git clone https://github.com/letsencrypt/letsencrypt $ cd letsencrypt/ $ ./letsencrypt-auto certonly --standalone -d bot.сайт

Необходимо будет указать некоторые настройки и согласиться с условиями предоставления услуг. После успешного выполнения команд, сертификаты будут находиться в /etc/letsencrypt/live/bot.сайт/

Настройка Nginx

Теперь пора поставить популярный HTTP сервер nginx который в нашем случае будет выполнять роль проксирующего (принимать запросы от клиентов и передать их дальше следуя инструкциям в конфигурационном файле).

$ sudo apt-get install -y nginx $ cd /etc/nginx/sites-available/ $ sudo nano telegram_bot.conf

Заполняем новый файл telegram_bot.conf следующим содержимым:

Server { listen 80; listen 443 ssl; server_name bot..сайт/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/bot..pem; location / { proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_pass http://localhost:8001/; } }

ВНИМАНИЕ : Не забудьте заменить хост bot.сайт на свой собственный.

Прописываем наш новый конфиг в настройки nginx и перезагружаем его, чтобы изменения вступили в силу:

$ cd /etc/nginx/sites-enabled/ $ sudo ln -s ../sites-available/telegram_bot.conf telegram_bot.conf $ sudo service nginx restart

Что мы только что сделали?

  • Прописали валидный SSL серификат для нашего сайта
  • Все запросы, поступающие на хост, будут проксироваться на наше будущее Django приложение, которое в свою очередь, должно крутиться на 8001 порту.
  • Передаём дополнительные HTTP заголовки в каждом запросе (хост, IP адрес клиента, схему https/http и так далее). Более подробно про настройки nginx можно прочитать .

Чтобы проверить успешность наших настроек, можно запустить django приложение через тестовый сервер командой runserver на 8001 порту и зайти на сайт:

$ cd ~/planetpython_telegrambot/ $ python manage.py runserver 8001

Открываем браузер и видим (я сразу открыл через https):

URL Not Found это нормальное явление, так как у нас задан всего 1 валидный URL для непосредственной работы с Telegram - /planet/bot// (не считая настройки Django админки).

Настройка Gunicorn через Supervisor

Пора приступить к настройке production-ready HTTP сервера Gunicorn , который, кстати, полностью написан на языке Python и хорошо зарекомендовал себя в реальном бою (к слову, во всех "живых" проектах я использую именно эту связку: nginx+gunicorn)

Что такое Supervisor?

Supervisor это утилита процесс-менеджер. Она "следит за здоровьем" ваших процессов-демонов и в случае их падения, старается снова их поднять. Если в ходе работы Gunicorn "падает" (системная ошибка, не та фаза луны и так далее), Supervisor старается его снова "поднять", таким образом работоспособность сайта не страдает. К слову, у меня в планах есть идея написать небольшую заметку про эту утилиту, так сказать Supervisor Advanced Usage. Стоит отметить, что все процессы, запущенные в Supervisor должны работать в foreground режиме, чтобы утилита понимала когда что-то идёт не по плану.

Для начала составим конфигурационный файл для запуска Gunicorn внутри Supervisor. Его содержимое выглядит вот так:

Command=/home/django/.pyenv/versions/telegram_bot/bin/gunicorn blog_telegram.wsgi:application -b 127.0.0.1:8001 -w 1 --timeout=60 --graceful-timeout=60 --max-requests=1024 directory=/home/django/planetpython_telegrambot/ user=django redirect_stderr=True stdout_logfile=/tmp/gunicorn.log stderr_logfile=/tmp/gunicorn_err.log autostart=true autorestart=true startsecs=10 stopwaitsecs=10 priority=999

Сохраняем файл под именем gunicorn.conf (~/planetpython_telegrambot/gunicorn.conf ). К слову, Gunicorn прописан в зависимостях нашего проекта (requirements.txt ) и так как мы его уже установили в наше окружение, то узнать путь исполняемого файла можно выполнив команду внутри активированного виртуального окружения (активация происходит автоматически при переходе в директорию веб-приложения из-за наличия там файла .python-version , созданного через pyenv local):

$ pyenv which gunicorn

Содержимое конфигурационного файла для supervisord:

File=/tmp/telgram_bot_supervisord.sock logfile=/tmp/telgram_bot_supervisord.log logfile_maxbytes=50MB logfile_backups=10 loglevel=info pidfile=/tmp/telgram_bot_supervisord.pid nodaemon=false minfds=1024 minprocs=200 supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface serverurl=unix:///tmp/telgram_bot_supervisord.sock files = /home/django/planetpython_telegrambot/gunicorn.conf

Сохраняем в ~/planetpython_telegrambot/supervisord.conf

$ supervisord

Запуск должен пройти без каких либо ошибок. Чтобы узнать статус текущих процессов, запускаем утилиту supervisorctl:

$ supervisorctl gunicorn RUNNING pid 20901, uptime 0:04:18 supervisor>

Для получения помощи, можно выполнить команду help. А для получения информации о команде - help . Например:

Supervisor> help stop stop Stop a process stop:* Stop all processes in a group stop Stop multiple processes or groups stop all Stop all processes supervisor>

После успешного запуска supervisor, сайт должен быть доступен онлайн.

Автозапуск веб-приложения при перезагрузке

А что будет, если наш виртуальный сервер внезапно перезагрузится? (сбой в дата-центре, неполадки на хост машине, криворукий админ накосячил и т.д.). В случае такого сценария, сейчас наше приложение не будет запущено автоматически. Чтобы это исправить необходимо приложить ещё немного усилий дабы написать небольшой скрипт, который мы успешно поместим в механизм автозагрузки ОС Ubuntu (Debian-like дистрибутивов).

Доводилось ли вам слышать про так называемые upstart файлы? Именно написанием одного из них мы сейчас и займёмся. К слову, на текущий момент Upstart признана устаревшей и в новых версиях ОС на базе Linux планируется полный переход на systemd.

Description "Supervisor Telegram bot django app starting handler" start on runlevel stop on runlevel [!2345] respawn setuid django setgid django chdir /home/django/planetpython_telegrambot/ exec /home/django/.pyenv/versions/telegram_bot/bin/supervisord

Файл должен быть помещён в /etc/init/ (в моём случае я дал ему имя telegram_bot.conf). Если ранее все запуски не вызывали проблем, то после рестарта системы, приложение автоматически будет запущено:

$ sudo shutdown -r now

Теперь необходимо прописать наш URL на стороне Telegram используя вызов API метода setWebhook:

Import telepot bot_token = "BOT_TOKEN" bot = telepot.Bot(bot_token) bot.setWebhook("https://bot..format(bot_token=bot_token))

На этом настройка бота закончена. Посылаем команды нашему боту @PythonPlanetBot и получаем адекватные ответы:)

  • Tutorial

Введение

Многие начинающие веб разработчики размышляют о том, где бы разместить свое творение. Обычно для этих целей применяются машины под управлением *NIX подобных систем. Мой выбор остановился на Raspberry PI, поскольку малинка:
  • работает под управлением полноценного Linux,
  • долгое время лежит на столе и пылится.
Я хочу рассказать о том, как настроить сервер, работающий в сети с динамическим внешним IP адресом. Для запуска крупных проектов такое решение не годится, а для демонстрации своего портфолио и персонального применения вполне подойдет.

Нам понадобятся

  1. Raspberry PI модели B, B+ или Raspberry PI 2 (поскольку на платах этих моделей имеется Ethernet) с установленной Raspbian и активированным SSH сервером. О настройке можно почитать , или . Помимо Raspian для малинки существует большое количество альтернативных дистрибутивов . Тут, как говорится, «на любой вкус и цвет».
  2. Рабочее Django приложение.
  3. Роутер с поддержкой DDNS. Этот пункт не обязателен, поскольку DDNS можно настроить на самой малинке.
Я буду работать с малинкой модели B+.

Подготовка

На малинке установлена Raspbian 7.8.
Для начала необходимо найти малинку в сети, чтобы подключиться к ней по ssh.
nmap -sP 192.168.1.1/24 | grep raspberry

В моем случае в сети две малинки, одна из которых моя с IP адресом 192.168.1.100. В некоторых сетях nmap не показывает сетевые имена устройств.


В этом случае найти raspberry pi можно по MAC-адресу, который имеет префикс B8:27:EB.
sudo nmap -sP -n 192.168.1.1/24 | grep -B 2 B8:27:EB


Параметр -B для grep определяет какое количество предшествующих строк следует вывести.

Подключаемся к малинке по ssh.
ssh [email protected]


Для начала разгоним малинку до 1 ГГц с помощью raspi-config .
Устанавливаем питоновский менеджер пакетов
sudo apt-get install python-pip

Переходим к установке необходимых пакетов. В моем web приложении используется СУБД MySQL. В качестве Frontend и Backend используется nginx и gunicorn соответственно.
sudo apt-get install nginx gunicorn mysql-client mysql-server python-mysqldb


В процессе установки mysql необходимо ввести данные для root пользователя СУБД. python-mysqldb - драйвер, необходимый при работе с моделями в Django. Django установим из питоновских репозиториев.
sudo pip install django

На момент написания статьи актуальные версии nginx и gunicorn в репозиториях для малинки 1.2.1 и 0.14.5 соответственно. Версия MySQL для малинки 5.5. Так же для работы с Django необходимо установить SciPy .
sudo apt-get install python-scipy

Nginx 1.2.1 устарел. Более новый можно собрать из исходников . Свежий gunicorn можно установить из питоновских репозиториев.

Настройка сервера

Размещаем web-приложение на малинке (например в /home/pi).
Если у Вас есть рабочие конфиги, то достаточно их скопировать в соответствующие директории:
  • для nginx /etc/nginx/sites-enabled/
  • для gunicorn /etc/gunicorn.d/
C nginx ничего сложного нет. Я бы хотел обратить внимание на настройки для gunicorn.
CONFIG = { "mode": "wsgi", "working_dir": "/home/pi/project", #"working_dir": "/home/pi/project/project", "user": "www-data", "group": "www-data", "python": "/usr/bin/python", "args": ("--bind=127.0.0.1:8081", "--workers=5", # 5 достаточно для малинки "--graceful-timeout=60", "--timeout=60", #"--debug", #"wsgi:application", "project.wsgi",), }

Если working_dir (путь к файлу wsgy.py) указать "/home/pi/project/project" , а в args указать "wsgi:application" , то на малинке воркеры сначала стартуют, потом умирают без указания причины (под Ubuntu, например, gunicorn работает с обоими вариантами настроек).

Перенос MySQL

Дамп имеющейся БД можно сделать с помощью утилиты mysqldump .
mysqldump -u root -p dbname > dbname.sql

Полученный файл состоит из набора SQL-инструкций, которые восстанавливают структуру, а так же информацию, хранимую в базе данных.


На малинке создаем базу данных. Запускаем mysql shell.
mysql -u root -p

Добавляем новую базу данных.
mysql> create database dbname character set utf8 collate utf8_general_ci; mysql> grant all privileges on dbname.* to someusr@localhost identified by "somepassword";
Восстанавливаем данные с дампа. При размере дампа в 162 Мб время восстановления составило около 10 минут.
mysql -u root -p dbname < dbname.sql

Следует отметить, что базы данных лучше хранить на внешнем накопителе, иначе micro SD карта может быстро придти в негодность из-за частых операций записи. Как это сделать можно почитать . Конфиг mysql расположен по пути /etc/mysql/my.cnf

Проверка

Перезапускаем nginx и gunicorn. Если все настроено верно, можно открыть главную страницу.


Переходим к нагрузочному тестированию. Установим apache benchmark.
sudo apt-get install apache2-utils

Протестируем в 4 потока 1000 запросами Raspberry PI модель B+.
ab -c 4 -n 1000 http://192.168.1.100/

Vladislav@vladislav-N53SV:~$ ab -c 4 -n 1000 http://192.168.1.100/ This is ApacheBench, Version 2.3 <$Revision: 1528965 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.1.100 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.8.0 Server Hostname: 192.168.1.100 Server Port: 80 Document Path: / Document Length: 24839 bytes Concurrency Level: 4 Time taken for tests: 1309.607 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 25018000 bytes HTML transferred: 24839000 bytes Requests per second: 0.76 [#/sec] (mean) Time per request: 5238.429 (mean) Time per request: 1309.607 (mean, across all concurrent requests) Transfer rate: 18.66 received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.1 1 1 Processing: 4924 5237 91.4 5227 6419 Waiting: 4919 5227 91.3 5217 6403 Total: 4925 5238 91.4 5228 6420 Percentage of the requests served within a certain time (ms) 50% 5228 66% 5245 75% 5255 80% 5265 90% 5296 95% 5335 98% 5382 99% 5667 100% 6420 (longest request)
Запросы идут медленно, поскольку большую часть времени запроса занимает работа с БД. Ко мне недавно пришла Raspberry PI 2 модель B. Посмотрим на что она способна c теми же настройками и данными.
vladislav@vladislav-N53SV:~$ ab -c 4 -n 1000 http://192.168.1.14/ This is ApacheBench, Version 2.3 <$Revision: 1528965 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.1.14 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.8.0 Server Hostname: 192.168.1.14 Server Port: 80 Document Path: / Document Length: 24838 bytes Concurrency Level: 4 Time taken for tests: 170.083 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 25017000 bytes HTML transferred: 24838000 bytes Requests per second: 5.88 [#/sec] (mean) Time per request: 680.330 (mean) Time per request: 170.083 (mean, across all concurrent requests) Transfer rate: 143.64 received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 0.1 1 1 Processing: 569 678 104.6 650 1338 Waiting: 567 676 104.1 647 1334 Total: 569 679 104.6 651 1338 Percentage of the requests served within a certain time (ms) 50% 651 66% 682 75% 708 80% 727 90% 796 95% 890 98% 1045 99% 1138 100% 1338 (longest request)
Raspberry PI 2 обрабатывает запросы в среднем в 6,16 раз быстрее. Разработчики малинки не обманули.

Настройка DDNS

Настроить DDNS можно на роутере или на самой малинке. Я выбираю No IP , поскольку пользуюсь им несколько лет. Рассмотрим бесплатное использование.

Регистрация хоста

Если у вас есть учетная запись - проходим авторизацию, иначе регистрируемся . После авторизации попадаем сюда .


Кликаем AddHost и заполняем форму.


Внизу кликаем кнопку AddHost
Хост добавлен. Справа от имени хоста отображается внешний IP адрес Вашей сети.


Настраиваем DDNS на роутере
Для примера я настрою DDNS на ASUS RT-N56U с прошивкой от padavan версии 3.4.3.9-091. Открываем в страницу меню роутера
(например 192.168.1.1). WAN->DDNS.


Выбираем сервис no-ip.com, указываем регистрационные данные, а так же наш добавленный хост (technopark-test.ddns.net).
Остальные параметры выставляем по собственному желанию.


Теперь при смене внешнего IP адреса наше приложение остается доступным в сети.
Настройка переадресации портов
Нам нужно, чтобы при обращении к хосту малинка отдавала веб приложение. Роутер занимается перенаправлением входящих пакетов, пришедших из вне с порта X на внутренный порт Y. В меню роутера переходим WAN->Переадресация портов. Необходимо перенаправлять внешний 80 порт на 80 порт малинки. Добавим новое правило и применим изменения.

Теперь малинка обрабатывает все приходящие пакеты на 80 порту. Проверим, введя в адресной строке браузера хост, полученный в No IP.


Теперь наше веб приложение доступно для пользователей сети Интернет.
Настраиваем DDNS на малинке
Этот вариант не подходит, если малинка имеет частный IP , поскольку она будет оправлять свой локальный IP адрес на на сервис No IP. Это еще один способ узнать IP адрес малинки локальной сети. Установим DDNS клиент.
sudo apt-get install ddclient

Во время установки необходимо выбрать сервис. Выбираем other и вводим dynupdate.no-ip.com, протокол dyndns2, имя пользователя, пароль, интерфейс - eth0, имя хоста.


Для проверки я выставил интервал обновления IP в 60 секунд. В файле /etc/default/ddclient необходимо выставить значение daemon_interval=«60».

Десерт

Моя малинка давно лежала на столе и пылилась, вместе с tm1638 и , выводя показания температуры и влажности в помещении и прочей информации.
Все же мне было интересно попробовать управлять GPIO Raspberry PI из django. Я разработал простое web приложение, которое объединило мои ранние наработки. Оно позволяет посмотреть температуру и влажность, измеренные с помощью DHT11, некоторую полезную информацию, управляет 8-ми релейным модулем (который может быть использован для управления электроприборами) и отправляет текст на tm1638.

Для управления GPIO необходимо запускать сервер с правами root. Это потенциальная уязвимость.
Полноценное использования web приложения предполагает работу сервера без прав суперпользователя, настройку https, добавление возможности администрирования учетных записей, ведение логов, разделение доступа к управляемым устройствам, работу электроприборов по расписанию и многое другое.
Впрочем это уже совсем другая история статья.

Заключение

Имея Raspberry PI моделей B, B+ или Raspberry PI 2, power bank , а так же «open» Ethernet jack получаем компактный сервер, который можно использовать для демонстрации своих наработок. Настройка сервера для Django приложений на Raspberry PI под управлением Raspbian мало чем отличится от любой другой сборки Linux. Пакеты в репозиториях могут быть устаревшими. Для работы с новыми версиями можно вручную собирать программы из исходников.
Добавить метки

Какая оптимальная структура для ваших Django приложений, настроек и других ассоциированных директорий?

Когда вышла Django 1.4 она включала обновленую структуру проекта, которая прошла долгий путь, чтобы стать основной, но здесь, собраны некоторые улучшения, чтобы сделать структуру лучше.

Подобный вопрос нам задают постоянно, поэтому я хочу потратить немного времени и рассказать все наше отношение к этому, чтобы всех клиентов можно было отправлять к этому документу. Эта заметка написана для Django 1.7.1, но может быть легко применена для всех Django версий выше 1.4.

Почему данная структура лучше

  1. Позволяет вам держать, пересобирать и переисопльзовать индивидальные Django приложения для использования в других проектах. Ведь не всегда создаваемое приложение делается реиспользуемым, но в будущем, может вырасти в такое. Построение проекта описываемым способом, позволяет писать реиспользуемые приложения сразу же, а не только, когда потребуется.
  2. Поощряет разработку реиспользуемых приложений
  3. Индивидуальных настройки для каждого окружения. Никаких больше “if DEBUG ==True” в едином монолитном файле настроек. Это позволяет легко видеть, какие настройки общие и что переопределяется на каждом окружении.
  4. Индивидульный список pip зависимостей для каждого окружения.
  5. Шаблоны проекта и статические файлы, если требуется, могут переопределять значения по умолчанию уровня приложений.
  6. Небольшие более детальные тестовые файлы, которые легче для чтения и понимания.

Предположим, у вас есть 2 приложения blog и users и 2 окружения dev и prod , значит, ваш проект должен иметь следующий вид:

myproject/ manage.py myproject/ __init__.py urls.py wsgi.py settings/ __init__.py base.py dev.py prod.py blog/ __init__.py models.py managers.py views.py urls.py templates/ blog/ base.html list.html detail.html static/ … tests/ __init__.py test_models.py test_managers.py test_views.py users/ __init__.py models.py views.py urls.py templates/ users/ base.html list.html detail.html static/ … tests/ __init__.py test_models.py test_views.py static/ css/ … js/ … templates/ base.html index.html requirements/ base.txt dev.txt test.txt prod.txt

Продолжение этой статьи расскажет, как привести свой проект к такой структуре и почему она лучше.

Текущая структура по умолчанию

Мы начнем с создания нового проекта с именем foo , да, я знаю, что это очень креативное название. Мы предполагаем здесь, что дальше проект будет расположен на домене foo.com , но почему название проекта должно точно повторять домен, ведь проект всеравно будет жить и без всяких подобных требований.

Если вы запустите свой проект с помощью команды django-admin.py startproject foo , вы получите следующую структуру:

foo/ manage.py foo/ __init__.py settings.py urls.py wsgi.py

Эта схема очень хороша для старта. У нас есть корневая директория foo , которая содержит наш manage.py и директорию проекта foo/foo . Эту директорию можно добавить в свою систему контроля версия, например в git.

Вы можете подумать, что директория foo/foo начало проекта, где все кроме этого это Django приложения или вспомогательные файлы относящиеся к проекту.

Исправляем настройки

Наша первая миссия, это исправить ваш плохой файл настроек. Мы показали данную архитектуру нашим клиентам и были впечатлены, как мало людей знают, что такое можно реализовать. Я виню в этом тот факт, что каждый знает, что файл настроек это просто Python код, но они не думают о нем, как о Python коде.

И так, давайте исправим наши настройки. Для нашего проекта foo реализуем схему с 4 окружениями: dev, stage, jenkins, и production. Давайте дадим каждому окружения свой собственный файл. Процесс для этого следующий:

  1. В foo/foo создадим директорию settings и пустой файл __init__.py внутри нее.
  2. Перенесем foo/foo/settings.py в foo/foo/settings/base.py
  3. Создадим индивидуальные файлы dev.py , stage.py , jenkins.py , и production.py в foo/foo/settings/ . Каждый из этих файлов должен содержать следующее

from base import *

Так, почему это важно? Для локальной разработки вам требуется DEBUG =True , но вы также, можете случайно выкатить это и в продакшен, поэтому просто откройте foo/foo/settings/production.py и после первой строки импорта вставьте DEBUG =False . Теперь, ваш продакшен сайт защищен от такой случайности.

Что еще вы можете настроить? Достаточно очевидно, что для каждого окружения вы захотите иметь различные базы данных, например на разных хостах. Для этого, просто внесите эти настройки в соответствубщие файлы настроек.

Использование этих настроек

Использование этих настроект очень легкое и не зависит от того, какой метод вы предпочитаете. Для использования настроек определенного окружения, вы должны всего лишь:

export DJANGO_SETTINGS_MODULE = “foo.settings.jenkins”

И бум, вы теперь используете jenkins настройки.

Или вы можете предпочесть передачу настроек через коммандную строку:

./manage.py migrate -settings= foo.settings.production

Или используя gunicorn:

gunicorn -w 4 -b 127.0.0.1:8001 -settings= foo.settings.dev

Что еще должно быть настроено?

Другая часто используемая уловка с Django настройками, это изменить тип некоторых настроек с tuple на list. Для примера, INSTALLED_APPS изменить с:

INSTALLED_APPS = ( … )

INSTALLED_APPS = [ … ]

В foo/settings/base.py мы теперь можем проще добавлять и удалять приложения основываясь на конкретном файле настроек для текущего окружения. Для примера, возможно вам требуется модуль django-debug-toolbar установленным только в dev окружении.

Этот трюк также часто используется с настройками TEMPLATE_DIRS и MIDDLEWARE_CLASSES .

Еще один часто используемый трюк, это разбить ваши приложения на 2 списка, первый это глобальные зависимости, второй - специфичные для данного проекта. Как, например, здесь:

PREREQ_APPS = [ ‘ django . contrib . auth ’ , ‘ django . contrib . contenttypes ’ , … ‘ debug_toolbar ’ , ‘ imagekit ’ , ‘ haystack ’ , ] PROJECT_APPS = [ ‘ homepage ’ , ‘ users ’ , ‘ blog ’ , ] INSTALLED_APPS = PREREQ_APPS + PROJECT_APPS

Почему это часто используется? Во первых, это помогает лучше различать Django core компоненты, сторонние приложения и внутренние, специфичные для данного проекта. Тем не менее, PROJECT_APPS часто управляет списком специфичных пакетов, для вещей таких как: тестирование и покрытие кода тестами. Вы имеет список с вашими приложениями, поэтому можете легко и автоматизированно убедиться, что все тесты были запущены только для них, а не для каких-то посторонних модулей.

Исправляем зависимости

Большинство проектов содержат лишь один файл requirements.txt , который ставит зависимости примерно так:

pip install -r requirements.txt

Этого достаточно для маленьких проектов, но малоизвестная возможность requirements файлов это использование ключа -f для включения других файлов:

R base.txt pytest == 2.5.2 coverage == 3.7.1

Я признаю, что это не огромная выгода, но это позволяет разделить зависимости на каждое окружение. Но для успокоения совести скажу, что это позволяет увеличить скорость установки пакетов, ведь вам не требуется стаивть на продакшен инсталяции пакеты, которые не будут использоваться.

Тестовые файлы

Почему мы разделяем тесты так сильно? Главная причина, если вы пишете достаточное количество тестов в одном файле tests.py для каждого приложения, то в конце концов он станет огромным и не поддерживаемым. Это плохо для читабельности, но также это простой факт, что вы тратите много времени на пролистывание текста.

В итоге, вы уменьшите количество мердж конфликтов при работе в команде, что является положительной чертой. Маленькие файлы - ваши друзья.

Ссылки (URLs)

Для маленьких проектов заманчиво определять все ссылки в одном файле foo/urls.py для сохранения их в одном месте. Как бы то ни было, если ваша цель это ясность и реиспользование, вы должны определять ссылки в каждом приложении и загружать их в корневом файле. Вместо:

urlpatterns = patterns (‘’ , url (r ’ ^ $’ , HomePageView . as_view (), name = ‘ home ’ ), url (r ’ ^ blog / $’ , BlogList . as_view (), name = ‘ blog_list ’ ), url (r ’ ^ blog / (? P < pk > \d + ) / $’ , BlogDetail . as_view (), name = ‘ blog_detail ’ ), … url (r ’ ^ user / list / $’ , UserList . as_view (), name = ‘ user_list ’ ), url (r ’ ^ user / (? P < username > \w + ) / $’ , UserDetail . as_view (), name = ‘ user_detail ’ ), )

вы должны использовать:

urlpatterns = patterns (‘’ , url (r ’ ^ $’ , HomePageView . as_view (), name = ‘ home ’ ), url (r ’ ^ blog / ‘ , include (‘ blog . urls ’ )), url (r ’ ^ user / ‘ , include (‘ user . urls ’ )), )

Шаблоны и статические файлы

Использование templates/ и static/ директорий на каждое приложение дает способность к реиспользованию этого приложения в другом проекте как есть, без изменений.

В этом случае, приложение предоставляет шаблоны по умолчанию, а также различные вспомогательные файлы, такие как специальные Javascript файлы и поставляется все это в одном пакете.

Также, это дает нам возможность переопределить шаблоны на каждый проект базируясь на директории foo/templates/ . При добавлении шаблона templates/blog/detail.html мы перезаписываем или скрываем шаблон по умолчанию blog/templates/blog/detail.html .

Переиспользование Django приложений

Допустим, вы используете предлагаемую структуру проекта некоторое время, однажды, вы поймете, что ваш новый проект нуждается в блоге и один из ваших проектов прекрасно к этому подходит. Вы скопируете файлы в … НЕ ПРАВИЛЬНО! Теперь вы имеете две копии приложения. Исправления ошибок или новые функции в одном, будут вручную переноситься между проектами если предположить, что вы всегда помните про это.

Вместо этого, сделайте новый репозиторий для вашего блога и вставьте в него директорию foo/blog/ . И настройте, чтобы ваш существующий проект foo и новый проект, для установки блога через pip.

Они смогут использовать различные версии приложения, если это важно, или использовать всегда последнюю версию для получения всех исправлений и новых функций, как и разработчик. Вы все еще можете переопределять шаблоны и статические файлы в каждом проекте отдельно.

Дополнительные ресурсы

Наши друзья Дэнни и Аудрей из CartWheel Web напомнили нам про Cookie Cutter и специальный cookiecutter-django от Дэнни, мощная утилита для создания начального проекта, быстро и повторяемо.

Кроме того, если вы ищете все про Django уловки и рекомендации, вы не можете пройти мимо книги Two Scoops of Django: Best Practices For Django 1.6 которую мы рекомендуем всем нашим клиентам.

Обратная связь

Мы надеемся, вы нашли данное улучшение архитектуры проекта полезным. Если вы нашли какой-то баг или имеет предложение иил просто хотите пообщаться, пишите нам. Спасибо за прочтение.

В этом руководстве мы рассмотрим основные ошибки 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), автосоздание путей шаблонов на основе структур приложения, интегрирование умного кеширования и многое другое.