Best Practices в работе с моделями Django

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