skip to content

In a Django model, which field options does the database enforce, and which does only model validation check?

level: middleimportance: should knowfreq 48%

answer

  1. what appears in the CREATE TABLE
  2. NOT NULL, UNIQUE, VARCHAR(n)
  3. blank, choices, validators stay in Python
  4. save() skips full_clean()
  5. db_index adds no rule

basics

~10 s

The database enforces what reaches the schema: null=False (NOT NULL), unique=True (a unique constraint), CharField max_length (VARCHAR length) and db_default. blank, choices, validators and unique_for_date are checked only by full_clean() and forms.

solid answer

~40 s

Ask whether the option changes the SQL. `null=False` becomes `NOT NULL`; `unique=True` becomes a unique constraint and is also checked by `validate_unique()`; a `CharField`'s `max_length` becomes `VARCHAR(n)` and a `MaxLengthValidator`; `db_default` becomes a column `DEFAULT`; `db_index=True` only builds an index and enforces nothing. The rest lives in Python: `blank`, `choices`, `validators`, `unique_for_date` and a `TextField`'s `max_length` are checked only when `full_clean()` runs — in `ModelForm`s, the admin, or when you call it — and a plain `default` is applied when the instance is created in Python. `save()` does not call `full_clean()`, and `update()` and `bulk_create()` skip it too, so any rule that must hold for every row needs a database-level form: a column option or a `Meta` constraint.

go deeper

for a junior

Name the options that change the table — null, unique, CharField max_length — and know that blank, choices and validators are checked by forms and full_clean().

for a middle

Walk through each option's schema and validation effect and list the write paths that skip full_clean(), such as save(), update() and bulk_create().

for a senior

Move rules that must always hold into constraints, handle IntegrityError from unique races, and keep validators for friendly errors.

for a principal

Set the policy for which invariants live in the database versus application code when several services or imports write the same tables.

## The dividing line Every option on a Django model field ends up in one of two places: - **The schema** — the `CREATE TABLE`/`ALTER TABLE` Django generates. The database enforces these on **every** write, whoever makes it. - **Python validation** — `Model.full_clean()`, which `ModelForm`s and the admin call, and which you may call yourself. These run only on paths that validate. `Model.save()` does **not** call `full_clean()`, and queryset `update()`, `bulk_create()`, data migrations and raw SQL bypass model validation entirely. So the practical question for any rule is: which layer holds it? ## Option by option | option | in the schema | in model validation | |---|---|---| | `null=False` (default) | `NOT NULL` | rejects `None`, unless `blank=True` skips the empty value | | `unique=True` | unique constraint, which also creates an index | `validate_unique()` checks for duplicates | | `CharField(max_length=n)` | `VARCHAR(n)` (PostgreSQL and SQLite also allow no limit) | `MaxLengthValidator` | | `db_default` (5.0+) | column `DEFAULT` | skipped for unsaved defaults | | `db_index=True` | an index, no rule | nothing | | `default` | nothing on the column | nothing; applied when the instance is built | | `blank` | nothing | allows empty values | | `choices` | nothing | rejects values outside the choices | | `validators` | nothing | runs each validator | | `unique_for_date` | nothing | checked by `validate_unique()` | | `TextField(max_length=n)` | nothing | nothing; only the form widget's `maxlength` | A few of these deserve a note: - **`unique=True`** is enforced twice. Validation gives a friendly error, but two concurrent requests can both pass it; the database constraint is what actually prevents the duplicate, surfacing as `IntegrityError` from `save()`. Setting `db_index=True` alongside `unique=True` is redundant. - **`db_index=True`** speeds lookups but allows any value. Django's documentation now prefers `Meta.indexes`, and says `db_index` may be deprecated in future. - **`default`** is Python-only. When a migration adds a column with a `default`, Django uses it to fill existing rows and then drops the `DEFAULT` from the column, so rows inserted outside Django get nothing. `db_default` keeps a real `DEFAULT`. - **`FileField`** is stored as a `VARCHAR` holding the file's name, `max_length=100` by default, so long upload paths can exceed it. ## The menu item, annotated ```python from decimal import Decimal from django.core.validators import MinValueValidator from django.db import models class MenuItem(models.Model): name = models.CharField(max_length=120, unique=True) # VARCHAR(120) + UNIQUE description = models.TextField(blank=True) # blank: validation only price = models.DecimalField( max_digits=7, decimal_places=2, validators=[MinValueValidator(Decimal("0.01"))], # validation only ) diet = models.CharField( max_length=5, choices=[("none", "No restriction"), ("vegan", "Vegan")], # validation only default="none", # Python only ) photo = models.FileField(upload_to="menu/", blank=True) # VARCHAR(100) ``` A nightly import that calls `MenuItem.objects.bulk_create(...)` can therefore store a price of `0` and a diet of `"keto"`, but not a duplicate `name`, a missing `name`, or a 200-character `name` on PostgreSQL. ## Checking a model quickly The fastest way to settle an argument about what the database enforces is to read the SQL Django will run: ```bash python manage.py sqlmigrate menu 0001 ``` Whatever appears in that `CREATE TABLE` — `NOT NULL`, `UNIQUE`, `varchar(120)`, a `DEFAULT`, a `CHECK` — the database enforces. Anything you declared on the model that is missing from it lives only in Python validation. ## Moving a rule into the database When a Python-only rule must hold for every row, express it in the schema: 1. Tighten the column: `null=False`, `unique=True`, a narrower `max_length`. 2. Add a `CheckConstraint` or `UniqueConstraint` in `Meta.constraints` for value ranges, allowed values or conditional uniqueness. Since Django 4.1, `full_clean()` also validates those constraints, so forms still get friendly errors. 3. Keep the Python validator too when you want a clear message before the database refuses the write. ## Interview framing The crisp answer is: *the database enforces what changes the SQL; everything else is advice to validation*. Then name the paths that skip validation — `save()` on its own, `update()`, `bulk_create()`, raw SQL — because that is where the difference bites.

  • If unique=True is enforced by the database, why does validation also check it?
    `validate_unique()` gives a readable form error before any write. It cannot prevent a race — two requests can both pass the check — so the database's unique constraint is the real guarantee, and code that saves must still handle `IntegrityError`.
  • Does a model field's default end up in the database schema?
    No. Django applies `default` when the instance is created in Python; a migration that adds the column uses it to fill existing rows and then drops the column `DEFAULT`. Use `db_default` (Django 5.0+) when the database itself should supply the value.

saying these in an interview costs you the question

  • save() runs full_clean(), so validators always apply
  • choices and validators become database CHECK constraints
  • db_index=True prevents duplicate values
  • a field's default is written into the column DEFAULT
  • TextField(max_length=...) limits the column length