1. Правильный нейминг моделей
В целом для названий моделей рекомендуется использовать существительные в единственном числе. Например, User
, Post
, Article
. То есть, существительным должен быть последний компонент названия, как в Some New Shiny Item
. Единственное число уместно, когда один юнит модели не содержит информацию о нескольких объектах.
2. Название поля Relationship
Связям (relationships), таким как «один ко многим» (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
Нет никакого смысла использовать ForeignKey
с unique=True
, поскольку для таких случаев есть OneToOneField
.
5. Порядок атрибутов и методов в модели
Предпочтительный порядок указания атрибутов и методов в модели:
- константы (для выбора и пр.)
- поля модели
- пользовательские менеджеры
- meta
def __unicode__
(python 2) илиdef __str__
(python 3)- другие специальные методы
def clean
def save
def get_absolut_url
- другие методы.
Этот список был составлен на основе рекомендуемого в документации и слегка расширен.
6. Добавление модели с помощью миграции
Допустим, вам нужно добавить модель. Имея готовый класс модели, выполните серию команд manage.py: makemigrations
и migrate
(для Django 1.6 и ниже используйте South
).
7. Денормализация
Не следует допускать бездумную денормализацию в реляционных базах данных. Всегда старайтесь избегать этого, за исключением случаев, когда вам по каким-то причинам (например, для повышения производительности) приходится постоянно денормализовывать данные.
Если на этапе проектирования базы данных вы понимаете, что многие данные придется денормализовывать, хорошим вариантом будет использование NoSQL. Однако, если большинство данных не требуют непременной денормализации, подумайте о реляционной базе с JsonField
для хранения некоторых данных.
8. BooleanField
Не используйте null=True
или blank=True
для BooleanField
. Также следует отметить, что дефолтные значения для подобных полей лучше конкретизировать. Если вы понимаете, что поле может остаться пустым, используйте NullBooleanField
.
9. Бизнес-логика в моделях
Бизнес-лоигку проекта лучше всего размещать в моделях, а именно — в методах и менеджере модели. Допустимо делать так, чтобы методы модели только запускали какие-то методы/функции. Если разместить логику в моделях неудобно или невозможно, следует перенести ее в формы.
10. Дублирование полей в ModelForm
Не следует без необходимости дублировать поля модели в ModelForm
или ModelSerializer
. Если вы хотите указать, что форма использует все поля модели, примените MetaFields
. Если вам нужно переопределить виджет для поля, а в самом поле ничего меняться не будет, используйте атрибут widgets
класса Meta
.
11. Не используйте ObjectDoesNotExist
Использование ModelName.DoesNotExist
вместо ObjectDoesNotExist
сделает ваш перехват исключений более точным. Это хорошая практика.
12. Использование choices
При использовании choices
рекомендуется следующее:
- Храните в базе данных строки, а не числа (хотя это не лучшее решение с точки зрения опционального использования базы данных, на практике этот подход более удобен. Строки более наглядны, что позволяет использовать четкие фильтры с опциями get в 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. Много флагов в модели?
Если это оправдано, замените несколько BooleanField
одним полем, например:
class Article(models.Model): is_published = models.BooleanField(default=False) is_verified = models.BooleanField(default=False) …
Допустим, логика нашего приложения предполагает, что в «исходном положении» статья не опубликована и не проверена. Затем она проверяется (при этом is_verified
помечается как True
), а затем публикуется. То есть, статья не может быть опубликована, пока она не проверена.
В общей сложности у нас получается 3 условия. Но с 2 булевыми полями у нас нет 4 возможных вариантов. Кроме того, нужно следить за тем, чтобы не было статей с неправильными комбинациями условий в BooleanField
. Поэтому лучшим вариантом будет использовать не два BooleanField
, а одно поле статуса:
class Article(models.Model): STATUSES = Choices('new', 'verified', 'published') status = models.IntegerField(choices=STATUSES, default=STATUSES.draft) …
Этот пример, быть может, не слишком показательный, но представьте, что у вас в модели таких BooleanField
три или даже больше. Контролировать проверку комбинаций значений этих полей может быть довольно утомительным делом.
15. Имя модели в имени поля это излишество
Не добавляйте имена моделей в имена полей, если в этом нет необходимости. Например, если в таблице User
есть поле user_status
, и никаких других ххх_status
в этой модели нет, это поле стоит переименовать в просто status
.
16. В базе не должно быть грязных данных
Всегда используйте PositiveIntegerField
, а не IntegerField
(конечно, там, где это не будет бессмысленным), потому что «плохие» данные не должны попадать в базу. По той же причине следует всегда использовать unique
, unique_together
для логически уникальных данных и ни в коем случае не ставить required=False
в каждом поле.
17. Получение самого первого/последнего объекта
Вы можете использовать ModelName.objects.earliest('created'/'earliest')
вместо order_by('created')[0]
. Также можно поместить 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
, у вас будет выполняться более простой запрос к базе данных, и код станет более производительным.
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. Применяйте пользовательские Manager и QuerySet
Чем крупнее проект, над которым вы работаете, тем чаще вы будете повторять какой-то код в разных местах. Чтобы распределить бизнес-логику в моделях и при этом придерживаться принципа DRY, можно применить пользовательские Manager
и 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, стоит применить пользовательский QuerySet
:
class CustomQuerySet(models.query.QuerySet): """Substitution the QuerySet, and adding additional methods to QuerySet """ def with_comments_counter(self): """ Adds comments counter to queryset """ return self.annotate(comments_count=Count('comment_set'))
Теперь вы можете написать так:
posts = Post.objects.filter(...).with_comments_counter() posts[0].comments_count