Когда выходить за пределы ORM

ORM закрывает большую часть задач, а annotate, Func, Q и F решают многое. К собственному полю или сырому SQL стоит переходить, когда вы снова и снова преобразуете одно и то же значение, нужна возможность конкретной базы или замер показал, что запрос ORM слишком медленный.

ЗадачаИнструмент
Хранить свой типподкласс Field
Добавить фильтр вроде tags__hasсвой Lookup
Одно SQL-выражение внутри querysetRawSQL, Func
Сложный запрос, возвращающий моделиModel.objects.raw()
Отчёт, DDL, массовая записьconnection.cursor()

💡 Сырой SQL не переносится между SQLite, PostgreSQL и MySQL. Покройте его тестами и держите в одном месте.

Своё поле Field

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

Путь значения туда и обратно
Путь значения туда и обратно
python
from django.db import models

class TagListField(models.TextField):
    description = "A list of tags stored as comma-separated text"

    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return [t for t in value.split(",") if t]

    def to_python(self, value):
        if value is None or isinstance(value, list):
            return value
        return [t for t in value.split(",") if t]

    def get_prep_value(self, value):
        if value is None or isinstance(value, str):
            return value
        return ",".join(value)

class Post(models.Model):
    title = models.CharField(max_length=100)
    tags = TagListField(default=list)