Fork me on GitHub
11/9/2006

Организация кода в Django-проекте

Джеймс Бенетт написал такую статью, что ее можно смело и настойчиво рекомендовать к прочтению всем разработчикам, использующим Django. В первую очередь это касается тех, кто только начал знакомиться с этим фреймворком. Мне так понравилась статья, что я не стал ее пересказывать, а перевел полностью. Я не стал обрамлять ее в blockquote, но Вам, надеюсь, и так понятно, что местоимение "я" следует заменять на "Джеймс Бенетт", а не на "автор блога".

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

Проекты vs. приложения

На самом деле, это два отдельных вопроса (хотя и близких по смыслу), но понимание различий в Django между "проектом" (project) и "приложением" (application) - большой шаг к хорошей структуре кода. Грубо говоря, вот что означают эти термины:

  • Приложение пытается предоставить единый, в некоторой степени самодостаточный, набор функций. Приложению позволено определять набор моделей (хотя может и не делать этого); определять и регистрировать свои шаблонные теги и фильтры (хотя, опять-таки, может и не делать).
  • Проект - это совокупность приложений, установленных в одной и той же базе данных, и использующих один и тот же файл настроек. В некотором смысле, определение проекта - в том, что он предоставляет файл настроек, в котором указывается, какую базу данных использовать, какие приложения устанавливать и другие параметры конфигурации. Проект может соответствовать одному веб-сайту, но может и не соответствовать - несколько проектов могут обслуживать один сайт. Проект также отвечает за настройки корневого URL, хотя в большинстве случаев достаточно того, что он "подключает" настройки URL из отдельных приложений.

Виды (view), свои манипуляторы (manipulator), свои обработчики контекста (context processor) и большинство других вещей, которые Django позволяет создавать, могут быть определены как на уровне проекта, так и на уровне приложения. Вы должны определять их там, где Вам удобней, хотя, в целом, лучше, если они определены внутри приложения (это улучшает его переносимость между проектами).

Структура файлов по умолчанию на уровне проекта

Когда Вы выполняете django-admin.py startproject, Django автоматически создает новый каталог, содержащий четыре файла:

  • __init__.py, пустой. Этот файл необходим для того, чтобы Python интерпретировал этот каталог как Python-модуль.
  • manage.py предоставляет удобные функции для управления и работы с проектом.
  • settings.py является файлом настроек проекта.
  • urls.py является файлом настроек корневого URL проекта

Вообще говоря, у Вас нет необходимости изменять эту структуру, и для совместимости и последовательности лучше этого не делать. Если у Вас всё же есть желание изменить эту структуру, то вот, что можно делать без опаски:

  • Вы можете сохранить настройки в файле, который не называется settings.py - Django найдет Ваши настройки, посмотрев значение переменной окружения DJANGO_SETTINGS_MODULE
  • Вы можете сохранить настройки корневого URL в другом месте, помимо urls.py - Django смотрит параметр ROOT_URLCONF для поиска места, где находятся настройки URL.

Структура файлов по умолчанию на уровне приложения

Когда Вы запускаете manage.py startapp, Django создает подкаталог в каталоге Вашего проекта и в нем создает следующие файлы:

  • __init__.py служит тем же целям, что и аналогичный файл в директории проекта
  • models.py является модулем, в котором находятся классы моделей приложения
  • views.py является модулем, в котором находятся виды, используемые в приложении.

Файлы __init__.py и models.py (или, конечно, если Вы захотите разделить модели по нескольким файлам, каталог с именем models, который будет действовать как модуль Python) необходимы. Без __init__.py Python не сможет импортировать приложение. В Django "намертво" зашито, что модели нужно искать либо в модуле models. Файл views.py опционален и Вы можете удалить его, если нет специфичных для приложения видов, либо переименовать его в нечто иное (хотя в целях последовательности лучше не переименовывать).

Экстра-фишки

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

  1. Для определения своих шаблонных тегов или фильтров, Вы должны создать подкаталог templatetags в каталоге приложения; этот каталог должен содержать файл __init__.py (чтобы Python смог подкаталог "воспринимать" как модуль). Далее, Вы определяете свои теги и фильтры в файлах, которые имеют любое имя по Вашему усмотрению. Если в каталоге templatetags у Вас есть файл, скажем mytags.py, то в шаблонах Вы можете подгружать этот файл {% load mytags %} для получения доступа к тегам или фильтрам, которые определены в нем.
  2. Для определения юнит-тестов, которые автоматически будут подключены к тест-каркасу (testing framework) Django, поместите их в модуль tests (это может быть файл tests.py, либо каталог tests). Тест-каркас также найдет все doctest'ы в этом модуле, но предпочтительное место для них - конечно docstring'и соответствующих классов или функций.
  3. Для того, чтобы указать свои SQL-запросы, которые будут выполнены сразу после установки приложения, Вы должны в каталоге приложения создать подкаталог sql; имена файлов должны быть такими же, как имена моделей, чьи таблицы эти запросы обрабатывают. Например, если у Вас есть приложение weblog, в котором есть модель Entry, файл sql/Entry.sql внутри каталога приложения может быть использован для модификации либо вставки данных в таблицу entries сразу после ее создания.
  4. Для того, чтобы указать свои Python-функции, которые будут выполнены сразу после установки приложения, Вы должны поместить их в файл management.py и использовать внутренний диспетчер Django для соединения ваших функций с сигналом post_syncdb.

Последний пункт требует пояснений, так что рассмотрим его чуть подробней.

Внутри Django для коммуникаций между составными его частями используется пакет PyDispatcher. Диспетчер работает примерно так:

  1. Различные части Django, равно как и другие приложения, определяют простые объекты, называемые "сигналами"
  2. Код, который хочет сообщить о происходящем, просит диспетчера послать определенный сигнал.
  3. Код, который хочет получать информацию о происходящем, использует метод диспетчера connect для того, чтобы слушать определенный сигнал.

Например, если у Вас есть желание написать функцию, которая будет выполняться каждый раз при установке нового приложения, Вы можете создать файл management.py внутри приложения и поместить в него следующий код:

from django.dispatch import dispatcher
from django.db.models import signals

def my_syncdb_func():
    # put your code here...

dispatcher.connect(my_syncdb_func, signal=signals.post_syncdb)

Некоторые приложения, идущие в поставке с Django, используют этот трюк для различных вещей:

  • django.contrib.sites ожидает момента своей установки, после чего создает начальный объект сайта "example.com", необходимый для функционирования "админки".
  • django.contrib.contenttypes ожидает момента установки любого нового приложения, после чего создает новых представителей класса ContentType для всех установленных моделей.
  • django.contrib.auth работает на два фронта: после установки приложения auth, он спрашивает о создании суперпользователя, а после установки любого другого приложения, создает права доступа для моделей этого приложения.

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

Другие полезные соглашения

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

На уровне проекта я обычно мало чего добавляю: чаще то, что мне требуется лучше вписывается на уровне приложения. Те не менее, если у Вас запущено несколько проектов, и каждый из них использует все или почти все одинаковые приложения, то весьма полезно тщательно выбирать настройки. Джейкоб пару раз упоминал трюк, который мы использовали в Journal-Worlds, и я думаю, он довольно полезен: весь наш код находится в одной структуре директорий, а файлы настроек - в другой, с "умолчательным" файлом настроек в корне дерева настроек. Файлы настроек для отдельного сайта импортируют "умолчательные" настройки и переопределяют либо добавляют нужные. Поскольку Django не требует, чтобы файлы настроек находились в том же дереве каталогов, что и проекты, использующие эти настройки, то это очень просто и весьма полезно.

На уровне приложения я обычно добавляю несколько файлов, в зависимости от того, над чем конкретно я работаю:

  • Если в приложении определяются какие-либо манипуляторы, я помещаю их в файл forms.py, а не в файл видов.
  • Если в приложении нужно несколько своих менеджеров, я помещаю их в файл managers.py вместо файла моделей.
  • Если я определяю свои обработчики контекста, я помещаю их в файл context_processors.py.
  • Если я определяю свои сигналы для диспетчера, то они лежат в signals.py.
  • Если приложение использует синдикат-ленты (syndication feed), то классы для лент лежат в feeds.py.
  • Классы-посредники помещаю в middleware.py, аналогично классы карт сайта лежат в sitemaps.py.
  • Другой код, который не вписывается ни во что другое, помещаю в пакет utils.

Я не всегда использую весь перечисленный функционал в приложении, но если использую, то приятно иметь соглашение по размещению, так что мне достаточно запомнить from appname import forms или from appname import feeds.

И еще одно замечание...

Это то, над чем я еще работаю: у меня есть желание найти простой способ тестировать зависимости моего приложения и проверять, что все верно сконфигурировано. Самый простой способ, по моему опыту, это положить некоторый код с необходимыми проверками в __init__.py приложения. Я написал простой пример в письме в список рассылки разработчиков Django и я все еще работаю над улучшением; есть несколько функций, припрятанных в django.core.management, эти функции позволяют не только проверить, что приложение может быть импортировано и что оно указано в параметре INSTALLED_APPS, но и проверить, установлено ли приложение в базу данных.

А как Вы делаете?

Похоже, я изложил все, что знаю, указал все используемые трюки для прозрачной структуры приложения или проекта Django. Если Вы увидите что-то, что Вам по душе, Вы можете спокойно использовать это. И если я что-то не упомянул, что знаете Вы, то, пожалуйста, напишите комментарий и пусть все об этом узнают :)

Комментарии

Все статьи