Разделяем толстые модули

Когда ваше джанговские приложение/проект обрастает кодом всё больше и больше, то встает вопрос как максимально удобно организовать файловую структуру. И хотя Джанга не сильно капризна и требовательна к структуре модулей/пакетов, но всё-таки не всегда разработчик может себе позволить вольности. Джанга всё-таки ожидает, что какие-то части приложения будут лежать в определенных местах. Это относится к моделям, тестам, шаблонным тегам, определениям админки и т.п.

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

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

models.py

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

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

Итак представим, что у вас есть большой и толстый файл models.py, в котором есть несколько моделей. Для примера возьмем предметную область блога - модели Post, Comment, Tag. Мы хотим чтобы каждая модель обитала в своем модуле, но при этом, чтобы обычные джанговские инструменты продолжали работать как обычно и даже не подозревали о таком разделении.

Создаем вместо модуля models.py пакет models c такими файлами:

- models/
   - __init__.py
   - comment.py
   - post.py
   - tag.py

В модули с соответствующими названиями, помещаем определения моделей. При этом для модели Comment имеющей ссылку на Post надо не забыть импортировать этот самый Post:

from blog.models.post import Post

Ну а для того чтобы посты можно было связать с тегами:

from blog.models.tag import Tag

Следующим шагом является импортирование всех трех моделей в __init__.py модуле пакета:

from blog.models.comment import Comment
from blog.models.post import Post
from blog.models.tag import Tag

Это делается для того, чтобы внешний код мог привычно импортировать модели из приложения и не думал о том, где они там на самом деле лежат. Например во views.py пишем:

from blog.models import Post, Comment, Tag

и работать с моделями как будто они лежать в привычном models.py.

Но вот именно с этими изменениями не всё заработает как надо. Например syncdb не создаст таблички для расположенных таким образом моделей. Причина в том, что джанга при своей "загрузке" пробегается по INSTALLED_APPS и кеширует модели, привязывая их к каждому из приложений. Причем названия этих приложений определятся просто - имя родительского пакета модуля, где лежит модель. В нашем "разделенном" варианте это models. В обычной ситуации это было бы blog и модели бы прицепились к правильному приложению. Для исправления этого недоразумения, необходимо воспользоваться возможностью явно для модели указать приложения к которому она относится:

class Post(models.Model):
   #...

   class Meta:
      app_label = 'blog'

и так же для моделей Comment и Tag.

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

tests.py

Подход описанный выше для models.py справедлив и для разделения тестов на модули и создания пакета tests. Последовательность такая же:

  • сделать пакет tests
  • создать необходимые модули внутри него
  • разбросать тест кейсы по модулям
  • в __init__.py импортировать все тест кейсы из дочерних модулей. Обычно я не заморачиваюсь и пишу ... import *

Кстати, очень часто для тестов нужны какие-то тестовые модели, которые в самом приложении не используются. Эти модели можно свободно объявлять в недрах tests.py/tests, прописывая им правильный app_label.

И ещё, если вы используете doctests, то с их разделение на какие-то логические части гораздо более трудоемкая задача, о который сейчас я говорить не буду, поскольку сам ещё не выработал best-practies.

admin.py

С определением админки всё достаточно просто. Если вы используете admin.autodiscover(), который пробегаясь по INSTALLED_APPS подгружает модуль admin в каждом из приложений, то можете в admin.py импортировать любый модули в которых тоже может быть определена админка. И тогда все определения во внешних модулях тоже зарегистрируются в админке.

В следующий раз расскажу, как можно держать шаблонные теги не только в templatetags пакете.

Но учтите, что вы должны быть точно уверены, что хотите раздробить какие-то модули. Иначе вы просто потеряете время и при этом усложните код проекта для понимания людей со стороны и даже вам самим в будущем.

Разделяйте и властвуйте!:-)

Комментарии 12

  1. Иван Маркеев написал:

    спасибо, полезно.

    Оставлен 21 Апрель 2009 в 09:41
  2. Виктор Коцеруба написал:

    Алекс, почему импорты в __init__.py абсолютные?

    Оставлен 21 Апрель 2009 в 13:27
  3. Иван Сагалаев написал:

    Наверно самая частая проблема - это разделение слишком большого файла models.py на более мелкие

    Я рискну высказать жесткую и непопулярную точку зрения (впрочем, это для меня обычно :-) ). Так вот, эта "проблема" на самом деле исключительно в голове. Я не видел ни одного реального юзкейса, когда бы работать с models.py становилось удобнее от разделения. Связанные по смыслу модели удобно хранить вместе.

    А вот если они не связаны (или слабо связаны) по смыслу, то надо разделять приложение на несколько, а не заниматься противоестественными вивисекциями на сердце внутри одного Франкенштенйа.

    Оставлен 21 Апрель 2009 в 14:25
  4. Александр Кошелев написал:

    Алекс, почему импорты в __init__.py абсолютные?

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

    Оставлен 21 Апрель 2009 в 19:45
  5. Александр Кошелев написал:

    Так вот, эта "проблема" на самом деле исключительно в голове. Я не видел ни одного реального юзкейса, когда бы работать с models.py становилось удобнее от разделения. Связанные по смыслу модели удобно хранить вместе.

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

    А вот если они не связаны (или слабо связаны) по смыслу, то надо разделять приложение на несколько, а не заниматься противоестественными вивисекциями на сердце внутри одного Франкенштенйа.

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

    Оставлен 21 Апрель 2009 в 20:37
  6. bw написал:

    теги не только в templatetag

    templatetags?

    Необходимость разделения, действительно, весьма субъективна, врядли когда-нибудь удастся выработать жесткие правила, для подобных вещей, когда и что делать. Десяток моделей вполне замечательно могут уживаться в одном модуле, а какие-то три - никак, тем не менее (допустим) они остаются мелкими и не хочется связываться с лишними сложностями при их разделении.

    ..bw

    Оставлен 22 Апрель 2009 в 08:03
  7. Иван Сагалаев написал:

    На то все люди и разные, что головы у них работают индивидуально. И в некоторых ситуациях, может быть действительно кому-то проще разбросать классы по разным модулям.

    Нет, я о другом удобстве. Это не субъективная вещь типа "мне кроссовки Адидас кажутся удобнее Рибок", а вполне объективная: система с большим количеством компонентов и уровней абстракции -- сложнее. Или по-другому, головы у всех работают по-разному, но у многих они работают просто неверно (даже у большинства, иначе бы не было нужды в обучении).

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

    Если в голове появляется нужда разделить модели, то скорее всего это должно вести к разделению приложений. Либо она просто появляется от желания спроектировать "что-нибудь эдакое", и тогда это желание надо задушить :-)

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

    Оставлен 22 Апрель 2009 в 10:28
  8. Иван Сагалаев написал:

    Да, на всякий случай хочу сказать, что когда говорю про "многих", у которых голова неверно работает, я не имею в виду каких-то конкретных людей. Только конкретную ситуацию.

    Оставлен 22 Апрель 2009 в 11:12
  9. Сергей Тарковский написал:

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

    Оставлен 23 Апрель 2009 в 18:13
  10. Pashka R. написал:

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

    Ну так о том и речь, но когда такая ситуация возникла - вот, пожалуйста, красивое решение

    Оставлен 24 Апрель 2009 в 18:27
  11. Иван Маркеев написал:

    Прикол! Сегодня обнаружил у себя в ленте пост: http://justinlilly.com/blog/2009/oct/27/fix-your-models-subdir/. Вспомнил про Ваш пост. Что об этом думаете?

    Оставлен 05 Ноябрь 2009 в 13:58
  12. Александр Кошелев написал:

    Да, это обратная сторона медали. У каждого решения есть свои особенности. Ждем когда тикет Майка Мэлоуна закроют.

    Оставлен 06 Ноябрь 2009 в 10:25

Пингбеки 2

  1. От Вопрос по поводу профилей в Django 09 Октябрь 2011 в 22:48

    как разбить models.py на несколько файлов - см. http://webnewage.org/2009/04/21/split/ и http://webnewage.org/2009/04/24/smart-split/

  2. От Django проект, много моделей. Где хранить? 30 Март 2011 в 22:21

    http://webnewage.org/2009/04/21/split/