Перевод статьи «Best practices working with Django models in Python».
Приступим:
1. Правильные имена моделей
Как правило, в качестве имён моделей рекомендуется использовать существительные в единственном числе, например: User
, Post
, Article
. Существительным должно быть именно последнее слово в названии, например Some New Shiny Item
. Использование единственного числа корректно, когда единица модели не содержит информацию о нескольких объектах сразу.
2. Названия полей, задающих отношения
Для таких отношений, как ForeignKey
, OneToOneKey
, ManyToMany
лучше указывать более специфичное имя.
Представьте, что существует модель с названием Article
, в которой помимо всего прочего определено отношение ForeignKey
для модели User
. Если это поле содержит информацию об авторе статьи, то имя author
подойдёт куда лучше, чем просто user
.
3. Правильное название для related-name
Атрибут related_name
логичнее указывать во множественном числе, так как обращение по этому имени возвращает набор запросов (queryset). Не поленитесь – дайте ему осмысленное имя. В большинстве случаев подходящий вариант для related_name
— имя модели во множественном числе. Например:
class Owner(models.Model): pass class Item(models.Model): owner = models.ForeignKey(Owner, related_name='items')
4. Не используйте ForeignKey с параметром unique=True
Нет смысла использовать unique=True
в отношениях ForeignKey
: для таких случаев есть OneToOneField
.
5. Порядок атрибутов и методов в модели
Предпочтительно описывать атрибуты и методы модели в следующем порядке:
- константы (choices и т.д.)
- поля модели
- задание custom managers
meta
def __unicode__
(python 2) илиdef __str__
(python 3)- другие специальные методы
def clean
def save
def get_absolut_url
- остальные методы
Обратите внимание, что данная последовательность была взята из документации и немного дополнена.
6. Добавление модели через миграцию
Если требуется добавить модель, то после создания класса модели выполните команды manage.py (в указанном порядке): makemigrations
и migrate
(или используйте South для Django версии 1.6 и ниже).
7. Денормализация
Не допускайте бездумное использование денормализации в реляционных базах данных. Всегда старайтесь этого избегать, за исключением случаев осознанного применения денормализации данных для каких бы то ни было целей (например, ради производительности).
Если на стадии разработки БД вы понимаете, что придется денормализовать слишком много данных, может быть, правильным решением было бы использование NoSQL. Однако, если большинство данных этого не требует, задумайтесь о хранении части данных в реляционной БД с JsonField
.
8. BooleanField
Не используйте null=True
или blank=True
для BooleanField
. Также отметим, что для таких полей лучше указывать значения по умолчанию. Если вы понимаете, что поле всё же может остаться пустым, вам понадобится NullBooleanField
.
9. Бизнес-логика в моделях
Лучшее место для размещения бизнес-логики проекта — это модели, а именно методы и менеджеры моделей. Возможно, некоторые методы будут только вызывать какие-нибудь методы/функции.
Если поместить логику в модели неудобно или вовсе невозможно, вам стоит заменить относящиеся к ней формы или сериализаторы в тасках (tasks).
10. Дублирование полей в ModelForm
Не следует без надобности дублировать поля моделей в ModelForm
или ModelSerializer
. Если вы хотите указать, что форма использует все поля моделей, используйте MetaFields
.
Если вам нужно переопределить виджет поля так, чтобы всё остальное в этом поле осталось неизменным, используйте Meta widgets
для указания виджетов.
11. Не используйте ObjectDoesNotExist
Указание ModelName.DoesNotExist
вместо ObjectDoesNotExist
сделает обработку исключений более специализированной, что является хорошей практикой.
12. Использование choices
При использовании choices
рекомендуют придерживаться следующих правил:
- Хранить в БД строки, а не числа. Хотя это не лучший выход с точки зрения использования необязательных данных, на практике это оказывается удобнее, так как строки более наглядны. Это позволяет использовать понятные фильтры для получения вариантов из полей в REST-фреймворках.
- Переменные для хранения вариантов являются константами. Поэтому они должны быть указаны в верхнем регистре.
- Указывать варианты перед списками полей.
- При описании списка состояний указывать его нужно в хронологическом порядке (например:
new
,in_progress
,completed
). - Можно использовать
Choices
из библиотекиmodel_utils
. Возьмём, к примеру, модельArticle
:
from model_utils import Choices class Article(models.Model): STATUSES = Choices( (0, 'draft', _('draft')), (1, 'published', _('published')) ) status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) …
13. Зачем нужны лишние .all()?
При использовании ORM нет необходимости дополнительно вызывать метод all
перед filter()
, count()
, и т.д.
14. В модели много флагов?
Если это оправдано, замените несколько BooleanFields
одним полем статуса. Пример:
class Article(models.Model): is_published = models.BooleanField(default=False) is_verified = models.BooleanField(default=False) …
Пусть логика нашего приложения предполагает, что статья изначально не опубликована и не проверена. Затем она проверяется и помечается: is_verified
становится True
, а затем публикуется.
Как можно заметить, статью нельзя опубликовать непроверенной. Поэтому всего у нас есть 3 состояния. При наличии двух булевых полей получается 4 возможных состояния, которых у нас в реальности нет. Поэтому придётся делать проверки, чтобы не допустить наличия статей с неверными сочетаниями булевых полей. Вот почему использование одного поля статуса вместо двух логических переменных – лучшее решение:
class Article(models.Model): STATUSES = Choices('new', 'verified', 'published') status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) …
Этот пример может быть не очень показательным, но представьте, что в вашей модели 3 или более таких логических переменных. Отслеживание сочетаний всех таких полей может действительно утомить.
15. Имя модели в названиях полей – многословность
Не добавляйте имена моделей в названия полей: в этом нет необходимости. Например, если таблица User имеет поле user_status
– лучше переименовать его в status
(при условии, что других полей со статусами в данной модели нет).
16. Избегайте «грязных» данных в базе
Всегда следует использовать PositiveIntegerField
вместо IntegerField
, если в этом есть смысл, потому что в базу не должны попадать «грязные» данные.
По той же причине всегда следует использовать unique
, unique_together
для данных, которые логично сделать уникальными, и никогда не ставить required=False
во всех полях подряд.
17. Получение первого/последнего объекта
Вместо order_by('created')[0]
можно использовать ModelName.objects.earliest('created'/'earliest')
, а ещё можно применять get_latest_by
в модели Meta. Помните, что latest/earliest
так же, как и get
, могут вызывать исключение DoesNotExist
. Таким образом, order_by('created').first()
– самый полезный вариант.
18. Никогда не пользуйтесь len(queryset)
Не прибегайте к методу len
, если хотите узнать количество объектов в queryset
. Для этих целей может быть вызван метод count
.
Если же делать так: len(ModelName.objects.all())
, сначала будет выполнен запрос на выбор всех данных из таблицы, затем эти данные будут преобразованы в Python-объект, и только потом длина этого объекта будет найдена с помощью len
. Использовать такой подход крайне не рекомендуется, поскольку в случае с count
произойдёт лишь обращение к SQL-функции COUNT()
. При выполнении count
будет обработан более простой запрос в той же базе данных и будет потрачено куда меньше ресурсов для исполнения кода на Python.
19. if queryset – плохая идея
Не используйте queryset
в качестве логического значения – вместо if queryset: сделать что-то
используйте if queryset.exists(): сделать что-то
.
Помните, что наборы запросов очень ленивые, и если вы используете queryset
как булево значение, в БД будет выполнен неприемлемый запрос.
20. Использование help_text для документации
Используйте help_text
в полях модели как часть документации. Это определённо облегчит понимание структуры данных для вас, ваших коллег и пользователей-администраторов.
21. Хранение информации о деньгах
Не стоит использовать FloatField
для хранения данных о количестве денег. Вместо этого можно прибегнуть к классу DecimalField
. Вы можете также хранить эту информацию в центах, единицах, и т.д.
22. Не применяйте null=true, если это не нужно
null=True
– позволяет столбцу БД хранить значение null.
blank=True
– будет использовано только в формах для валидации и не относится к базе данных. В текстовых полях лучше хранить значение default.
blank=True default=''
Так вы получите только одно возможное значение для столбцов без данных.
23. Избавьтесь от _id
Не нужно добавлять суффикс _id
к ForeignKeyField
и OneToOneField
.
24. Определяйте __unicode__ или __str__
Во всех неабстрактных моделях добавляйте методы __unicode__
(python 2) или __str__
(python 3). Эти методы должны всегда возвращать строки.
25. Прозрачный список полей
Не используйте Meta.exclude
для описания списка полей в ModelForm
. Для этого больше подходит Meta.fields
, так как он делает этот список понятным. По той же причине не стоит применять Meta.fields=”__all__”
.
26. Не сваливайте все загруженные пользователем файлы в одну папку
Порой, если ожидается большое количество скачиваний файлов, даже отдельной папки для каждого FileField
будет недостаточно. Хранение множества файлов в одной папке означает, что система будет искать требуемый файл дольше. Во избежание такой ситуации можно сделать следующее:
def get_upload_path(instance, filename): return os.path.join('account/avatars/', now().date().strftime("%Y/%m/%d"), filename) class User(AbstractUser): avatar = models.ImageField(blank=True, upload_to=get_upload_path)
27. Используйте абстрактные модели
Если вы хотите, чтобы модели разделяли какую-то логику, можно воспользоваться абстрактными моделями.
class CreatedatModel(models.Model): created_at = models.DateTimeField( verbose_name=u"Created at", auto_now_add=True ) class Meta: abstract = True class Post(CreatedatModel): ... class Comment(CreatedatModel): ...
28. Используйте custom Manager и наборы запросов
Чем больше проект, над которым вы работаете, тем больше кода повторяется в разных его местах.
Чтобы не отходить от принципа DRY и размещать в моделях бизнес-логику, можно использовать custom Managers
и Queryset
.
Вот пример. Если требуется получить количество комментариев к постам из примера выше:
class CustomManager(models.Manager): def with_comments_counter(self): return self.get_queryset().annotate(comments_count=Count('comment_set'))
Можно использовать следующее:
posts = Post.objects.with_comments_counter() posts[0].comments_count
Если нужно применить этот метод в связке с другими методами queryset
, следует использовать CustomQuerySet
:
class CustomQuerySet(models.query.QuerySet): """Замена QuerySet, добавление дополнительных методов в QuerySet """ def with_comments_counter(self): """ добавляет в queryset счётчик комментариев """ return self.annotate(comments_count=Count('comment_set'))
И вы сможете использовать это:
posts = Post.objects.filter(...).with_comments_counter() posts[0].comments_count
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]