null vs blank
coreintermediatenull is database-level: null=True lets a column store SQL NULL. blank is validation-level: blank=True lets a form/full_clean() accept an empty value. They are independent — setting one does not set the other, and both are usually needed together for a genuinely optional field.
Think of it as
null answers "what can the DATABASE COLUMN hold" — blank answers "what can a FORM leave empty." For a CharField/TextField, Django's own convention is to never need null=True at all: an empty string ("") is already the string type's own "nothing here" value, so allowing NULL too just creates two different ways to mean the same thing (redundant, and a source of `if value` vs `if value is not None` bugs). A DateField or ForeignKey has no such empty value of its own — NULL is the only way those types can represent "nothing here," so null=True is the normal, correct choice for an optional field of those types.
What we're doing: Make a text field and a date field both genuinely optional, using the correct null/blank combination for each type.
- 3
- subtitle has no null=True — an unset subtitle is stored as "" (empty string), the string type's own natural "nothing here" value.
- 4
- published_at needs BOTH — a DateField has no empty-but-valid date, so NULL (via null=True) is the only way to represent "not yet published," and blank=True is what lets a form leave it unset.
Why this works: The two fields need different treatment because of what each type CAN represent as "empty" on its own — a string already has "" for that; a date has nothing analogous, so it borrows NULL from the database layer instead. Using null=True on subtitle too would just create a second, redundant "empty" state ("" and NULL both meaning "no subtitle"), one more thing every reader of this code has to account for.
Setting null=True on a CharField "just in case," without blank=True
Wrong
Better
What you see: A form built from this model still marks subtitle as required — leaving it empty fails validation — even though the database column itself would happily accept NULL. Separately, code now has to check both `if article.subtitle` AND `article.subtitle is not None` to mean the same thing.
Why: null only ever affects the database column, never form/full_clean() validation — blank is the ONLY setting that makes a field optional in a form, so null=True alone does nothing to relax that requirement. And because CharField already has "" as its own empty value, adding null=True on top creates two different representations of "no subtitle" that every reader and every query now has to handle.
- title: blank=False (required), null=False (NOT NULL) — required, no NULL — the normal default
- subtitle (CharField): blank=True (form may skip), null=False (NOT NULL) — blank=True alone — "" is the empty value
- published_at (DateField): blank=True (form may skip), null=True (may store NULL) — needs BOTH — no empty date exists
null vs blank — what each actually controls
Together
Remember: null is database-level (can the column be NULL); blank is validation-level (can a form leave it empty) — independent settings, usually needed TOGETHER for a non-text optional field, but text fields should use blank=True alone.
See also: models · text and numeric fields · primary keys and field options

