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

Python Django Tutorial: Full-Featured Web App Part 2 - Applications and Routes

Corey Schafer·
4 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

A Django project can contain multiple apps, and each app can be routed independently through URL configuration.

Briefing

Django’s routing model lets a single project host multiple independent apps—then stitches them together through URL configuration—so adding a blog becomes mostly a matter of wiring URL patterns to view functions. The practical payoff is immediate: once the blog app defines its own views and URL patterns, the main project can direct requests like `/blog/` and `/blog/about/` to the right code, without duplicating logic across the site.

The workflow starts by creating a new app inside an existing Django project. A project is the top-level container (with `manage.py` and project settings), while each app owns its own functionality and file structure. Using `python manage.py startapp blog` generates a `blog/` directory with the standard Django app layout. Even before writing templates or complex logic, the app can already serve pages by defining a view.

Inside `blog/views.py`, a `home(request)` view is added. It returns an `HttpResponse` containing a simple HTML `<h1>` message (“blog-home”), establishing the basic pattern: each route maps to a Python function that accepts a `request` argument and returns a response. A second view, `about(request)`, is later added the same way, returning “blog about” in an `<h1>` tag.

Next comes URL routing within the app. A new `blog/urls.py` file defines URL patterns using Django’s `path()` helper. The blog homepage is mapped with an empty string path (`''`) to `views.home`, and it’s given the name `blog-home` to avoid collisions with other apps that might also define a generic `home` route. An additional pattern maps `about/` to `views.about` and names it `blog-about`. The app-level `urls.py` mirrors the project-level approach but only concerns itself with routes *inside* the blog.

To connect the app to the overall site, the project’s `urls.py` imports `include` and adds a pattern that forwards the `/blog/` prefix to the blog app’s URLconf: `path('blog/', include('blog.urls'))`. Django’s `include()` mechanism “chops off” the matched prefix (`blog/`) and passes only the remaining path to `blog.urls`. That’s why navigating to `/blog/about/` results in the `about` pattern matching inside `blog/urls.py`, which then calls `views.about`.

Testing in the browser confirms the behavior: hitting the site root initially returns a 404 until routes exist; `/blog/` returns the “blog home” HTML; `/blog/about/` returns the “blog about” HTML. The tutorial also emphasizes why trailing slashes matter: patterns like `blog/` and `about/` trigger Django’s automatic redirects so users don’t end up on inconsistent URLs.

Finally, the routing setup can be adjusted so the blog becomes the site’s home page. By changing the project-level URL pattern to include the blog URLs at an empty path, the root URL (`/`) serves `blog-home`, and `/about/` serves `blog-about`. The core takeaway is that routing is centralized and composable: change one line in the project URLconf to move an entire app’s routes, while the app’s internal views and URL patterns remain untouched.

Cornell Notes

A Django project can host multiple apps, and each app can define its own views and URL patterns. Creating a `blog` app adds a place to write view functions like `home(request)` and `about(request)` that return `HttpResponse` objects. The app’s `blog/urls.py` maps paths such as `''` (homepage) and `about/` to those views, using names like `blog-home` and `blog-about` to avoid collisions. The project-level `urls.py` then uses `include()` to route requests with a prefix (e.g., `path('blog/', include('blog.urls'))`) into the app’s URLconf. Changing the prefix in the project URLconf can make the blog the site’s root without modifying the blog app itself.

Why does Django use both a project-level `urls.py` and an app-level `urls.py`?

The project-level `urls.py` decides which URL prefixes map to which app URL configurations. The app-level `urls.py` then handles the routes *within* that prefix. With `include()`, Django strips the matched prefix (like `blog/`) and forwards only the remaining path (like `about/`) to the included URLconf, where patterns such as `''` and `about/` are matched against the app’s view functions.

How does `include()` affect the path that the app-level URLconf receives?

When the project matches `path('blog/', include('blog.urls'))`, Django removes the `blog/` portion from the URL before passing control to `blog.urls`. So `/blog/about/` becomes `about/` inside `blog.urls`, and `/blog/` becomes an empty string inside `blog.urls`, which matches the `''` pattern mapped to `views.home`.

What’s the role of naming URL patterns like `name='blog-home'`?

URL names enable reverse lookups later (e.g., generating links without hardcoding paths). Using a specific name like `blog-home` instead of a generic `home` prevents collisions if another app also defines a `home` route. The tutorial highlights this as a practical reason for more explicit naming.

Why add trailing slashes to patterns such as `path('about/', ...)` and `path('blog/', ...)`?

Trailing slashes help Django redirect requests that omit them. If a user visits a version without the slash, Django can redirect to the canonical version with the slash, keeping behavior consistent and avoiding duplicate “almost the same” URLs that might confuse users or break relative linking.

How can the blog become the site’s root page without changing the blog app code?

By changing the project-level URL pattern so the blog URLs are included at an empty path. Instead of `path('blog/', include('blog.urls'))`, the project can include `blog.urls` at `path('', include('blog.urls'))`. Then the app’s `''` route serves the homepage at `/`, and `about/` becomes available at `/about/`.

Review Questions

  1. If a request goes to `/blog/about/`, list the exact sequence of URLconf checks from project-level to app-level and identify which view function runs.
  2. What would break (or change) if the blog homepage pattern in `blog/urls.py` were not `''` but something else? Explain the resulting URL you’d need to visit.
  3. How does adding `path('blog/', include('blog.urls'))` differ from adding `path('', include('blog.urls'))` in terms of the URLs users must type?

Key Points

  1. 1

    A Django project can contain multiple apps, and each app can be routed independently through URL configuration.

  2. 2

    Creating a `blog` app with `python manage.py startapp blog` sets up the app structure needed for views and URL patterns.

  3. 3

    View functions like `home(request)` and `about(request)` return `HttpResponse` objects and act as the endpoints for routes.

  4. 4

    App-level routing in `blog/urls.py` maps paths (e.g., `''` and `about/`) to view functions using `path()` and can assign unique names like `blog-home`.

  5. 5

    Project-level routing uses `include()` to forward a URL prefix (e.g., `blog/`) into the app’s URLconf.

  6. 6

    Django’s `include()` strips the matched prefix before matching the remaining path inside the included URL patterns.

  7. 7

    Changing only the project-level prefix can move an entire app’s routes (including making the blog the site root) without altering the app’s views or internal URL patterns.

Highlights

`include()` works like a router handoff: `/blog/about/` becomes `about/` inside `blog.urls` after Django strips the `blog/` prefix.
Using `name='blog-home'` avoids future route-name collisions when multiple apps define similar endpoints like `home`.
Trailing slashes trigger Django redirects, keeping canonical URLs consistent (e.g., `blog/` vs `blog`).
By including `blog.urls` at an empty path, the blog homepage can become the site’s root at `/` with no changes to `blog/views.py`.
Even with only `HttpResponse` and `<h1>` tags, the routing pipeline can be verified end-to-end in the browser.

Topics

  • Django Apps
  • URL Routing
  • Views and HttpResponse
  • include() Prefixes
  • Trailing Slashes