Meta options¶
Database-level model behavior beyond individual fields is configured through a nested Meta class:
class Foo(Model):
...
class Meta:
table = "custom_table"
constraints = [UniqueConstraint(fields=("field_a", "field_b"))]
| Option | Meaning |
|---|---|
abstract |
No table; the fields and Meta pass to the concrete subclasses — see abstract. |
table |
The table's name — see table. |
primary_key |
None for a table without a primary key — see Models without a primary key. |
managed |
False leaves the table to something other than migrations — see managed. |
schema |
The table's schema — an attached database on SQLite, a database of the server on ClickHouse — see schema. |
app |
The app label of "app.Model" references — see app. |
swappable |
The setting through which a project replaces the model — see Swappable models. |
extensions |
The PostgreSQL extensions the model needs — see Extensions. |
constraints |
Unique, check and exclusion constraints — see Constraints. |
triggers |
The table's triggers — see Triggers. |
views |
Views of SQL or of a queryset — see Views. |
materialized_views |
Materialized views — see Materialized views. |
dictionaries |
Dictionaries of the model's rows, read by key (ClickHouse) — see Dictionaries. |
functions |
Functions stored in the database — see Functions. |
sequences |
Sequences, created before the table — see Sequences. |
row_level_security |
Row level security of the table, on or forced — see Row level security. |
policies |
The row level security policies of the table — see Row level security. |
grants |
Privileges on the table and the model's other objects — see Grants. |
indexes |
The table's indexes — see Indexes. |
ordering |
The default ordering of the model's queries — see ordering. |
get_latest_by |
What latest() and earliest() order by — see get_latest_by. |
soft_delete_field |
The DatetimeField marking a soft-deleted row — see Soft delete. |
soft_delete_hard_cascade |
True really deletes the rows a soft delete cascades to — see soft_delete_hard_cascade. |
optimistic_lock_field |
The version every write of an instance checks and bumps — see Optimistic locking. |
tenant_field |
The column holding the row's tenant — see tenant_field. |
tenant_schema |
True keeps the table in each tenant's schema — see Schema per tenant. |
track_dirty_fields |
True keeps the snapshot of each row get_dirty_fields() compares with — see Tracking changes. |
change_capture |
Every write of the model also writes its changed rows to the outbox — see Capturing a model's changes. |
returning |
The default of returning of bulk_create() and bulk_update() — see returning. |
table_description |
The table's comment — see table_description. |
table_options |
How each dialect stores the table — see Table options. |
manager |
The manager of Model.objects — see manager. |
abstract¶
True: no table is generated for the model; its fields and Meta are inherited by the concrete
subclasses, each getting a deep copy of them rather than a shared object. A model is abstract only when
its own Meta says so: a Meta subclassing the abstract model's (class Meta(Base.Meta)) leaves it
concrete.
A concrete model subclassing another concrete model is a model of its own: a table of its own, with copies of
the fields, and none of the other model's Meta options but its manager, copied for the
subclass — its Model.objects filters the subclass's rows the same way. A relation other models point at the
other model with isn't the subclass's — its rows aren't in that table — so reading it on the subclass
(child.refs) raises AttributeError.
table¶
Database table name override (default: derived from the model name). PostgreSQL cuts an identifier to 63
bytes: a generated table name, M2M through table or through column longer than that is shortened with a
digest suffix, so two long names never become the same identifier; an explicit table, column
(source_field or the field name), through table, index or constraint name over 63 bytes raises
ConfigurationError on every database. A name holding a dot raises ConfigurationError too — the schema
is Meta.schema.
managed¶
bool, default True. False makes makemigrations/migrate, Hare.generate_schemas(), hare drift
and truncate_all_models() skip this model's table entirely — it's never created, altered, or flagged as
drift, as if it didn't exist to the migration system. For a model backed by something the migration
system shouldn't own (a view, a table introspected at runtime) — see
Hare.register_live_models(). Switching an existing model's managed either way
only alters its options in the migration state — its table is never dropped or created for it (see
Operations).
schema¶
The schema the table is in: a schema on PostgreSQL, an attached database on SQLite (the connection
attaches it — see attached_databases), a
database of the server on ClickHouse. Two models may use the same table in different schemas —
Hare.generate_schemas() orders the tables by schema and name. Migrations create and drop a schema where
the database has CREATE SCHEMA (Features.supports_schemas, PostgreSQL); elsewhere the database has to
exist. On SQLite a relation with db_constraint=True to a model of another schema is refused when the
model is bound — a foreign key there references only a table of its own database
(Features.references_other_schemas).
app¶
The app label in "app.Model" string references.
ordering¶
Tuple of ordering strings, e.g. ("-created_at", "name"), or F("field").asc()/.desc(), or an
expression (Lower("name").desc(nulls_last=True), F("price") * F("quantity")) — the default
.order_by(), also of first()/last()/reverse(), iterator(), a slice, a related manager and
prefetch_related(), of a query with an aggregate annotation too; a .values() query grouped by the
fields named leaves it out (Implicit GROUP BY);
.order_by() with no arguments drops it.
get_latest_by¶
A field name or a sequence of them ("-created_at" for descending), e.g. ("created_at", "id") — what
latest()/earliest() order by when given no fields. Anything else, or a name that is not a field of
the model, raises ConfigurationError when the model is declared.
soft_delete_hard_cascade¶
bool, default False. Needs soft_delete_field. True makes a soft delete really delete the cascaded
rows of models without soft_delete_field and the auto-generated M2M through rows, as a hard delete
would; by default they are kept, so restore() brings the relations back — see
Soft delete.
tenant_field¶
Name of the column holding the row's tenant — see
Multi-tenancy. A forward
ForeignKeyField/OneToOneField name means its key column (company → company_id); a relation with a
composite key or a field without a column of its own raises ConfigurationError.
returning¶
bool, default False — the default of the returning parameter of bulk_create()/bulk_update()
when a call leaves it as None; an explicit True/False on a call always overrides it — see
bulk_create().
table_description¶
The table's description, written as its comment; when unset, the first line of the model's docstring.
SQLite keeps it as a comment inside the parentheses of CREATE TABLE — a migration changing it rebuilds
the table.
manager¶
A Manager() instance customizing the default queryset (e.g. auto-filters). Fresh-copied per concrete
subclass. Whatever its get_queryset() filters is also applied to every JOIN to the model
(select_related(), .only(), .order_by(), nested .filter()), so a joined row is scoped exactly
like Model.objects.all(); only plain .filter()/.exclude() conditions on the model's own fields are
supported there (annotations, LIMIT, or a filter across a further relation raise ConfigurationError).
Its filter is not applied by refresh_from_db(), nor by delete()/delete_preview()'s
CASCADE/SET_NULL/SET_DEFAULT handling and PROTECT/RESTRICT checks — those see every row that
exists (and every row physically referencing the deleted ones), keeping only
Meta.tenant_field/Meta.soft_delete_field handling. A Manager assigned as a class attribute (e.g.
all_objects = Manager()) on the model, an abstract base or a plain mixin class is copied together with
its constructor arguments and bound to each concrete model.
Options of extensions¶
An extension of hare reads options of its own from Meta too: outbox_wakeup of an
outbox model, new_version_excluded_fields of a versioned
model. They describe what the extension does, not
the table, so migrations don't write them. An extension declares the options it reads with
ExtensionModelOptions.allow("name", ...) (hare.models.class_building.extension_model_options) when it
is imported.
What Meta refuses¶
Any other Meta attribute — inherited from a base of Meta too — raises ConfigurationError when the
class is defined, naming the option it is closest to
(Meta.ordring isn't a model option - did you mean Meta.ordering?): a misspelled option would otherwise
silently do nothing. db_table, app_label and db_table_comment name the hare option to use instead
(table, app, table_description).
An option naming something takes a non-empty text, and each entry of extensions the name of an
extension — another value raises ConfigurationError: it would go into the DDL.
default_connection is not a Meta option — it's set at app-registration time via Hare.init()'s
apps.<name>.default_connection config key, not declared inside class Meta; one written in Meta
raises ConfigurationError saying so.
Models without a primary key¶
An append-only log, an event stream or a columnar store's table often has no primary key.
Meta.primary_key = None declares such a model — no id is added and the table is created without a
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
What works: create()/save() of a new row, bulk_create(), every read (filter(), exclude() —
across relations too — values(), aggregate(), annotate(), group_by(), select_related(),
prefetch_related() of its forward relations, iterator(), get(), first()), QuerySet.update() and
QuerySet.delete() by any condition, and reaching the rows from the related model
(book.readings.all(), Book.objects.filter(readings__reader=...), Count("readings"),
readings__isnull=True, prefetch_related("readings")). A condition through a relation in exclude(),
update() or delete() matches the written row by every column, NULLs matching NULLs — rows equal in
every column are indistinguishable anyway. update().returning() and delete().returning() build the
instances from the rows RETURNING gives, every column of each — there is no key to read them back by —
and a soft delete (Meta.soft_delete_field) writes and returns the rows the same way, include_deleted()
included.
What needs a primary key raises ConfigurationError naming the model: save() of a fetched row (an
UPDATE), delete()/restore()/refresh_from_db() of one instance, a ForeignKeyField/OneToOneField
to the model without to_field=, and a
ManyToManyField on it or to it. pk in a filter or an ordering is a FieldError; instance.pk is
None, and an instance is equal only to itself. Across the relation from the other model, only
isnull/not_isnull compare the related rows as a whole (readings__isnull=True); readings=,
readings__in= name a row by its key and raise FieldError. count() of model rows made .distinct()
raises QueryError before any SQL — a row's identity is its key; values()/values_list() rows count
distinct. iterator() pages with OFFSET where the ordering can tie, so rows equal in it all come, and a
sliced Prefetch of such rows reads each parent's rows whole and takes its slice of them.
Migrations create and alter the table like any other (the state keeps primary_key: None); giving the
model a primary key or taking it away, like any change of a primary key, needs a hand-written migration.
inspectdb turns a table without a primary key into such a model — a unique index of the table stays a
unique index.
Table options¶
Meta.table_options lists how each dialect stores the model's table — one entry per dialect. A
connection uses the entry of its own dialect and ignores the others, so one model runs on every database
it is routed to:
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",)),
]
| Options | Option | CREATE TABLE |
|---|---|---|
SqliteTableOptions |
without_rowid |
WITHOUT ROWID — the rows are stored in the primary key's index; needs a primary key the database doesn't generate. |
strict |
STRICT (SQLite 3.37+) — each column holds values of its declared type only — see strict. |
|
PostgresqlTableOptions |
unlogged |
CREATE UNLOGGED TABLE — no write-ahead log: faster writes, emptied after a crash, not replicated. |
storage_parameters |
WITH (...) — {"fillfactor": 70, "autovacuum_enabled": False}; a name maps to a number, a boolean or a string. |
|
tablespace |
TABLESPACE name; None is the database's default tablespace. |
|
partitioning |
PARTITION BY ... and a table per partition — see Partitioned tables. |
|
ClickhouseTableOptions |
engine |
ENGINE = ... — "MergeTree" by default, any engine with its arguments ("ReplacingMergeTree(version)"). |
order_by |
ORDER BY (...) — the fields the rows are sorted by, and expressions of hare or RawSQLTerm; empty for the primary key (tuple() without one). A primary key must be its beginning. |
|
partition_by |
PARTITION BY ... — an expression (Trunc("viewed_at", "month")); None for one partition. |
|
ttl |
TTL ... — an expression of the moment a row is deleted (F("viewed_at") + timedelta(days=365)), or RawSQLTerm of the table's whole TTL clause; None for none. |
|
settings |
SETTINGS name = value, ... — pairs of a name and a value. |
|
sample_by, column_codecs, column_ttls, projections, distributed_over, sharding_key, lightweight_updates |
See ClickHouse table options. |
Two entries for one dialect, or an entry that isn't a TableOptions, raise ConfigurationError. A
dialect package declares its own options class (see Writing a
dialect).
Migrations write the entries into migration files; a change of the connection's entry is an
AlterModelOptions — PostgreSQL alters the table in place (SET TABLESPACE,
SET LOGGED/SET UNLOGGED, SET (...)/RESET (...)), SQLite rebuilds it. hare drift compares the
table's storage with the storage the entry of the connection's dialect gives a table, and inspectdb
writes a table's options into Meta.table_options.
strict (SQLite)¶
STRICT (SQLite 3.37+) — each column holds values of its declared type only, a wrong value is refused by
the database; a column of another type is created as the STRICT type of its affinity (INTEGER,
REAL, TEXT, BLOB, ANY). An older SQLite raises UnSupportedError before any DDL; changing it
rebuilds the table.