Django ORM

Meta и менеджеры в Django ORM | Курс Django ORM урок 1.3

Meta и менеджеры в Django ORM | Курс Django ORM урок 1.3
Mikhail
Автор
Mikhail
Опубликовано 16.03.2026
0,0
Views 9

Цель урока

Разобраться с классом Meta внутри модели, что он контролирует на уровне БД и на уровне Django. Понять, что такое менеджер модели, как работает objects по умолчанию и зачем вообще нужен этот слой абстракции.

Необходимые знания

  • Уроки 1.1 и 1.2: поля и связи
  • Базовое понимание индексов в SQL

Класс Meta в Django ORM

Meta, внутренний класс модели, который управляет поведением модели в целом, а не отдельных полей. Он не создает колонок, он влияет на таблицу, индексы, ограничения и поведение ORM.

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["name"]
        verbose_name = "product"
        verbose_name_plural = "products"
        db_table = "shop_product"

Разберем каждую опцию.


ordering - сортировка по умолчанию

class Meta:
    ordering = ["name"]           # по возрастанию
    ordering = ["-created_at"]    # по убыванию (минус = DESC)
    ordering = ["-created_at", "name"]  # несколько полей

Когда задан ordering, каждый QuerySet без явного order_by() автоматически получает ORDER BY:

Product.objects.all()
SELECT * FROM shop_product ORDER BY name ASC;

Это удобно, но есть скрытая цена. ordering применяется к каждому запросу, включая те, где сортировка не нужна: count(), exists(), агрегации. До Django 3.0 это вызывало лишние JOIN при ordering по связанным полям. Сейчас Django умнее, но привычка осознанно управлять сортировкой указывая явный order_by(), правильная.

Сортировка по связанному полю:

class Meta:
    ordering = ["category__name", "name"]

Генерирует JOIN при каждом запросе без явного order_by(), это потенциально дорого на больших таблицах.

Чтобы получить QuerySet без сортировки по умолчанию:

Product.objects.order_by()  # сброс ordering, пустой list убирает все

verbose_name и verbose_name_plural

class Meta:
    verbose_name = "товар"
    verbose_name_plural = "товары"

Используется в Django Admin и формах. На SQL не влияет. Если не задать, Django генерирует из имени класса: Product → "product" / "products".


db_table - имя таблицы

class Meta:
    db_table = "products"  # вместо shop_product

По умолчанию Django формирует имя как appname_modelname. Менять стоит только если:

  • Работаете с legacy БД, где таблицы уже названы иначе
  • Нужно соответствие определенному соглашению об именах

При смене db_table на существующей БД нужна миграция, которая переименует таблицу.


indexes - составные и специализированные индексы

Для одиночных индексов на поле достаточно db_index=True. Meta.indexes нужен для:

  • составных индексов по нескольким полям
  • частичных индексов по условию
  • индексов с особыми параметрами
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    category = models.ForeignKey(Category, on_delete=models.PROTECT)
    is_active = models.BooleanField(default=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            # Составной индекс ускоряет запросы с фильтром по обоим полям
            models.Index(fields=["category", "is_active"], name="product_category_active_idx"),

            # Сортировка по убыванию для запросов ORDER BY created_at DESC
            models.Index(fields=["-created_at"], name="product_created_desc_idx"),
        ]

SQL, который создает миграция:

CREATE INDEX product_category_active_idx
    ON shop_product (category_id, is_active);

CREATE INDEX product_created_desc_idx
    ON shop_product (created_at DESC);

Частичный индекс (PostgreSQL)

Индекс только по строкам, где is_active = True. Если активных товаров 10% от общего числа, индекс будет в 10 раз быстрее:

from django.db.models import Q

class Meta:
    indexes = [
        models.Index(
            fields=["price"],
            condition=Q(is_active=True),
            name="product_active_price_idx",
        ),
    ]
CREATE INDEX product_active_price_idx
    ON shop_product (price)
    WHERE is_active = true;

Этот индекс используется только если запрос включает условие is_active = True. Подробно про индексы в уроке 5.1.


constraints - ограничения на уровне БД

from django.db.models import CheckConstraint, UniqueConstraint, Q

class Review(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    rating = models.PositiveSmallIntegerField()

    class Meta:
        constraints = [
            # Один пользователь, один отзыв на продукт
            UniqueConstraint(
                fields=["product", "user"],
                name="unique_review_per_user",
            ),
            # Рейтинг только от 1 до 5
            CheckConstraint(
                condition=Q(rating__gte=1) & Q(rating__lte=5),
                name="valid_rating_range",
            ),
        ]

SQL:

ALTER TABLE shop_review
    ADD CONSTRAINT unique_review_per_user UNIQUE (product_id, user_id);

ALTER TABLE shop_review
    ADD CONSTRAINT valid_rating_range CHECK (rating >= 1 AND rating <= 5);

CheckConstraint, ограничение проверяется базой данных при каждой INSERT и UPDATE. Даже если обойти валидацию Django, БД откажет в записи невалидных данных.

Разница между unique_together (устаревший с Django 4.2, рекомендуется UniqueConstraint) и UniqueConstraint:

# Старый способ, устаревший с Django 4.2, но еще работает
class Meta:
    unique_together = [("product", "user")]

# Новый способ, больше возможностей
class Meta:
    constraints = [
        UniqueConstraint(fields=["product", "user"], name="unique_review_per_user"),
    ]

UniqueConstraint поддерживает дополнительные параметры: condition для частичного unique constraint, include для covering index, nulls_distinct (Django 5.0+, PostgreSQL 15+). Подробно, в уроке 6.3.


abstract - абстрактные модели

class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True  # таблица не создается

Абстрактная модель не создает таблицу в БД. Она служит базовым классом для других моделей:

class Product(TimestampedModel):
    name = models.CharField(max_length=200)
    # автоматически получает created_at и updated_at

Product создаст одну таблицу со всеми полями, включая унаследованные. Это просто переиспользование кода, никакого наследования на уровне БД. Подробно о наследовании моделей в уроке 9.1.


Менеджеры моделей

Менеджер это объект, через который Django ORM взаимодействует с базой данных. Каждая модель получает менеджер по умолчанию с именем objects.

# Все эти вызовы идут через менеджер objects
Product.objects.all()
Product.objects.filter(is_active=True)
Product.objects.get(id=1)
Product.objects.create(name="iPhone", price=999)

Что такое менеджер

Менеджер это экземпляр класса Manager. Он привязан к модели (знает, с какой таблицей работать) и предоставляет методы для построения QuerySet.

from django.db import models

# Manager - это класс
print(type(Product.objects))  # <class 'django.db.models.manager.Manager'>

# objects - атрибут класса Product, экземпляр Manager
# Через него все запросы к таблице shop_product

Когда вы пишете Product.objects.filter(...), происходит следующее:

  1. Product.objects, обращение к менеджеру
  2. .filter(...), менеджер создает QuerySet
  3. QuerySet знает модель и строит SQL
  4. SQL уходит в PostgreSQL только при итерации (ленивость)

Зачем нужен менеджер как отдельный слой

Менеджер отделяет "получение данных" от "данных как таковых". Модель описывает структуру, менеджер, способы доступа к данным.

Это важно, когда нужно добавить логику на уровне запросов, общую для всего приложения. Например, все запросы к Product должны возвращать только активные товары. Без кастомного менеджера каждый запрос в коде должен иметь filter(is_active=True). С менеджером, достаточно одного места.

Несколько менеджеров

Модель может иметь несколько менеджеров:

class Product(models.Model):
    name = models.CharField(max_length=200)
    is_active = models.BooleanField(default=True)

    # Стандартный менеджер, все продукты
    objects = models.Manager()

    # Второй менеджер, только активные
    active = models.Manager()  # пока не переопределен, то же самое что objects

Кастомные менеджеры, которые фильтруют данные, мы создадим в уроке 8. Сейчас важно понять: objects, это не встроенная часть ORM, это менеджер по умолчанию. Его можно переопределить или добавить рядом другой.

Первый менеджер - менеджер по умолчанию

Django использует первый объявленный менеджер как менеджер по умолчанию. Это влияет на:

  • Django Admin, использует менеджер по умолчанию для выборки объектов
  • related objects при доступе через обратные связи
class Product(models.Model):
    # Если первый менеджер фильтрует данные - это может сломать Admin
    active = models.Manager()  # первый = по умолчанию
    objects = models.Manager()

    # Правильно: objects первым, чтобы админка видела все объекты
    objects = models.Manager()
    active = models.Manager()

Лучше явно указать use_in_migrations = True и Meta.default_manager_name если меняете порядок. Но самый безопасный вариант, всегда оставлять objects первым менеджером.


Что получилось в нашем проекте

Посмотрим на полные Meta для наших моделей с учетом всего изученного:

class Product(models.Model):
    # ... поля ...

    class Meta:
        ordering = ["name"]
        verbose_name = "product"
        verbose_name_plural = "products"
        indexes = [
            models.Index(fields=["category", "is_active"], name="product_category_active_idx"),
            models.Index(fields=["-created_at"], name="product_created_desc_idx"),
        ]


class Review(models.Model):
    # ... поля ...

    class Meta:
        ordering = ["-created_at"]
        constraints = [
            models.UniqueConstraint(
                fields=["product", "user"],
                name="unique_review_per_user",
            ),
            models.CheckConstraint(
                condition=models.Q(rating__gte=1) & models.Q(rating__lte=5),
                name="valid_rating_range",
            ),
        ]


class Category(models.Model):
    # ... поля ...

    class Meta:
        verbose_name_plural = "categories"
        ordering = ["name"]

Практическое задание

  1. Добавьте в модель Order constraint, который запрещает отрицательный total_price. Сделайте миграцию. Проверьте через psql (\d shop_order), что constraint появился.

  2. Добавьте в модель Product составной индекс по полям is_active и price. Как называется этот индекс в PostgreSQL после миграции?

  3. Создайте абстрактную модель TimestampedModel с полями created_at и updated_at. Унаследуй от неё Product и Order. Убедитесь что миграция не создает отдельную таблицу TimestampedModel.

  4. Выполните в shell: python from shop.models import Product print(type(Product.objects)) print(Product.objects.model) Что возвращает каждая из этих строк?


Возможные ошибки

Сортировка по связанному полю в Meta.ordering без индекса

class Meta:
    ordering = ["category__name"]  # JOIN при каждом запросе

Если запросов много и таблица большая, это заметно влияет на производительность. Лучше явно указывать order_by() там, где нужна сортировка.

Дублирование unique_together и UniqueConstraint

class Meta:
    unique_together = [("product", "user")]  # устаревший способ
    constraints = [
        UniqueConstraint(fields=["product", "user"], name="unique_review")  # создаст второй constraint
    ]

Используйте что-то одно. Предпочитай UniqueConstraint, он гибче.

Именование constraints и indexes

Django не позволяет два объекта с одинаковым именем. Названия должны быть уникальными в рамках БД. Принятое соглашение: {table}_{поля}_{тип}, например shop_review_product_user_uniq.

Забыть abstract = True в базовом классе

class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    # Забыли abstract = True - Django создаст таблицу timestampedmodel
    # и будет использовать multi-table inheritance вместо abstract

Связь со следующим уроком

В уроке 1.4 разбираем миграции, что происходит внутри файла миграции, как Django отслеживает изменения схемы, как откатывать и как писать data migrations. После этого урока блок 1 будет закрыт и мы перейдем к работе с данными.


<< Урок 1.2

Урок 1.4 >>


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы