Models & the ORM
🗃️ Models & the ORM
QuerySet API.
Defining a Model
A model is a Python class whose attributes describe the table's columns — Django infers the SQL types, the column names, and generates the migration for you:
Field Types
CharField(short text, requires max_length), TextField (unlimited text), IntegerField, DecimalField, DateField, BooleanField — each maps to an appropriate SQL column type.
blank vs null
null=True allows NULL at the database level; blank=True allows an empty value in forms/validation — the two are independent and commonly both set together for optional fields.
__str__
Not required, but without it, the admin site and shell both show an unhelpful Book object (1) instead of the book's actual title.
Implicit id
Django adds an auto-incrementing primary key field automatically unless you define your own — no need to declare it yourself.
🔀 Migrations
Django's migrations are auto-generated by diffing your models against the last known schema state — you rarely hand-write the SQL yourself, which is a real difference from writing raw migration files by hand:
makemigrations
Compares your current models against the migration history and writes a new migration file describing the difference — it does not touch the database.
migrate
Actually applies pending migration files to the database — the step that runs the real CREATE TABLE/ALTER TABLE SQL.
🔍 The QuerySet API
Every model gets a default .objects manager — the entry point for every query, returning a QuerySet that reads much like a chained, English-ish sentence:
FastAPI + SQLAlchemy vs Django's QuerySet
FastAPI + SQLAlchemy Session
Django QuerySet
QuerySets Are Lazy
Writing Book.objects.filter(...) does not hit the database — a QuerySet only actually runs its query once it's evaluated: iterated over, sliced, or converted to a list/bool:
This is why chaining .filter().exclude().order_by() across several lines is cheap — Django combines everything into a single SQL query at the moment it's finally evaluated, rather than running a separate query per method call.
Automatic SQL Injection Protection
Every value passed into the QuerySet API is parameterized automatically — the same defense the SQL Injection course spent a full chapter teaching how to do by hand:
💻 Coding Challenges
Challenge 1: Define and Migrate a Model
Define an Author model with name (CharField), bio (TextField, optional), and birth_year (IntegerField, optional), add a proper __str__, then write the two commands you'd run to create and apply its migration.
Goal: Practice choosing appropriate field types and the makemigrations/migrate workflow.
Challenge 2: Write Five QuerySet Queries
Against the Book model from this chapter, write QuerySet expressions for: all in-stock books ordered by price ascending, books under $15, the single book with pk=3, all books excluding out-of-stock ones, and the count of all books.
Goal: Build fluency with filter, exclude, order_by, get, and count.
Challenge 3: CRUD in the Django Shell
Using python manage.py shell, write the Python statements to create a new Book, fetch it back with .get(), update its price and save it, then delete it — all through the ORM, no raw SQL.
Goal: Practice the full create/read/update/delete cycle through the ORM's object-oriented API.
.get() Raises When It Doesn't Return Exactly One RowBook.objects.get(title="Dune") looks like it should behave like a dictionary's .get() — returning None if nothing matches. It does not: zero matching rows raises Book.DoesNotExist, and more than one matching row raises Book.MultipleObjectsReturned. Use .get() only when you're certain exactly one row should match (typically by primary key), and reach for .filter(...).first() — which returns None gracefully instead of raising — whenever a missing row is a normal, expected outcome rather than a bug.
🎯 What's Next
A single model can now be created, queried, and migrated — the next chapter covers what happens when models need to reference each other: Model Relationships, ForeignKey and ManyToMany fields, and the N+1 query problem this course has run into before, seen here from the ORM's side.