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

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

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? True

2 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:

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: archived

3 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 0001

A 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 TypeWhat It Stores
CharFieldShort text with a required max_length
TextFieldLarge, unlimited-length text
IntegerFieldWhole numbers
BooleanFieldA true/false flag
DateTimeFieldA 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

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.