Models & Migrations
Reviewed & published by Brayan K
A Django model is a Python class that defines the structure of your data — each model maps to a single database table and each attribute maps to a column — and migrations are the version-controlled files Django generates to create and update those tables as your models change.
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 define your first model, learn the most common field types and their options, and master the makemigrations and migrate workflow that keeps your database in step with your code.
What You'll Learn in This Lesson
- How a model class maps to a database table
- The core field types and what each one stores
- Writing a readable __str__ method for your models
- Field options like null, blank, default, and choices
- Generating migrations with makemigrations
- Applying schema changes with migrate
1 Defining a Model Class
A model lives in your app's models.py file. You subclass models.Model and declare each column as a class attribute set to a field instance. Here is a Post model with several common field types:
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
views = models.IntegerField(default=0)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title- CharField — short text; always needs max_length.
- TextField — large, unlimited-length text like article bodies.
- IntegerField — whole numbers such as counts.
- BooleanField — a true/false flag.
- DateTimeField — a date and time value.
The __str__ method tells Django (and the admin) how to display a single object. Without it, objects print as something unhelpful like Post object (1).
# A Django model is just a Python class. Here we simulate one with a
# plain class so it runs anywhere — the real version subclasses models.Model.
class Post:
def __init__(self, title, body, views=0, published=False):
self.title = title # like a CharField
self.body = body # like a TextField
self.views = views # like an IntegerField
self.published = published # like a BooleanField
def __str__(self):
return self.title
# Create an instance, just like Post.objects.create() would give you
first = Post("Hello Django", "My very first post.", views=5, published=True)
print(str(first)) # uses __str__ -> "Hello Django"
print("Views:", first.views)
print("Published?", first.published)
# ✅ Expected output:
# Hello Django
# Views: 5
# Published? True2 Field Options
Every field accepts options that control how it behaves and what the database column allows. These are the ones you'll use constantly:
- max_length — required on CharField; the column's character limit.
- null — if True, the database column may store NULL.
- blank — if True, the field may be left empty in forms and the admin.
- default — a value used when none is supplied.
- auto_now_add — set the timestamp once, when the row is first created.
- choices — restrict the value to a fixed list of options.
from django.db import models
class Article(models.Model):
STATUS_CHOICES = [
("draft", "Draft"),
("review", "In Review"),
("live", "Published"),
]
headline = models.CharField(max_length=200)
summary = models.TextField(blank=True) # optional in forms
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.headline# choices restrict a field to a fixed set of values. Here we model that
# logic in plain Python to see how defaults and validation feel.
STATUS_CHOICES = [
("draft", "Draft"),
("review", "In Review"),
("live", "Published"),
]
valid_values = [value for value, label in STATUS_CHOICES]
def make_article(headline, status="draft"):
if status not in valid_values:
return f"Invalid status: {status}"
return f"{headline} [{status}]"
print(make_article("Big News", "live")) # allowed
print(make_article("Quiet Update")) # uses default "draft"
print(make_article("Oops", "archived")) # not in choices
# ✅ Expected output:
# Big News [live]
# Quiet Update [draft]
# Invalid status: archived3 makemigrations & migrate
Writing a model in models.py does not change the database by itself. You take two steps: makemigrations writes a migration file describing the change, and migrate applies it to the database.
# 1. Generate a migration file from your current models
python manage.py makemigrations
# -> Migrations for 'blog':
# blog/migrations/0001_initial.py
# + Create model Post
# 2. Apply the migration to the database
python manage.py migrate
# -> Applying blog.0001_initial... OK
# Optional: preview the SQL a migration will run
python manage.py sqlmigrate blog 0001A generated migration file is plain Python. It records the operations Django will perform:
# blog/migrations/0001_initial.py
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Post",
fields=[
("id", models.AutoField(primary_key=True)),
("title", models.CharField(max_length=200)),
("body", models.TextField()),
("published", models.BooleanField(default=False)),
],
),
]# A migration is an ordered list of operations applied in sequence.
# Here we simulate the two-step workflow so the idea sticks.
model_state = {"Post": ["id", "title", "body"]} # current models.py
def makemigrations(state):
# Pretend we detected a new field and recorded the operation
operation = "AddField(model=Post, name=published)"
print("Created migration: 0002_post_published.py")
return operation
def migrate(operation, applied):
print("Applying:", operation, "... OK")
applied.append(operation)
applied = []
op = makemigrations(model_state)
migrate(op, applied)
print("Applied migrations:", len(applied))
# ✅ Expected output:
# Created migration: 0002_post_published.py
# Applying: AddField(model=Post, name=published) ... OK
# Applied migrations: 1🎯 Your Turn: Complete the Field
A CharField always needs a max_length. Fill in the blank with the number 200 so the field definition is valid.
# 🎯 YOUR TURN: replace ___ with the max_length value 200
def char_field(max_length):
return f"title = models.CharField(max_length={max_length})"
length = ___
print(char_field(length))
# EXPECTED OUTPUT:
# title = models.CharField(max_length=200)Common Errors (And How to Fix Them)
❌ Your models have changes that are not yet reflected in a migration
You edited a model but forgot to capture the change.
✅ Fix: run python manage.py makemigrations then python manage.py migrate.
❌ CharFields must define a 'max_length' attribute
A CharField was declared without a length limit.
✅ Fix: add a length, e.g. models.CharField(max_length=100).
❌ You are trying to add a non-nullable field without a default
Adding a required field to a table that already has rows leaves them with no value.
✅ Fix: give the field a default= value, or make it null=True.
📋 Quick Reference
| Field Type | What It Stores |
|---|---|
| CharField | Short text with a required max_length |
| TextField | Large, unlimited-length text |
| IntegerField | Whole numbers |
| BooleanField | A true/false flag |
| DateTimeField | A date and time value |
🏆 Mini Challenge: Build a Product Model
Model a simple Product with a name, a price, and an in-stock flag, then print a readable summary using its __str__ method.
# 🏆 Mini Challenge: simulate a Product model.
# In real Django: name=CharField, price=IntegerField, in_stock=BooleanField.
class Product:
def __init__(self, name, price, in_stock=True):
self.name = name
self.price = price
self.in_stock = in_stock
def __str__(self):
status = "in stock" if self.in_stock else "sold out"
return f"{self.name} - {self.price} ({status})"
catalog = [
Product("Keyboard", 49, True),
Product("Monitor", 199, False),
]
for product in catalog:
print(product)
# EXPECTED:
# Keyboard - 49 (in stock)
# Monitor - 199 (sold out)❓ Frequently Asked Questions
Lesson complete — your data has a schema!
You can now define models with the core field types, tune their behaviour with options like null, blank, default, and choices, and keep your database in sync using makemigrations and migrate.
Practice quiz
What does a Django model map to in the database?
- A single table
- A single column
- A whole database
- A SQL query
Answer: A single table. Each model maps to one database table.
What base class does a model subclass?
- forms.Form
- django.db.models.Model
- models.Field
- object only
Answer: django.db.models.Model. Models subclass django.db.models.Model.
Which field type requires a max_length?
- TextField
- IntegerField
- CharField
- BooleanField
Answer: CharField. CharField always needs a max_length.
Which field stores a true/false flag?
- IntegerField
- BooleanField
- CharField
- DateTimeField
Answer: BooleanField. BooleanField stores a true/false value.
What does the __str__ method do on a model?
- Runs a query
- Saves the row
- Tells Django how to display a single object
- Creates the table
Answer: Tells Django how to display a single object. __str__ provides the readable representation of an instance.
What does makemigrations do?
- Applies changes to the database
- Deletes the database
- Starts the server
- Writes a migration file describing model changes
Answer: Writes a migration file describing model changes. makemigrations plans the change by writing a migration file.
What does migrate do?
- Applies migration files to the database
- Generates a migration file
- Edits models.py
- Prints SQL only
Answer: Applies migration files to the database. migrate runs the migrations against the database.
What does auto_now_add=True do on a DateTimeField?
- Updates the time on every save
- Sets the timestamp once when the row is created
- Allows NULL
- Makes the field unique
Answer: Sets the timestamp once when the row is created. auto_now_add sets the timestamp once at creation.
For an optional text field, which option does Django convention prefer?
- null=True
- unique=True
- default=None
- blank=True (store '' not NULL)
Answer: blank=True (store '' not NULL). Prefer blank=True so empties store as an empty string.
Should migration files be kept in version control?
- No
- Yes, they are the shared schema history
- Only locally
- Only on the server
Answer: Yes, they are the shared schema history. Migrations are version-controlled so everyone rebuilds the same schema.
Continue this course
- Previous: The Django Admin
- Next: The ORM: Queries — Create, read, update, and delete data with Django's QuerySet API
- Quick reference: Django cheat sheet › Project Setup
Frequently asked questions
What is a Django model?
A Django model is a Python class that subclasses django.db.models.Model and defines the shape of your data. Each model maps to one database table, and each class attribute you declare as a Field maps to a column in that table. Instead of writing SQL to create tables, you describe them in Python and Django translates your classes into the right database structure for you.
What are migrations and why do I need them?
Migrations are version-controlled files Django generates that record changes to your models — creating a table, adding a column, altering a field. You run 'makemigrations' to write a migration file describing the change, then 'migrate' to apply it to the database. They give you a repeatable, ordered history of your schema so every developer and every server ends up with the exact same database structure.
What is the difference between makemigrations and migrate?
'makemigrations' looks at your models, compares them to the last recorded state, and writes a new migration file describing what changed — but it does not touch the database. 'migrate' takes those migration files and actually runs the SQL against the database to bring it up to date. Think of makemigrations as 'plan the change' and migrate as 'apply the change'.
Do I need to run makemigrations every time I change a model?
Yes. Any time you add, remove, or alter a field — or add a whole new model — you must run makemigrations to capture the change, then migrate to apply it. If you change a model but forget makemigrations, Django will warn you that you have unapplied model changes and your database will be out of sync with your code.