Django

Models and migrations — the basics

How a Python class becomes a database table, and why Django makes you generate a migration file instead of just changing the schema directly.

Beginner

3 min read

A model is a Python class that describes a database table

from django.db import models
 
class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    published_at = models.DateTimeField(auto_now_add=True)
    is_featured = models.BooleanField(default=False)

Each class attribute becomes a column in the corresponding database table — title becomes a text column limited to 200 characters, body becomes an unbounded text column, and so on. Every model automatically inherits from models.Model, which is what gives it the database-talking behavior (saving, querying, deleting) covered in the next lessons. Django also adds an automatic id column (an auto-incrementing primary key) to every model unless you explicitly define your own.

The common field types, and what each one actually stores

models.CharField(max_length=200)   # short text — max_length is required
models.TextField()                  # long text, no length limit
models.IntegerField()               # whole numbers
models.FloatField()                 # decimal numbers
models.BooleanField(default=False)  # True/False
models.DateTimeField(auto_now_add=True)  # a timestamp, set automatically on creation
models.ForeignKey("Author", on_delete=models.CASCADE)  # a link to another model

CharField requiring max_length (while TextField doesn't) reflects a real database-level distinction, not an arbitrary Django rule — many databases store short bounded text differently from unbounded text internally. ForeignKey is how one model relates to another (an Article belonging to an Author) — on_delete=models.CASCADE specifically means "if the related Author is deleted, delete this Article too," one of several defined behaviors for what should happen to dependent rows.

Migrations: how a model change actually reaches the database

Editing a model class doesn't touch the database at all by itself — Python code and the database schema are two separate things that have to be kept in sync deliberately:

python manage.py makemigrations   # look at model changes, generate a migration file
python manage.py migrate          # actually apply that migration to the database

makemigrations compares your current models against the last-known state and writes a new file in migrations/ describing exactly what changed (add this column, remove that one, alter this field's type). migrate then runs that file's instructions against the actual database. Splitting it into these two steps — generate, then apply — means the exact schema change is captured as a reviewable, version-controlled file before it ever touches real data, rather than the database schema silently drifting to match whatever the models currently say.

Why a generated migration file, instead of just syncing automatically

A migration file is committed to git like any other code change — it can be code-reviewed, it runs identically across every developer's machine and every environment (dev, staging, production), and it can be rolled back with migrate app_name previous_migration_name if something's wrong. If Django just auto-synced the database to match the models on every change, there'd be no record of how the schema evolved over time, no way to review a schema change before it hits production, and no clean way to undo one specific change.

Using a model: creating, reading, updating, deleting

Article.objects.create(title="Hello", body="First post")   # create
 
Article.objects.all()                        # read — every article
Article.objects.get(id=1)                     # read — exactly one, by id
Article.objects.filter(is_featured=True)      # read — matching some condition
 
article = Article.objects.get(id=1)
article.title = "Updated title"
article.save()                                 # update
 
article.delete()                                # delete

This is CRUD (Create, Read, Update, Delete) — the four basic operations almost any data-backed application needs, and Article.objects (Django calls this the manager) is the entry point for all of them. .filter() in particular is the one worth internalizing early: it doesn't fetch anything from the database immediately — it builds up a description of a query, only actually run once you iterate over it or otherwise force it to produce results, a behavior covered in detail in the "QuerySets are lazy" lesson.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does each class attribute on a Django model class become?

2. Why does CharField require max_length while TextField doesn't?

3. What actually happens when you edit a Django model class, before running any commands?

4. What is Article.objects used for?