In a Django model, what is the inner class Meta for, and which options does it typically hold?
answer
- about the model, not a column
- table name, order, labels
- indexes and constraints live here
- app label plus model name
basics
~20 sclass 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 sThe 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 linesfrom 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
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.
Explain which Meta options change the schema and which are recorded as AlterModelOptions, and why UniqueConstraint replaces unique_together.
Review Meta changes by their database cost: renames, new indexes and constraints on large tables versus harmless option changes.
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.