Model Fields & Field Options
Reviewed & published by Brayan K
A model field is a class attribute on a Django model that maps to one column in the database table and declares that column's data type, such as CharField for text or IntegerField for whole numbers.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll meet the common field types, learn the options that shape each column — null, blank, default, unique, choices, and db_index — and finally untangle the classic confusion between null and blank.
What You'll Learn in This Lesson
- The common field types and the columns they create
- CharField max_length and TextField for long text
- DecimalField, BooleanField, DateTimeField, EmailField, SlugField
- Field options: default, unique, choices, db_index
- How a ForeignKey models a relationship
- The crucial difference between null and blank
1 The Common Field Types
Every attribute you put on a model is a field, and each field class maps to a database column type. CharField holds short text and requires a max_length; TextField holds unbounded text; IntegerField and DecimalField hold numbers; BooleanField holds true/false; and DateTimeField holds a timestamp. Specialised text fields like EmailField and SlugField are CharFields with extra validation built in.
# blog/models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200) # short text, VARCHAR(200)
description = models.TextField() # long text, no max_length
quantity = models.IntegerField() # whole number
price = models.DecimalField(max_digits=6, # exact money, e.g. 9999.99
decimal_places=2)
in_stock = models.BooleanField() # True / False
created_at = models.DateTimeField(auto_now_add=True) # set once on insert
contact = models.EmailField() # validates "name@host"
slug = models.SlugField() # url-safe "my-product"A ForeignKey is special: instead of a scalar value it stores a link to a row in another table, turning one model into a child of another.
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey( # link to one Category row
Category,
on_delete=models.CASCADE, # delete products if category goes
)# Each field type validates a value differently. Here are plain-Python
# validators that mirror what CharField, IntegerField and EmailField do.
def validate_charfield(value, max_length):
if not isinstance(value, str):
return "invalid: not text"
if len(value) > max_length:
return "invalid: longer than max_length " + str(max_length)
return "ok"
def validate_integerfield(value):
if not isinstance(value, int):
return "invalid: not a whole number"
return "ok"
def validate_emailfield(value):
if "@" not in value:
return "invalid: missing @"
return "ok"
print("name ->", validate_charfield("Keyboard", 200))
print("name ->", validate_charfield("x" * 201, 200))
print("qty ->", validate_integerfield(42))
print("qty ->", validate_integerfield("42"))
print("email ->", validate_emailfield("sales@shop.com"))
print("email ->", validate_emailfield("not-an-email"))
# EXPECTED:
# name -> ok
# name -> invalid: longer than max_length 200
# qty -> ok
# qty -> invalid: not a whole number
# email -> ok
# email -> invalid: missing @
# ✅ Expected output:
# name -> ok
# name -> invalid: longer than max_length 200
# qty -> ok
# qty -> invalid: not a whole number
# email -> ok
# email -> invalid: missing @2 Field Options: default, unique, choices & db_index
Every field accepts options that refine its behaviour. default supplies a value when none is given, unique=True forbids duplicate values across rows, choices restricts the field to a fixed set of values, and db_index=True adds a database index to speed up lookups.
# blog/models.py
from django.db import models
class Post(models.Model):
STATUS_CHOICES = [
("draft", "Draft"),
("published", "Published"),
("archived", "Archived"),
]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True) # no two posts share a slug
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
default="draft", # used when none supplied
)
views = models.IntegerField(default=0)
email = models.EmailField(db_index=True) # indexed for fast lookups- default=value — the value stored when you don't set the field yourself.
- unique=True — the database rejects a second row with the same value.
- choices=[...] — only the listed values are valid; the admin shows a dropdown.
- db_index=True — build an index so filtering on the column is fast.
# choices restricts a field to a known set; default fills in a value when
# none is given. This mirrors that logic for a "status" field.
STATUS_CHOICES = [
("draft", "Draft"),
("published", "Published"),
("archived", "Archived"),
]
valid_values = [value for value, label in STATUS_CHOICES]
def clean_status(value=None):
if value is None:
return "draft" # the field's default
if value not in valid_values:
return "invalid: " + value + " not in " + str(valid_values)
return value
print("no value given ->", clean_status())
print("published ->", clean_status("published"))
print("deleted ->", clean_status("deleted"))
# EXPECTED:
# no value given -> draft
# published -> published
# deleted -> invalid: deleted not in ['draft', 'published', 'archived']
# ✅ Expected output:
# no value given -> draft
# published -> published
# deleted -> invalid: deleted not in ['draft', 'published', 'archived']3 null vs blank: The Crucial Distinction
These two options look alike but live in different worlds. null is about the database: null=True lets the column hold a real SQL NULL. blank is about forms and validation: blank=True lets a form accept an empty value without complaining. One controls storage, the other controls whether the field is required when a human fills in a form.
# blog/models.py
class Profile(models.Model):
# Required in forms, NOT NULL in the database (the defaults)
username = models.CharField(max_length=50)
# Optional in forms; stored as "" rather than NULL (string convention)
bio = models.TextField(blank=True)
# Truly optional date: empty form allowed AND NULL in the database
birth_date = models.DateField(null=True, blank=True)
# null alone: DB can be NULL, but forms still require a value
nickname = models.CharField(max_length=50, null=True)The standard rules of thumb are worth memorising:
- null=True — the database column is allowed to store NULL.
- blank=True — Django forms accept an empty value (the field is optional).
- For text fields, prefer blank=True alone and store "" instead of NULL.
- For non-text optional fields (dates, numbers, FKs) use null=True, blank=True together.
# null is database-level (can the column be NULL?).
# blank is form-level (is an empty value allowed when validating a form?).
# They are independent, so we model each field with both flags.
def db_allows_null(value, null):
if value is None and not null:
return "DB ERROR: column is NOT NULL"
return "DB ok"
def form_allows_empty(value, blank):
if value in (None, "") and not blank:
return "FORM ERROR: this field is required"
return "FORM ok"
# bio = TextField(blank=True): empty form ok, but DB is still NOT NULL
print("bio DB :", db_allows_null("", null=False))
print("bio FORM:", form_allows_empty("", blank=True))
# birth_date = DateField(null=True, blank=True): truly optional
print("date DB :", db_allows_null(None, null=True))
print("date FORM:", form_allows_empty(None, blank=True))
# username = CharField(): required everywhere
print("user FORM:", form_allows_empty("", blank=False))
# EXPECTED:
# bio DB : DB ok
# bio FORM: FORM ok
# date DB : DB ok
# date FORM: FORM ok
# user FORM: FORM ERROR: this field is required
# ✅ Expected output:
# bio DB : DB ok
# bio FORM: FORM ok
# date DB : DB ok
# date FORM: FORM ok
# user FORM: FORM ERROR: this field is required🎯 Your Turn: Enforce a CharField max_length
Fill in the blank so the validator rejects any value longer than the field's max_length.
# 🎯 YOUR TURN: replace ___ so values longer than max_length are rejected.
def validate_charfield(value, max_length):
if len(value) ___ max_length: # too long -> invalid
return "invalid: too long"
return "ok"
print(validate_charfield("Mouse", 200))
print(validate_charfield("x" * 250, 200))
# EXPECTED OUTPUT:
# ok
# invalid: too longCommon Errors (And How to Fix Them)
❌ "CharFields must define a 'max_length' attribute"
You declared a CharField (or SlugField) without telling Django how wide the column should be.
✅ Fix: add max_length=..., e.g. models.CharField(max_length=200).
❌ Empty form field saves NULL on a CharField
You set null=True on a text field, so now blanks become NULL and you have two kinds of "empty".
✅ Fix: drop null=True and use blank=True instead so empties store as "".
❌ "duplicate key value violates unique constraint"
A field marked unique=True received a value that already exists in another row.
✅ Fix: ensure the value is unique before saving, or remove unique=True if duplicates are actually allowed.
📋 Quick Reference
| Field / Option | What It Does |
|---|---|
| CharField(max_length=N) | Short text column, capped at N characters |
| DecimalField(max_digits, decimal_places) | Exact decimal number, ideal for money |
| ForeignKey(Model, on_delete=...) | A link to a row in another table |
| null=True | Database column may store NULL |
| blank=True | Forms accept an empty value (optional) |
🏆 Mini Challenge: Validate a Whole Model Instance
Combine everything: write a validator that checks a row against several field rules at once and reports each problem it finds.
# 🏆 Mini Challenge: validate one Product row against its field rules.
# Rules: name is a CharField(max_length=20), price is a DecimalField
# (must be a positive number), status uses choices, email needs an "@".
CHOICES = ["draft", "published", "archived"]
def validate_product(row):
errors = []
if len(row["name"]) > 20:
errors.append("name too long")
if not isinstance(row["price"], (int, float)) or row["price"] <= 0:
errors.append("price must be a positive number")
if row["status"] not in CHOICES:
errors.append("status not a valid choice")
if "@" not in row["email"]:
errors.append("email missing @")
return errors if errors else ["valid"]
good = {"name": "Mug", "price": 9.99, "status": "published", "email": "a@b.com"}
bad = {"name": "x" * 30, "price": -1, "status": "deleted", "email": "nope"}
print("good ->", validate_product(good))
print("bad ->", validate_product(bad))
# EXPECTED:
# good -> ['valid']
# bad -> ['name too long', 'price must be a positive number', 'status not a valid choice', 'email missing @']❓ Frequently Asked Questions
Lesson complete — you can design any model field with confidence!
You now know the common field types and the columns they create, when to reach for default, unique, choices, and db_index, how a ForeignKey links two tables, and the all-important difference between null (database) and blank (forms).
Practice quiz
Which option is required on a CharField?
- default
- null
- max_length
- unique
Answer: max_length. CharField must define max_length for its column width.
Which field stores large, unbounded text?
- CharField
- TextField
- SlugField
- IntegerField
Answer: TextField. TextField holds unbounded text with no max_length.
What does null=True control?
- Whether forms accept an empty value
- Whether the field is indexed
- The default value
- Whether the database column may store SQL NULL
Answer: Whether the database column may store SQL NULL. null is a database-level option allowing SQL NULL.
What does blank=True control?
- Whether the database column may be NULL
- Whether the column is unique
- Whether forms accept an empty value
- The column type
Answer: Whether forms accept an empty value. blank is a validation-level option for forms.
For an optional text field, the Django convention is to use:
- null=True only
- unique=True
- db_index=True
- blank=True (store '' not NULL)
Answer: blank=True (store '' not NULL). Prefer blank=True alone so empties store as an empty string.
What does a ForeignKey field store?
- A scalar number
- A link to a row in another table
- Free text
- A boolean
Answer: A link to a row in another table. ForeignKey stores a link to one row in a related table.
Which ForeignKey argument is required to define delete behaviour?
- on_delete
- related_name
- to_field
- db_column
Answer: on_delete. ForeignKey requires on_delete, e.g. models.CASCADE.
With choices, what is stored in the database?
- The human-readable label
- An index number
- The first item of each tuple (the value)
- Both items
Answer: The first item of each tuple (the value). choices stores the first tuple item (the value), not the label.
What does db_index=True do?
- Makes the field required
- Creates a database index for faster lookups
- Sets a default value
- Allows NULL
Answer: Creates a database index for faster lookups. db_index=True builds an index to speed up filtering.
Which field needs max_digits and decimal_places?
- IntegerField
- BooleanField
- DecimalField
- EmailField
Answer: DecimalField. DecimalField requires max_digits and decimal_places.
Continue this course
- Previous: Migrations Deep Dive
- Next: Model Methods, __str__ & Meta — Add behavior to models with methods, properties, and the Meta class
- Quick reference: Django cheat sheet
Frequently asked questions
What is a Django model field?
A model field is a class attribute on a Django model that maps to a single column in the database table and declares that column's data type and rules. For example, CharField becomes a VARCHAR column, IntegerField becomes an INTEGER column, and DateTimeField becomes a timestamp column. Each field instance also carries options like max_length, null, and default that shape both the database schema and Django's form validation.
What is the difference between null and blank?
null is a database-level option: null=True lets the column store a real SQL NULL. blank is a validation-level option: blank=True lets Django forms accept an empty value without raising a validation error. They are independent. For string-based fields the Django convention is to leave null=False (store an empty string rather than NULL) but set blank=True if the field is optional in forms.
When should I use choices on a field?
Use choices when a field should only ever hold one value from a fixed, known set, such as a status that can be draft, published, or archived. You pass a list of two-tuples where the first item is the value stored in the database and the second is the human-readable label shown in forms and the admin. Django then validates that any saved value is one of the allowed choices and renders a dropdown automatically.
What does db_index do?
db_index=True tells Django to create a database index on that column, which makes lookups and filters on the field much faster at the cost of slightly slower writes and a little extra storage. Some fields add an index automatically: unique=True implies an index, and ForeignKey fields are indexed by default. Reach for db_index on columns you frequently filter or order by, like a slug or an email you look users up by.