Django Models: Define Your Schema
Learn to define your database schema with Django models — fields, relationships, and migrations, plus hands-on steps and troubleshooting.
Focus: Django models define database schema
Ever spent hours writing SQL by hand, only to watch your database schema drift from your application code? With Django, you define your entire database layout in Python — Django models define database schema once, and the framework handles the SQL, the tables, and even the migrations that keep everything in sync. This lesson walks you through the core concept and gives you a hands-on path to building your first model, from fields to foreign keys, so your data layer stays clean, consistent, and Pythonic.
The problem this lesson solves
When you're building a web app, your database is the backbone. But connecting raw SQL to Python code is messy: you write CREATE TABLE statements, map columns to Python variables, and then write your own query layer. Any change to your data structure means updating SQL, Python, and your queries — and one missed spot leads to runtime errors or silent data corruption.
Django's model system eliminates that whole class of problem. Instead of hand-written SQL, you declare your schema as a Python class. Django turns that class into a real database table, creates the exact SQL for your chosen database engine, and generates migration files that track every schema change. The result: your data layer is defined in one place, stays in sync with your code, and is far easier to maintain.
This lesson solves the specific pain of designing your schema: choosing the right field types, setting constraints, and connecting tables with relationships — all without touching SQL.
Core concept / mental model
Think of a Django model as a blueprint for a database table. Each model is a Python class that inherits from django.db.models.Model. Each field in that class maps to a column in the table, and each instance of the class (an object) becomes a row. When you create a model, Django doesn't just describe the table — it also gives you a powerful Object-Relational Mapper (ORM) that lets you query the database using Python instead of SQL.
Visualize it like this:
Python class Database table
------------------ ------------------
class Book(models.Model):
title = ... -> title column
author = ... -> author column
published = ... -> published column
Every Django model automatically gets an id primary key unless you specify otherwise. That id is what makes each row uniquely identifiable.
The real power comes from relationships: ForeignKey, OneToOneField, and ManyToManyField let you model how tables relate. A ForeignKey creates a many-to-one link (e.g., many books to one author). A ManyToManyField sets up a junction table automatically. These are the building blocks of any relational schema.
Another core idea: migrations. Whenever you change a model, you run Django's migration tooling to bring the actual database in line. Migrations are version-controlled, so every developer on your team gets the same schema evolution history.
How it works step by step
Alright, let's see how you go from a blank project to working tables — step by step.
1. Create a Django app
Models live in apps. Start a new app inside your project (or use an existing one).
python manage.py startapp library
This creates a library/ directory with a models.py file — that's where your models go.
2. Define your first model
Open models.py and write a Book model. Each field class tells Django what type of column to create and what constraints to enforce.
# library/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_year = models.IntegerField()
isbn = models.CharField(max_length=13, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
CharFieldstores text; you must give amax_length.IntegerFieldstores whole numbers.unique=Truemakes theisbncolumn a unique index — no duplicate ISBNs allowed.auto_now_add=Truesets the timestamp automatically on creation.
The __str__ method makes the object read nicely in the admin and in shells.
3. Register the app
Add 'library' to INSTALLED_APPS in your project's settings.py. This tells Django your app exists and can be migrated.
# settings.py
INSTALLED_APPS = [
# ...
'library',
]
4. Generate and apply migrations
This is where Django writes the actual SQL for you.
python manage.py makemigrations library
python manage.py migrate
makemigrations creates a migration file that describes the schema change. migrate applies it to your database. If you check your database, you'll see a library_book table with the columns you defined.
5. Add relationships
Real apps rarely have a single isolated table. Add an Author model and link books to authors with a foreign key.
# library/models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField(blank=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
published_year = models.IntegerField()
isbn = models.CharField(max_length=13, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
on_delete=models.CASCADEmeans when an author is deleted, their books go too.related_name='books'lets you accessauthor.books.all()from the author side.
Run makemigrations and migrate again — Django will create the foreign key column and the appropriate index.
Hands-on walkthrough
Let's put it together with a complete, runnable example. This builds a small Book and Author model, applies migrations, and shows how to insert and query data.
Setup (if you haven't already)
# Create a project and app
python -m venv venv
source venv/bin/activate
pip install django
python -m django startproject mysite
cd mysite
python manage.py startapp library
Add 'library' to INSTALLED_APPS, then write the models as above.
Full model example
# library/models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField(blank=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
published_year = models.IntegerField()
isbn = models.CharField(max_length=13, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Run migrations
python manage.py makemigrations library
python manage.py migrate
You should see output like:
Migrations for 'library':
library/migrations/0001_initial.py
- Create model Author
- Create model Book
Operations to perform:
Apply all migrations: admin, auth, contenttypes, library, sessions
Running migrations:
Applying library.0001_initial... OK
Play with the ORM
Jump into Django's interactive shell and create your first objects.
python manage.py shell
from library.models import Author, Book
# Create an author
author = Author.objects.create(name='J.K. Rowling', bio='Author of the Harry Potter series')
# Create books linked to that author
book1 = Book.objects.create(title='Harry Potter and the Philosopher Stone', author=author, published_year=1997, isbn='9780747532699')
book2 = Book.objects.create(title='Harry Potter and the Chamber of Secrets', author=author, published_year=1998, isbn='9780747538493')
# Query all books by this author
author.books.all() # => <QuerySet [<Book: Harry Potter and the Philosopher Stone>, <Book: Harry Potter and the Chamber of Secrets>]>
# Filter by title
Book.objects.filter(title__icontains='chamber')
# Count books
Book.objects.count() # => 2
You've just defined a database schema with Django models and used the ORM to interact with it — no SQL wrote by you!
Compare options / when to choose what
Django gives you a rich set of field types and relationship options. Here's how to pick what fits your data.
| Field type | Use when | Example |
|---|---|---|
CharField |
Short to medium text, always needs max_length |
Name, title, slug |
TextField |
Long form text, no practical length limit | Bio, article body |
IntegerField |
Whole numbers | Count, year, price (cents) |
DecimalField |
Exact decimal arithmetic (avoid floats for money) | Price, ratings |
BooleanField |
True/false flags | Is published, email verified |
DateField / DateTimeField |
Dates and timestamps. auto_now_add and auto_now handle creation/update times |
Published date, created at |
EmailField |
Email addresses with validation | Contact email |
FileField / ImageField |
Uploading files/images. Handles storage path and URL | Profile picture, PDF |
When it comes to relationships, use ForeignKey for most many-to-one cases. It's the right default when one object "owns" many of another. Use OneToOneField when you want a one-to-one link (like extending the built-in User model with a profile). Use ManyToManyField for cases like books and genres — a book has many genres and a genre has many books.
Pro tip: Always put
blank=TrueonCharField/TextFieldif the field can be empty in forms.blankis about form validation,nullis about the database. For text fields, preferblank=Truewithoutnull=Trueto avoid storing a string "NULL" and an empty string at the same time.
Don't forget the standard meta options: you can add class Meta to a model to set ordering, unique constraints, and table names. For example ordering = ['-published_year'] inside Meta makes queries order by year descending by default.
Troubleshooting & edge cases
Even with Django's hand-holding, things can go wrong. Here are the most common pitfalls and fixes.
1. "Table already exists" or migration conflicts
If another dev already applied a migration, you might hit conflicts. Fix: run python manage.py makemigrations --check to see if any model changes are missing. For conflicts, use python manage.py makemigrations --merge to combine migration branches.
2. Forgetting to register the app
You define models but manage.py makemigrations says "No changes detected." Cause: the app isn't in INSTALLED_APPS. Double-check settings.py.
3. on_delete errors with older models
If you create a ForeignKey and forget on_delete, Django raises an error (TypeError). Always specify it. CASCADE is the common choice, but consider PROTECT if you don't want related rows to be silently deleted.
4. max_length missing on CharField
Django will refuse to create the field and raise TypeError: CharField.__init__() missing 1 required positional argument: 'max_length'. Always provide a reasonable length.
5. NULL vs empty string
You made a CharField nullable with null=True, but now your code checks if value is None and it's actually an empty string. Avoid null=True on string fields unless you have a specific reason. Use blank=True for form validation instead.
6. Foreign key name conflicts
If you name a field author but also have a related model Author, be careful with related_name collisions. If two foreign keys point to the same model, you must set related_name to something unique (e.g., related_name='published_books').
What you learned & what's next
You've successfully dived into Django models defining your database schema. You can now: explain the core idea behind models — that a Python class maps to a database table; complete a practical exercise by defining Book and Author models, running migrations, and using the ORM to create and query data. You've seen how field types, constraints, and relationships give you a full toolkit to design any schema Django can handle.
Callout: The model layer is the foundation, but classes like
CharField,ForeignKey, and the ORM only shine when you integrate them with views, forms, and templates.
Up next in this track, you'll build on this foundation by learning Django Admin and QuerySets — how to manage your data through the admin interface and write advanced queries. From there, you'll tie models to views and templates to build dynamic pages that read and write your database.
Keep practicing: modify the Book model to add a genre field, run a migration, and see how easy it is to evolve your schema as your app grows.
Practice recap
Open your library/models.py and add a Genre model with a name field. Then add a ManyToManyField called genres to Book. Run makemigrations and migrate, then use the shell to create a few genres and attach them to a book. This will confirm you've mastered model relationships and migrations.
Common mistakes
- Forgetting to add your app to
INSTALLED_APPS— migrations just won't detect your models. - Omitting
on_deleteinForeignKey— Django raises a TypeError; always specify it (oftenCASCADE). - Using
null=TrueonCharFieldandTextField— leads to empty strings vs null confusion; useblank=Trueinstead. - Skipping
max_lengthonCharField— Django requires it and will throw an error. - Creating two foreign keys to the same model without
related_name— causes reverse accessor clashes.
Variations
- You can define models without apps by using Django's
AppConfigin the project, but it's not recommended — apps keep things organized. - Use
abstract = Truein a model'sMetato create abstract base classes that don't create tables, only share fields. - For legacy databases, you can use
python manage.py inspectdbto generate models from an existing database schema.
Real-world use cases
- Designing an e-commerce catalog with Product and Category models linked by a ManyToManyField.
- Building a social platform with UserProfile extending the built-in User via a OneToOneField.
- Creating a blog system where Post and Comment models use ForeignKey with CASCADE so deleting a post removes its comments.
Key takeaways
- A Django model is a Python class that maps to a database table; each field becomes a column.
- Migrations (makemigrations and migrate) keep your actual database schema in sync with your model definitions.
- ForeignKey, OneToOneField, and ManyToManyField let you model the three core types of relational data.
- Choosing the right field type (CharField vs TextField vs DecimalField) has real consequences for data integrity.
- Constraints like unique=True and choice field options give you data validation at the database level.
- Always specify on_delete for foreign keys and avoid null=True on string fields unless strictly needed.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.