skip to content

Meta Options & Constraints

The class Meta block: ordering, indexes, UniqueConstraint and CheckConstraint, db_table and default_related_name. Interviewers probe conditional unique constraints and the cost of default ordering.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Django model, what is the inner class Meta for, and which options does it typically hold?

level: juniorimportance: must knowfreq 60%

answer

  1. about the model, not a column
  2. table name, order, labels
  3. indexes and constraints live here
  4. app label plus model name

basics

~20 s

class Meta holds model-level options rather than fields: the table name (db_table), default ordering, human-readable names, indexes, constraints and a few behaviour switches. Django reads it when building the model's _meta; it never becomes a column.

solid answer

~30 s

The nested `class Meta` carries **model-level** configuration, as opposed to fields, which become columns. The everyday options are `ordering`, `db_table` (default `<app_label>_<model_name>`, like `billing_subscription`), `verbose_name` and `verbose_name_plural`, `indexes`, `constraints` and `get_latest_by`; others such as `abstract`, `proxy`, `managed`, `permissions` and `default_related_name` switch behaviour. Django collects them into the model's `_meta` options object. Some change the schema, so `makemigrations` emits SQL-producing operations for `db_table`, `indexes` and `constraints`, while options like `ordering` and `verbose_name` are recorded as `AlterModelOptions` with no table change. `unique_together` still exists but the docs point to `UniqueConstraint` instead.

code

python · 31 lines
python
from django.conf import settings
from django.db import models
from django.db.models import Q


class Subscription(models.Model):
    class Status(models.TextChoices):
        ACTIVE = "active"
        CANCELLED = "cancelled"

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    plan = models.ForeignKey("billing.Plan", on_delete=models.PROTECT)
    status = models.CharField(max_length=20, choices=Status)
    started_at = models.DateTimeField()
    renews_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-started_at"]
        verbose_name = "subscription"
        verbose_name_plural = "subscriptions"
        get_latest_by = "started_at"
        constraints = [
            models.UniqueConstraint(
                fields=["user"],
                condition=Q(status="active"),
                name="one_active_subscription_per_user",
            ),
        ]
        indexes = [
            models.Index(fields=["renews_at"], name="subscription_renews_idx"),
        ]

go deeper

for a junior

Recall that Meta holds model-level settings such as ordering, db_table and verbose_name, and that the default table name is app label plus model name.

for a middle

Explain which Meta options change the schema and which are recorded as AlterModelOptions, and why UniqueConstraint replaces unique_together.

for a senior

Review Meta changes by their database cost: renames, new indexes and constraints on large tables versus harmless option changes.

for a principal

Set conventions for Meta across apps: naming for constraints and indexes, when explicit db_table names are allowed, and how legacy options are phased out.

## Fields versus Meta A Django model class has two kinds of content: - **Fields** (`models.CharField`, `models.ForeignKey`, ...) declared as class attributes. Each becomes a column, or a join table for a many-to-many. - **Metadata**, declared inside a nested `class Meta:`. It describes the model as a whole: what its table is called, how its rows sort by default, which indexes and constraints exist, what the admin calls it. Django reads `Meta` while creating the model class and stores the result on `Model._meta`, an options object the ORM, the admin, forms and migrations all consult. `Meta` itself never becomes a column, and it is optional: a model without it gets sensible defaults. ## The options you meet most | Option | What it does | Default | |---|---|---| | `db_table` | name of the table | `<app_label>_<model_name>`, e.g. `billing_subscription` | | `ordering` | default `ORDER BY` for queries | none (unspecified order) | | `verbose_name`, `verbose_name_plural` | human-readable names (admin, messages) | derived from the class name; plural adds "s" | | `indexes` | list of `Index` objects | none beyond field-level indexes | | `constraints` | list of `UniqueConstraint`, `CheckConstraint` | none | | `get_latest_by` | default field(s) for `latest()` and `earliest()` | none, so those methods need arguments | | `unique_together` | legacy multi-column uniqueness | none; `UniqueConstraint` is recommended instead | Behaviour switches such as `abstract`, `proxy` and `managed`, plus `permissions` and `default_related_name`, also live in `Meta`, but each belongs to a larger subject (inheritance styles, permissions, relations). ## What reaches the database Not every option changes the schema. When you edit `Meta` and run `makemigrations`: 1. **Schema options** produce operations that run SQL: a changed `db_table` becomes `AlterModelTable`, a new entry in `indexes` becomes `AddIndex`, a new entry in `constraints` becomes `AddConstraint`. 2. **Python-only options** such as `ordering`, `verbose_name`, `get_latest_by` and `permissions` are recorded in an `AlterModelOptions` operation. Migrations still track them, because historical models need them, but applying the operation changes no table. Knowing the difference matters in review: an `AlterModelOptions` migration is safe to ship any time, while an `AddIndex` on a large table is real work for the database. ## How Django uses _meta Everything declared in `Meta`, plus what Django derives from the fields, ends up on `Model._meta`. You rarely need it in application code, but it explains where the options go: - `Subscription._meta.db_table` gives `"billing_subscription"`; - `Subscription._meta.ordering` and `Subscription._meta.constraints` hold the lists you declared; - `Subscription._meta.get_field("status")` returns the field object, which generic code (the admin, serializers, form factories) uses to build itself; - `Subscription._meta.verbose_name_plural` is what the admin shows as a heading. Because so much of Django reads this one object, a `Meta` option is often a single line that changes behaviour in several places at once. ## A subscription model In a billing app, a `Subscription` belongs to a user and a plan and moves through statuses. Its `Meta` states how lists are ordered, how the admin labels it, and the rule that a user can hold only one active subscription: - `ordering = ["-started_at"]` so the newest subscription comes first when no explicit order is given; - `verbose_name = "subscription"` and a matching plural; - a `UniqueConstraint` with a `condition` for the one-active rule; - an `Index` for the renewal job's lookups. The code example shows the whole block. ## Common mistakes - Declaring an option as a plain class attribute (`ordering = [...]` next to the fields) instead of inside `Meta`: Django ignores it there. - Reaching for `unique_together` in new code. It still works, but the docs call `UniqueConstraint` the replacement and note that `unique_together` may be deprecated; `index_together` has already been removed (Django 5.1) in favour of `indexes`. - Setting `db_table` to match a naming fashion on an existing model without planning the rename: it generates a table rename that other code (raw SQL, reporting tools) may not expect. - Treating `verbose_name` as cosmetic only: it shows up in admin headings, validation messages and permission names. A candidate who can say which `Meta` options touch the schema and which are Python-only shows they understand how Django turns a class into a table.

  • What does Meta.get_latest_by change in Django, and what happens without it?
    It names the default field or fields for `latest()` and `earliest()`, so `Subscription.objects.latest()` means "by `started_at`". Without it, calling `latest()` with no arguments raises `ValueError`, and you must pass the field explicitly, such as `latest("started_at")`. Both methods raise the model's `DoesNotExist` when the queryset is empty.
  • Why does changing Meta.ordering create a migration in Django even though the table does not change?
    Migrations keep a full historical copy of each model's state, including options that code running inside migrations may rely on. `makemigrations` therefore records `ordering`, `verbose_name` and similar options in an `AlterModelOptions` operation. Applying it updates the migration state and runs no SQL.

saying these in an interview costs you the question

  • Options in class Meta become hidden columns on the table.
  • Without db_table, the table is named after the class alone.
  • Changing Meta.ordering needs a data migration to reorder rows.
  • unique_together is the recommended way to add multi-column uniqueness.
  • Putting ordering = [...] among the fields works the same as in Meta.
open as a page

In Django, how do you enforce at most one active subscription per user with Meta.constraints, and why not unique_together?

level: middleimportance: must knowfreq 55%

basics

~10 s

Add UniqueConstraint(fields=["user"], condition=Q(status="active"), name=...) to Meta.constraints. The database then allows many cancelled rows per user but only one active one. unique_together cannot take a condition and is the legacy option.

open as a page

In Django 6.1, how do you declare a CheckConstraint in Meta.constraints, and on which code paths is it enforced?

level: middleimportance: should knowfreq 40%

basics

~20 s

Add models.CheckConstraint(condition=Q(...), name=...) to Meta.constraints. The database rejects any write that breaks it with IntegrityError, whatever the code path; full_clean() also checks it and raises ValidationError. The old check= keyword was removed in Django 6.0.

open as a page

In Django, which queries does Meta.ordering apply to, and what does a default ordering cost?

level: middleimportance: should knowfreq 44%

basics

~20 s

Meta.ordering adds an ORDER BY to every query on the model that does not call order_by(), including related managers and first()/last(). Aggregation queries with GROUP BY skip it. The cost is a sort on every such query.

open as a page

In a Django model, when do you declare Meta.indexes with Index(condition=...), include= or expressions instead of setting db_index=True on a field?

level: seniorimportance: should knowfreq 32%

basics

~10 s

db_index=True gives one plain single-column index. Meta.indexes with models.Index adds multi-column, descending, partial (condition), covering (include) and functional (expressions) indexes. Those need a name, and some backends ignore condition or include.

open as a page