Александр Кошелев
Александр Кошелев Python-разработчик

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

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

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

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

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 пакете.

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

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

comments powered by Disqus