Перейти к содержанию

Опции Meta

Всё, что относится к таблице модели целиком, а не к отдельному полю, задаётся во вложенном классе Meta:

class Foo(Model):
    ...
    class Meta:
        table = "custom_table"
        constraints = [UniqueConstraint(fields=("field_a", "field_b"))]
Опция Что задаёт
abstract Таблицы нет; поля и Meta переходят к конкретным подклассам — см. abstract.
table Имя таблицы — см. table.
primary_key None для таблицы без первичного ключа — см. Модели без первичного ключа.
managed False оставляет таблицу не миграциям — см. managed.
schema Схема таблицы — подключённая база в SQLite, база сервера в ClickHouse — см. schema.
app Метка приложения для ссылок "app.Model" — см. app.
swappable Настройка, через которую проект заменяет модель, — см. Заменяемые модели.
extensions Расширения PostgreSQL, нужные модели, — см. Расширения.
constraints Ограничения уникальности, проверки и исключения — см. Ограничения.
triggers Триггеры таблицы — см. Триггеры.
views Представления из SQL или queryset — см. Представления.
materialized_views Материализованные представления — см. Материализованные представления.
dictionaries Словари строк модели, которые читаются по ключу (ClickHouse), — см. Словари.
functions Функции, хранимые в базе, — см. Функции.
sequences Последовательности, создаются до таблицы, — см. Последовательности.
row_level_security Построчная защита таблицы, включённая или принудительная, — см. Защита на уровне строк.
policies Политики построчной защиты таблицы — см. Защита на уровне строк.
grants Права на таблицу и другие объекты модели — см. Права.
indexes Индексы таблицы — см. Индексы.
ordering Порядок запросов модели по умолчанию — см. ordering.
get_latest_by По чему сортируют latest() и earliest() — см. get_latest_by.
soft_delete_field DatetimeField, отмечающий мягко удалённую строку, — см. Мягкое удаление.
soft_delete_hard_cascade True по-настоящему удаляет строки, до которых доходит мягкое удаление, — см. soft_delete_hard_cascade.
optimistic_lock_field Версия, которую проверяет и увеличивает каждая запись экземпляра, — см. Оптимистичная блокировка.
tenant_field Колонка с арендатором строки — см. tenant_field.
tenant_schema True держит таблицу в схеме каждого арендатора — см. Схема на арендатора.
track_dirty_fields True хранит снимок каждой строки, с которым сравнивает get_dirty_fields(), — см. Отслеживание изменений.
change_capture Каждая запись модели пишет и изменённые строки в outbox — см. Захват изменений модели.
returning Значение returning у bulk_create() и bulk_update() по умолчанию — см. returning.
table_description Комментарий таблицы — см. table_description.
table_options Как каждый диалект хранит таблицу — см. Параметры хранения таблицы.
manager Менеджер Model.objects — см. manager.

abstract

True: таблица для модели не создаётся; поля и Meta наследуются конкретными подклассами, и каждый получает свою глубокую копию, а не общий объект. Модель абстрактна, только когда об этом говорит её собственная Meta: Meta, унаследованная от Meta абстрактной модели (class Meta(Base.Meta)), оставляет её конкретной.

Конкретная модель, унаследованная от другой конкретной, — самостоятельная модель: у неё своя таблица с копиями полей и нет опций Meta другой модели, кроме manager: его копия переходит к подклассу, и Model.objects подкласса фильтрует его строки так же. Связь, которой другие модели ссылаются на ту модель, не принадлежит подклассу — его строк нет в той таблице, — поэтому чтение её у подкласса (child.refs) даёт AttributeError.

table

Имя таблицы в базе (по умолчанию выводится из имени модели). PostgreSQL обрезает имя до 63 байт, поэтому сгенерированное имя таблицы, промежуточной таблицы связи «многие-ко-многим» или её колонки, которое длиннее, укорачивается и получает в конце хэш — два длинных имени никогда не совпадут. Явно заданное имя таблицы, колонки (source_field или имя поля), промежуточной таблицы, индекса или ограничения длиннее 63 байт даёт ConfigurationError на любом диалекте. Имя с точкой тоже даёт ConfigurationError — схему задаёт Meta.schema.

managed

bool, по умолчанию True. При False команды makemigrations/migrate, Hare.generate_schemas(), hare drift и truncate_all_models() полностью пропускают таблицу модели — она не создаётся, не изменяется и не считается расхождением со схемой, как будто для миграций её не существует. Подходит для модели поверх того, чем миграции управлять не должны (представление, таблица, схема которой читается во время работы) — см. Hare.register_live_models(). Смена managed у существующей модели в любую сторону меняет только её опции в состоянии миграций — таблица из-за этого не удаляется и не создаётся (см. Операции).

schema

Схема, в которой лежит таблица: схема в PostgreSQL, подключённая база в SQLite (её подключает соединение — см. attached_databases), база сервера в ClickHouse. Две модели могут использовать одно имя table в разных схемах — Hare.generate_schemas() упорядочивает таблицы по схеме и имени. Миграции создают и удаляют схему там, где у базы есть CREATE SCHEMA (Features.supports_schemas, PostgreSQL); в остальных база должна существовать. В SQLite связь с db_constraint=True на модель другой схемы отклоняется при привязке модели — внешний ключ там ссылается только на таблицу своей базы (Features.references_other_schemas).

app

Метка приложения для ссылок строкой вида "app.Model".

ordering

Кортеж строк сортировки, например ("-created_at", "name"), или F("field").asc()/.desc(), или выражения (Lower("name").desc(nulls_last=True), F("price") * F("quantity")) — порядок .order_by() по умолчанию, в том числе для first()/last()/reverse(), iterator(), среза, связанного менеджера и prefetch_related(), а также запроса с агрегатом; запрос .values(), сгруппированный по названным полям, его не применяет (Неявный GROUP BY); .order_by() без аргументов его отменяет.

get_latest_by

Имя поля или последовательность имён ("-created_at" — по убыванию), например ("created_at", "id"), — то, по чему сортируют latest()/earliest(), если поля не переданы. Иное значение или имя, которого нет среди полей модели, даёт ConfigurationError при объявлении модели.

soft_delete_hard_cascade

bool, по умолчанию False. Требует soft_delete_field. True заставляет мягкое удаление удалять по-настоящему каскадные строки моделей без soft_delete_field и строки автоматических промежуточных таблиц M2M, как при обычном удалении; по умолчанию они сохраняются, и restore() возвращает связи, — см. Мягкое удаление.

tenant_field

Имя колонки, в которой хранится арендатор строки, — см. Разделение данных по арендаторам. Имя прямой связи ForeignKeyField/OneToOneField означает её колонку ключа (company → company_id); связь с составным ключом или поле без своей колонки дают ConfigurationError.

returning

bool, по умолчанию False — значение параметра returning у bulk_create()/bulk_update(), если в вызове он оставлен None; явные True/False в вызове всегда важнее — см. bulk_create().

table_description

Описание таблицы, которое пишется её комментарием; если не задано, берётся первая строка docstring модели. SQLite хранит его комментарием внутри скобок CREATE TABLE — миграция, которая его меняет, пересоздаёт таблицу.

manager

Объект Manager(), который задаёт запрос по умолчанию (например, автоматические фильтры). Каждый конкретный подкласс получает свою копию. Всё, что фильтрует его get_queryset(), применяется и к каждому соединению с этой моделью (select_related(), .only(), .order_by(), вложенный .filter()), поэтому присоединённая строка отбирается так же, как в Model.objects.all(). Там поддерживаются только обычные условия .filter()/.exclude() по собственным полям модели (вычисляемые значения, LIMIT или фильтр через ещё одну связь дают ConfigurationError). Его фильтр не применяют refresh_from_db(), а также обработка CASCADE/SET_NULL/SET_DEFAULT и проверки PROTECT/RESTRICT в delete()/delete_preview(): они видят все существующие строки (и все строки, которые действительно ссылаются на удаляемые), учитывая только Meta.tenant_field/Meta.soft_delete_field. Manager, присвоенный атрибутом класса (например, all_objects = Manager()) модели, абстрактной базовой модели или обычного класса-примеси, копируется вместе с аргументами конструктора и привязывается к каждой конкретной модели.

Опции расширений

Расширения hare тоже читают из Meta свои опции: outbox_wakeup у модели очереди исходящих событий, new_version_excluded_fields у версионной модели. Они описывают работу расширения, а не таблицу, поэтому миграции их не записывают. Расширение объявляет опции, которые читает, вызовом ExtensionModelOptions.allow("name", ...) (hare.models.class_building.extension_model_options) при импорте.

Что отклоняет Meta

Любой другой атрибут Meta — в том числе унаследованный от базового класса Meta — даёт ConfigurationError при объявлении класса с названием ближайшей опции (Meta.ordring isn't a model option - did you mean Meta.ordering?): иначе опечатка в опции молча ничего бы не делала. Для db_table, app_label и db_table_comment ошибка называет опцию hare, которую нужно указать (table, app, table_description).

Опция, которая что-то называет, принимает непустой текст, а каждая запись extensions — имя расширения; другое значение даёт ConfigurationError: оно попало бы в DDL.

default_connection — не опция Meta: подключение по умолчанию задаётся при регистрации приложения ключом apps.<name>.default_connection в настройках Hare.init(), а не внутри class Meta; записанное в Meta значение даёт ConfigurationError с этим пояснением.

Модели без первичного ключа

У журнала, в который только добавляют записи, у потока событий или у таблицы колоночного хранилища первичного ключа часто нет. Meta.primary_key = None объявляет такую модель: поле id не добавляется, и таблица создаётся без PRIMARY KEY:

class ReadingLog(Model):
    book = fields.ForeignKeyField("models.Book", related_name="readings")
    reader = fields.CharField(max_length=50)
    read_at = fields.DatetimeField(auto_now_add=True)

    class Meta:
        primary_key = None

Что работает: create()/save() новой строки, bulk_create(), любое чтение (filter(), exclude() — в том числе через связи, values(), aggregate(), annotate(), group_by(), select_related(), prefetch_related() прямых связей, iterator(), get(), first()), QuerySet.update() и QuerySet.delete() по любому условию, а также доступ к строкам со стороны связанной модели (book.readings.all(), Book.objects.filter(readings__reader=...), Count("readings"), readings__isnull=True, prefetch_related("readings")). Условие через связь в exclude(), update() или delete() находит записанную строку по всем её колонкам, причём NULL совпадает с NULL: строки, равные во всех колонках, всё равно неразличимы. update().returning() и delete().returning() строят объекты из строк, которые отдаёт RETURNING, со всеми их колонками — ключа, чтобы перечитать строки, нет; мягкое удаление (Meta.soft_delete_field) так же записывает и возвращает строки, в том числе с include_deleted().

То, для чего нужен первичный ключ, даёт ConfigurationError с именем модели: save() прочитанной строки (это UPDATE), delete()/restore()/refresh_from_db() одного объекта, ForeignKeyField/OneToOneField на эту модель без to_field= и ManyToManyField в ней или на неё. pk в фильтре или сортировке — FieldError; instance.pk равен None, а объект равен только самому себе. Через связь со стороны другой модели связанные строки целиком сравнивают только isnull/not_isnull (readings__isnull=True); readings= и readings__in= указывают на строку по её ключу и дают FieldError. count() строк модели с .distinct() даёт QueryError до SQL — строку определяет её ключ; строки values()/values_list() считаются различающимися. iterator() при сортировке с возможными равными значениями идёт через OFFSET, так что приходят все равные строки, а срезанный Prefetch таких строк читает строки каждого родителя целиком и берёт из них срез.

Миграции создают и изменяют такую таблицу, как любую другую (в состоянии хранится primary_key: None); чтобы добавить модели первичный ключ или убрать его, нужна миграция, написанная вручную, — как и при любом изменении первичного ключа. inspectdb превращает в такую модель таблицу без первичного ключа; уникальный индекс таблицы остаётся уникальным индексом.

Параметры хранения таблицы

Meta.table_options перечисляет, как каждый диалект хранит таблицу модели, — по одной записи на диалект. Подключение берёт запись своего диалекта и не смотрит на остальные, поэтому одна модель работает на любой базе, к которой её направят:

from hare.dialects.postgresql.postgresql_table_options import PostgresqlTableOptions
from hare.dialects.clickhouse.clickhouse_table_options import ClickhouseTableOptions
from hare.dialects.sqlite.sqlite_table_options import SqliteTableOptions


class Book(Model):
    isbn = fields.CharField(max_length=13, primary_key=True)
    copies = fields.IntField()

    class Meta:
        table_options = [
            SqliteTableOptions(without_rowid=True),
            PostgresqlTableOptions(unlogged=True, storage_parameters={"fillfactor": 70}),
            ClickhouseTableOptions(engine="ReplacingMergeTree", order_by=("isbn",)),
        ]
Класс Параметр В CREATE TABLE
SqliteTableOptions without_rowid WITHOUT ROWID — строки хранятся прямо в индексе первичного ключа; нужен первичный ключ, который база не генерирует сама.
strict STRICT (SQLite 3.37+) — колонка хранит значения только своего типа — см. strict.
PostgresqlTableOptions unlogged CREATE UNLOGGED TABLE — без журнала предзаписи (WAL): запись быстрее, но после сбоя таблица очищается и не реплицируется.
storage_parameters WITH (...) — {"fillfactor": 70, "autovacuum_enabled": False}; значение — число, True/False или строка.
tablespace TABLESPACE name; None — табличное пространство базы по умолчанию.
partitioning PARTITION BY ... и по таблице на партицию — см. Партиционированные таблицы.
ClickhouseTableOptions engine ENGINE = ... — по умолчанию "MergeTree", любой движок со своими аргументами ("ReplacingMergeTree(version)").
order_by ORDER BY (...) — поля, по которым упорядочены строки, и выражения hare или RawSQLTerm; пусто — по первичному ключу (tuple(), если его нет). Первичный ключ должен быть его началом.
partition_by PARTITION BY ... — выражение (Trunc("viewed_at", "month")); None — одна партиция.
ttl TTL ... — выражение момента, когда строка удаляется (F("viewed_at") + timedelta(days=365)), или RawSQLTerm всего предложения TTL таблицы; None — без него.
settings SETTINGS name = value, ... — пары из имени и значения.
sample_by, column_codecs, column_ttls, projections, distributed_over, sharding_key, lightweight_updates См. Параметры таблицы ClickHouse.

Две записи для одного диалекта или запись, которая не является TableOptions, дают ConfigurationError. Пакет диалекта объявляет свой класс параметров (см. Как написать диалект).

Миграции записывают эти записи в файлы миграций; изменение записи для диалекта подключения — это операция AlterModelOptions. PostgreSQL меняет таблицу на месте (SET TABLESPACE, SET LOGGED/SET UNLOGGED, SET (...)/RESET (...)), SQLite пересоздаёт её. hare drift сравнивает хранение таблицы с тем, которое даёт таблице запись диалекта подключения, а inspectdb записывает параметры таблицы в Meta.table_options.

strict (SQLite)

STRICT (SQLite 3.37+) — колонка хранит значения только своего типа, неверное значение отвергает база; колонка другого типа создаётся с типом STRICT своей аффинности (INTEGER, REAL, TEXT, BLOB, ANY). Более старая SQLite даёт UnSupportedError до DDL; смена опции перестраивает таблицу.