Get AI summaries of any video or article — Sign up free
Python Django Tutorial: Full-Featured Web App Part 5 - Database and Migrations thumbnail

Python Django Tutorial: Full-Featured Web App Part 5 - Database and Migrations

Corey Schafer·
5 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Define a `Post` model with `title`, `content`, `date_posted`, and an `author` foreign key to `User` to replace dummy data with real database-backed posts.

Briefing

Django’s ORM turns database design into Python models, letting developers create real blog posts with zero hand-written SQL—then query, format, and manage that data through both the app and the admin site. The core move is defining a `Post` model with fields for `title`, `content`, `date_posted`, and an `author` relationship to Django’s built-in `User`, then using migrations to materialize those models as database tables.

The `Post` model is built in `models.py` by inheriting from `models.Model`. The tutorial defines `title` as a `CharField` capped at 100 characters, `content` as a `TextField` for longer text, and `date_posted` as a `DateTimeField` whose default value comes from `timezone.now` (passed as a function, not called immediately). For authorship, it adds an `author` field using `models.ForeignKey` pointing to the `User` model, with `on_delete=models.CASCADE` so deleting a user automatically deletes their posts. This establishes a one-to-many structure: one user can write many posts, while each post has exactly one author.

Once the model exists, the database changes happen through Django migrations. Running `python manage.py makemigrations` generates a migration file (e.g., `0001_initial.py`) describing the new schema. The tutorial then demonstrates how to inspect the exact SQL Django will run using `python manage.py sqlmigrate blog 0001`, where the output includes a `CREATE TABLE` for `blog_post` and column definitions that match the model fields (including an auto-incrementing primary key). Finally, `python manage.py migrate` applies the migration to the actual database.

With the table created, the Django shell (`python manage.py shell`) becomes a live playground for ORM queries. It shows how to list users (`User.objects.all()`), filter them (`filter(username=...)`), and fetch single records (`first()`, `get(id=...)`). It then creates `Post` objects tied to a user, highlights the need to call `.save()` when creating objects directly, and verifies that `date_posted` auto-populates from the model default. Adding a `__str__` method returning `self.title` makes query results readable in the shell.

The tutorial also demonstrates reverse relationships: from a user instance, `user.post_set.all()` retrieves all posts authored by that user. It even creates a post via `user.post_set.create(...)`, which automatically assigns the author and saves to the database without an explicit `.save()`.

To replace earlier dummy data, `blog/views.py` is updated to pull real posts from `Post.objects.all()` and pass them into templates. Because database timestamps look awkward in raw form, the `home.html` template formats `date_posted` using Django’s `date` filter with format codes (full month, day, and year). The final step registers the `Post` model in `blog/admin.py` so posts appear in the admin panel, where titles, content, and authors can be edited through a GUI.

Overall, the workflow ties together schema definition (models), safe database evolution (migrations), data access (ORM queries), presentation (template formatting), and management (admin registration) into one coherent system for a full-featured blog app.

Cornell Notes

The tutorial builds a real blog database by defining a `Post` model in Django and letting migrations create the corresponding SQL table. The model includes `title` (`CharField` with `max_length=100`), `content` (`TextField`), `date_posted` (`DateTimeField` defaulting to `timezone.now`), and an `author` foreign key to Django’s built-in `User` with `on_delete=models.CASCADE`. After running `makemigrations`, inspecting generated SQL with `sqlmigrate`, and applying changes with `migrate`, the ORM is used to create and query posts in the Django shell. The app then replaces dummy post data in `views.py` with `Post.objects.all()`, formats timestamps in the template using the `date` filter, and registers `Post` in `admin.py` so posts can be managed through the admin interface.

Why does the `Post` model use `timezone.now` as the default for `date_posted`, and why is it passed without parentheses?

`date_posted` is a `DateTimeField` meant to capture when a post is created. Using `default=timezone.now` ensures each new `Post` instance gets the current time at creation. The tutorial stresses not to write `timezone.now()` because that would execute immediately when the server loads, rather than at object creation time. It also contrasts this with `auto_now_add=True`, noting that `auto_now_add` prevents later updates to the stored creation timestamp, while `default=timezone.now` keeps flexibility.

What does `on_delete=models.CASCADE` do for the `author` foreign key?

The `author` field links each post to a `User`. With `on_delete=models.CASCADE`, deleting a user triggers deletion of all posts authored by that user. This matches the tutorial’s app logic: posts shouldn’t remain orphaned if their author is removed. The relationship is one-way in the sense that deleting a post does not delete the user.

How can developers see the exact SQL Django will generate before applying migrations?

After creating migrations with `python manage.py makemigrations`, the tutorial uses `python manage.py sqlmigrate blog 0001` (app name plus migration number) to print the SQL Django plans to run. The output includes a `CREATE TABLE` statement for `blog_post`, with columns corresponding to the model fields—like `title` becoming a `varchar(100)` and an auto-incrementing primary key even though it wasn’t explicitly defined in the model.

What’s the difference between creating a `Post` directly and creating one via `user.post_set.create(...)`?

When creating a `Post` directly (e.g., `post_one = Post(title=..., content=..., author=user)`), the tutorial shows that the object must be persisted with `post_one.save()`. When creating through the reverse relation (`user.post_set.create(...)`), Django automatically sets the author based on the relationship and saves the new post immediately, so no explicit `.save()` call is needed.

How does the tutorial format `date_posted` so it looks like a normal date in templates?

Raw `date_posted` values display with time and an odd format. In `home.html`, the tutorial applies Django’s `date` template filter: `{{ post.date_posted|date:

How does the admin interface gain the ability to edit posts?

The `Post` model must be registered in `blog/admin.py`. The tutorial imports `post` from `.models` and calls `admin.site.register(post)`. After reloading the admin page, a new “Posts” section appears, allowing titles, content, and the `author` dropdown to be edited and saved through the GUI.

Review Questions

  1. What fields and relationship does the `Post` model define, and how does `on_delete=models.CASCADE` affect data integrity?
  2. Walk through the migration workflow used here: `makemigrations`, `sqlmigrate`, and `migrate`. What does each step accomplish?
  3. In the Django shell, how do you retrieve all posts written by a specific user, and what reverse relationship name is used?

Key Points

  1. 1

    Define a `Post` model with `title`, `content`, `date_posted`, and an `author` foreign key to `User` to replace dummy data with real database-backed posts.

  2. 2

    Use `default=timezone.now` (without parentheses) for `date_posted` so each post gets a creation timestamp at the moment it’s created.

  3. 3

    Run `python manage.py makemigrations` to generate schema changes, inspect them with `python manage.py sqlmigrate blog 0001`, and apply them with `python manage.py migrate`.

  4. 4

    Use the Django ORM in `python manage.py shell` to create and query objects; remember to call `.save()` when creating instances directly.

  5. 5

    Add a `__str__` method (e.g., returning `self.title`) so posts display clearly in query results and admin listings.

  6. 6

    Replace dummy post dictionaries in `views.py` with `Post.objects.all()` and format `date_posted` in templates using the `date` filter.

  7. 7

    Register the `Post` model in `blog/admin.py` so posts become editable through the admin panel, including changing the author via a dropdown.

Highlights

A `Post` model plus migrations is enough to generate a real `blog_post` table automatically, including an auto-incrementing primary key and correctly typed columns.
`python manage.py sqlmigrate blog 0001` reveals the exact SQL Django will run, making migrations transparent and debuggable.
Reverse lookups like `user.post_set.all()` let code fetch all posts for a user without manually filtering `Post` by `author`.
Template-level date formatting fixes awkward timestamp output by applying Django’s `date` filter with format codes for month, day, and year.
Registering `Post` in `admin.py` instantly adds a GUI for creating, updating, and reassigning post authors.

Topics

  • Django Models
  • Database Migrations
  • Django ORM Queries
  • Template Date Formatting
  • Django Admin Registration