I have released a new Django podcast about learning Django. This new podcast, Django Riffs, is available at djangoriffs.com.

Episode 2 - Enter With URLs

On this episode, we discuss Django’s front door, URLs. We talk about what URLs are, how to build them in Django, and the functions Django provides to work with URLs.

Listen at djangoriffs.com.

What’s a URL?

A URL is a Uniform Resource Locator. It is the full address that goes into a browser.

Consider https://www.mattlayman.com/django-riffs/. Here are the parts:

  • Scheme, https://
  • Domain name:
    • Top Level Domain (TLD), com
    • Domain, mattlayman
    • Subdomain, www
    • Path or route, /django-riffs/

URLconf

Every route for Django to handle goes into a URL configuration, URLconf for short and in the documentation.

  • The main URLconf is defined by the ROOT_URLCONF settings.
  • Set to the project/urls.py file by the startproject command.

What is a URLconf? A Python module with a special list in it.

Open the file and find urlpatterns list at module level scope. This list is the rules that Django will follow to route user’s to your code.

  • Each type of route will have a path rule in the list.
  • Django will match from top to bottom.
  • When matching, hand off to your code that you’ll define in views.

Here’s an example URLconf.

# project/urls.py
from django.urls import path

from application import views

urlpatterns = [
    path("", views.home),
    path("about/", views.about),
]
  • Empty route matches /.
  • about/ does a literal match to example.com/about/.
  • Django URLs end with a slash as a design choice. This is controlled by the APPEND_SLASH setting.

path

path is the key function for defining individual routes. The first parameter is the route string. Routes are more than strings only. They can contain converters. A converter looks like:

    path("blog/<int:year>/", views.blog_by_year),

A converter’s job is to extract information from a URL. This would pull out an integer from mattlayman.com/blog/2020/ and provide a 2020 value to the view code.

Order matters! In the following code, the first ordering will never call blog_for_twenty_twenty because Django will stop as soon as it finds a match.

    path("blog/<int:year>/", views.blog_by_year),
    path("blog/2020/", views.blog_for_twenty_twenty),

# vs.

    path("blog/2020/", views.blog_for_twenty_twenty),
    path("blog/<int:year>/", views.blog_by_year),

View Basics

A view is code that takes an HttpRequest and returns an HttpResponse

# application/views.py
from django.http import HttpResponse

def blog_by_year(request, year):
    # ... some code to handle the year
    return HttpResponse('some response')

This view function pairs with the converter example above. The converter passes the extracted year to the view. Django did all the hard work of parsing the year for us!

re_path

More complicated URLs can be represented with re_path which gives the full power of Python’s regular expression engine. Check out URLs Lead The Way article to learn more.

Grouping

Django’s tool for grouping is the include function. Let’s think about a website devoted to some fantasy adventure.

# project/urls.py
from django.urls import path

from heroes import views as heroes_views

urlpatterns = [
    path("heroes/", heroes_views.index),
    path("heroes/<int:hero_id>/", heroes_views.hero_detail),
]

Project has to know the details of the views for heroes. That’s not cool. Here’s an alternate version that uses include.

# project/urls.py
from django.urls import include, path

urlpatterns = [
    path("heroes/", include("heroes.urls")),
]
# heroes/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path("", views.index),
    path("<int:hero_id>/", views.hero_detail),
]

The URL configurations function like a tree. Lower level configurations do not need to handle earlier parts of the route.

Naming

How can we reference URLs in code? We could hardcode a /heroes/ string. What if we change to /champions/? That could be a lot of editing.

Django lets us name URLs. This is a layer of indirection.

# heroes/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

To use this, we need a new tool, the reverse function.

>>> from django.urls import reverse
>>> reverse("index") == "/heroes/"
True

Namespacing

There’s a problem with index as the name of the URL. What if another view in a different app (like villains) also wanted to use the name index? There’s a conflict. Who wins? The name index becomes ambiguous.

How can we get around that? We create a namespace!

$ python
>>> import this
The Zen of Python, by Tim Peters

...
Namespaces are one honking great idea -- let's do more of those!

We could do a manual namespace by renaming to heroes_index. Plenty of Django developers do this!

But Django gives us another tool.

# heroes/urls.py
from django.urls import path

from . import views

app_name = "heroes"
urlpatterns = [
    path("", views.index, name="index"),
]

We added app_name to give the app a namespace. We need a small tweak to how we use reverse.

>>> from django.urls import reverse
>>> reverse("heroes:index") == "/heroes/"
True

Next Time

In the next episode, we’ll look at views, the different kinds of views, and tools to help build them.

You can follow the show on djangoriffs.com. Or follow me or the show on Twitter at @mblayman or @djangoriffs.

Please rate or review on iTunes, Spotify, or from wherever you listen to podcasts. Your rating will help others discover the podcast, and I would be very grateful.

Django Riffs is supported by listeners like you. If you can contribute financially to cover hosting and production costs, please check out my Patreon page to see how you can help out.