Лучшие практики работы с моделями Django на Python

Перевод статьи «Best practices working with Django models in Python».

Приступим:

1. Правильные имена моделей

Как правило, в качестве имён моделей рекомендуется использовать существительные в единственном числе, например: UserPostArticle. Существительным должно быть именно последнее слово в названии, например 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-фреймворках.
  • Переменные для хранения вариантов являются константами. Поэтому они должны быть указаны в верхнем регистре.
  • Указывать варианты перед списками полей.
  • При описании списка состояний указывать его нужно в хронологическом порядке (например: newin_progresscompleted).
  • Можно использовать 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 (при условии, что других полей со статусами в данной модели нет).

Photo by Teer XC on Unsplash

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]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх