Name Django urls for easier management and reverse matches

Problem

You want to avoid hard-coding URL links in view methods and templates to make it easier to update and add new URLs.

Solution

Use the name and namespace attributes on your project's URL definitions. Use the reverse method in view methods or the {% url %} tag in templates to reference project URLs. In addition, you can also use the app_name attribute to further classify URLs for multiple app instances.

How it works

Links or URL references tend to be hard-coded in a project's codebase. Whether it's in view methods to redirect users to certain locations or in templates to provide adequate user navigation. Hard-coding such links can present a serious maintenance problem leading to difficult to detect dead links once a project grows. Django offers a way to name URLs so it's easy to reference them in view methods and templates.

The most basic technique to name Django urls is to add the name attribute to url method definitions in urls.py. Listing 1 shows how to name a project's home page, as well as how to reference this URL from a view method or template.

Listing 1 - Django url using name

# Definition in urls.py 
url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage")

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('homepage'))

# Definition in template
<a href="{% url 'homepage' %}">Back to homa page</a>

The URL definition in listing 1 uses the regular expression r'^$' which translates into / or the root directory. Notice the name attribute with the homepage value. By assigning the URL a name you can effectively use this name as a reference in view methods and templates, which means any future changes made to the URL regular expression, automatically update all URL definitions in view methods and templates.

Next in listing 1 you can see a view method example that redirects control to reverse('homepage'). The Django reverse method attempts to look up a URL definition by the given name -- in this case homepage -- and substitutes it accordingly. Similarly, the link sample <a href="{% url 'homepage' %}">Back to home page</a> in listing 1 makes use of the Django {% url %} tag, which attempts to look up a URL by its first argument -- in this case homepage -- and substitutes it accordingly.

This same naming and substitution process is available for more complex URL definitions, such as those with arguments. Listing 2 shows the process for a URL with arguments.

Listing 2 - Django url with arguments using name

# Definition in urls.py 
url(r'^drinks/(?P<drink_type>\D+)/',TemplateView.as_view(template_name='drinks/index.html'),name="drink_type"),

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('drink_type', args=(drink.id,)))

# Definition in template
<a href="{% url 'drink_type' drink.id %}">Drink on sale</a>

<a href="{% url 'drink_type' 1 %}">Drink on sale</a>

The URL definition in listing 2 uses a more complex regular expression with an integer argument that translates into URLs in the form /drinks/1/ or /drinks/2/. In this case, the URL is given the argument name drink_type.

Because the URL uses an argument, the syntax for the reverse method and {% url %} tag are slightly different. The reverse method requires the URL arguments be provided as a tuple to the args variable, where as the {% url %} tag requires the URL arguments be provided as a list of values. Notice in listing 2 the arguments can equally be a variable or hard-coded value, so long as it matches the URL argument type -- which in this case must be an integer.

For URL definitions with more than one argument, the approach to using reverse and {% url %} is identical. For the reverse method you pass it a tuple with all the necessary arguments and for the {% url %} tag you pass it a list of values.

Note Beware of invalid URL definitions with reverse and {% url %}

Django will always check at start up that all reverse and {% url %} definitions are valid. This means that if you make an error in a reverse method or {% url %} tag definition -- like a typo in the URL name or the arguments types don't match (e.g. integer vs. string) -- the application will not start and throw an HTTP 500 internal error.

The error for this kind of situation is NoReverseMatch at....Reverse for 'urlname' with arguments '()' and keyword arguments '{}' not found. X pattern(s) tried. If you look at the error stack you'll be able to pinpoint where this is happening and correct it. Just be aware that this is a fatal error and is not isolated to the view or page where it happens, it will stop the entire application at start up.

Sometimes the use of the name attribute by itself is not sufficient to classify URLs. What happens if you have 2 or 3 homepages ? Or if you have 2 URLs that qualify as details, but one is for stores and the other for drinks ?

A crude approach would be to use composite names (e.g.drink_details,store_details). However, the use of composite names in this form can lead to difficult to remember naming conventions and sloppy hierarchies. A cleaner approach supported by Django is through the namespace attribute.

The namespace attribute allows a group of URLs to be identified with a unique qualifier. Because the namespace attribute is associated with a group of URLs, it's used in conjunction with the include method in URLs. If you're unfamiliar with the include method, see the previous recipe Consolidate and modularize Django URL definitions.

Listing 3 illustrates a series of URL definitions that make use of the namespace attribute with include.

Listing 3 - Django urls.py with namespace attribute

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^about/',include('coffeehouse.about.urls',namespace="about")),
    url(r'^stores/',include('coffeehouse.stores.urls',namespace="stores")),
]

# About urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^contact/$',views.contact,name="contact"),
]

# Stores urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^(?P<store_id>\d+)/$','coffeehouse.stores.views.detail',name="detail"),
) 

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('about:index'))

# Definition in template
<a href="{% url 'stores:index' %}">Back to stores index</a>

Listing 3 starts with a set of include definitions typical of a main Django urls.py file. Notice both include definitions have the namespace attribute. Next, you can see the urls.py files referenced in the main urls.py file that make use of the name attribute described in the past example. Notice both the about and stores urls.py files have a URL with name='index'.

To qualify a URL name with a namespace you use the syntax <namespace>:<name>. As you can see toward the bottom of listing 3, to reference the index in the about urls.py you use about:index and to reference the index in the stores urls.py file you use stores:index

The namespace attribute can also be nested to use the syntax <namespace1>:<namespace2>:<namespace3>:<name> to reference URLs. Listing 4 shows an example of nested namespace attributes.

Listing 4 - Django urls.py with nested namespace attribute

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^stores/',include('coffeehouse.stores.urls',namespace="stores")),
]

# Stores urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^(?P<store_id>\d+)/$',views.detail,name="detail"),
    url(r'^(?P<store_id>\d+)/about/',include('coffeehouse.about.urls',namespace="about")),
]

# About urls.py
from . import views

urlpatterns = [
    url(r'^$',views.index,name="index"),
    url(r'^contact/$',views.contact,name="contact"),
]

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('stores:about:index', args=(store.id,)))

# Definition in template
<a href="{% url 'stores:about:index' store.id %}">See about for {{store.name}}</a>

The URL structure in listing 4 differs from listing 3 in that it creates about URLs for each store (e.g./stores/1/about) instead of having a generic about URL (e.g./about). At the top of listing 4 we use the namespace="stores" to qualify all URLs in the stores urls.py file. Next, inside the stores urls.py file notice there's another include element with namespace="about" to qualify all URLs in the about urls.py. And finally inside the about urls.py file, there are URLs that just use the name attribute.

In the last part of listing 4 you can see how nested namespaces are used with the reverse method and {% url %} tag using a : to separate namespaces.

Under certain circumstances, using the namespace and name attributes may not be enough to distinguish between URLs. This can happen when you want to deploy multiple instances of the same Django app in the same project. A Django app in this case refers to the modular structure to divide a Django project's parts. If you've never heard of the Django app concept, I advise you to look over the recipe Set up content, understand Django urls, templates and apps.

In order to support URL naming for multiple instances of the same Django app, Django uses the app_name attribute. But what exactly does it mean to have multiple instances of the same Django app ? Let's say you develop a Django app called banners to display advertisements. The banners app is built in such a way that it has to run on different URLs (e.g./coffeebanners/,/teabanners/,/foodbanners/) to simplify the selection of banners. In essence, you require to run multiple instances of the banners app in the same project, each one on different URLs.

So what's the problem with using multiple app instances and URL naming ? It has to do with using named URLs that need to change dynamically based on the current app instance. This issue is easiest to understand with an example, so lets jump to the example in listing 5.

Note 99% of the time you can just use namespace and name to name URLs

Even when using multiple instances of a Django app in a project, you can generally make everything work by just using namespace with name -- this will become clearer shortly when you look at the following examples.

I would suggest you consider app_name only if you plan to distribute a Django app (e.g. https://www.djangopackages.com/categories/apps/) as it covers an edge-case for internal app logic used when an app is deployed with multiple instances -- this will also become clearer shortly when you look at the following examples.

Listing 5 - Django urls.py with multiple instances of the same app

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^coffeebanners/',include('coffeehouse.banners.urls',namespace="coffee-banners")),
    url(r'^teabanners/',include('coffeehouse.banners.urls',namespace="tea-banners")),
    url(r'^foodbanners/',include('coffeehouse.banners.urls',namespace="food-banners")),  
]

# Banners urls.py
from django.conf.urls import url
from . import views 

urlpatterns = [
    url(r'^$',views.index,name="index"),
]

# Definition in view method 
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('coffee-banners:index'))
    return HttpResponsePermanentRedirect(reverse('tea-banners:index'))
    return HttpResponsePermanentRedirect(reverse('food-banners:index'))

# Definition in template
<a href="{% url 'coffee-banners:index' %}">Coffee banners</a>
<a href="{% url 'tea-banners:index' %}">Tea banners</a>
<a href="{% url 'food-banners:index' %}">Food banners</a>

In listing 5 you can see we have three URLs that point to the same coffeehouse.banners.urls file and each has its own unique namespace. Next, lets take a look at the various reverse method and {% url %} tag examples in listing 5.

Both the reverse method and {% url %} tag examples in listing 5 resolve to the three different URL names using the <namespace>:<name> syntax. So you can effectively deploy multiple instances of the same Django app using just namespace and name.

However, by relying on just namespace and name the resolved URL names cannot adapt dynamically to the different app instances, which is most often an edge-case associated with internal app logic that must be included to support multiple instances of a Django app. Next, I'll describe both a view and template scenario that illustrates the purpose of the app_name attribute.

Suppose inside the banners app you want to redirect control to the app's main index URL (e.g. due to an exception). Now put on an app designer hat, how would you resolve this problem ? As an app designer you don't even know about the coffee-banners, tea-banners or food-banners namespaces, as these are deployment namespaces. How would you internally integrate a redirect in the app that adapts to multiple instances of the app being deployed ? This is the purpose of the app_name parameter.

Listing 6 illustrate how to leverage the app_name attribute to dynamically determine where to make a redirect.

Listing 6 - Django redirect that leverages app_name to determine URL

# Main urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^$',TemplateView.as_view(template_name='homepage.html'),name="homepage"),
    url(r'^coffeebanners/',include('coffeehouse.banners.urls',namespace="coffee-banners")),
    url(r'^teabanners/',include('coffeehouse.banners.urls',namespace="tea-banners")),
    url(r'^foodbanners/',include('coffeehouse.banners.urls',namespace="food-banners")),  
]

# Banners urls.py
from django.conf.urls import url
from . import views 

app_name = 'banners_adverts'
urlpatterns = [
    url(r'^$',views.index,name="index"),
]

# Logic inside Banners app
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse

def method(request):
    ....
    try:
       ...
    except:
       return HttpResponsePermanentRedirect(reverse('banners_adverts:index'))

Notice the urls.py file of the banners app has the app_name attribute before declaring the urlpatterns value. Next, notice the reverse method in listing 6 uses the banners_adverts:index value, where banners_adverts represents the app_name. This is an important convention, because Django relies on the same syntax to search for app_name or namespace matches.

So to what URL do you think banners_adverts:index resolves to ? It all depends on where the navigation takes place. If a user is navigating through the coffee-banners app instance (i.e. URL coffeebanners) then Django resolves banners_adverts:index to the coffee-banners instance index, if a user is navigating through the tea-banners app instance (i.e. URL teabanners) then Django resolves banners_adverts:index to the tea-banners instance index, and so on for any other number of instances. In case a user is navigating outside of a banners app instance (i.e. there is no app instance) then Django defaults to resolving banners_adverts:index to the last defined instance in urls.py which would be food-banners.

In this manner and based on where the request path is coming from (e.g if the user is on a path with /coffeebanners/ or /teabanners/) the reverse method resolves banners_adverts:index dynamically to one of the three URL app instances vs. hard-coding specific URL namespaces as illustrated in listing 5.

Now let's assume the banners app has an internal template with a link to the app's main index URL. Similarly, how would you generate this link in the template to take into account the possibility of multiple app instances ? Relying on the same app_name parameter solves this problem for the template link illustrated in listing 7.

Listing 7 - Django template link that leverages app_name to determine URL


# template banners/index.html
<a href="{% url 'banners_adverts:index' %}">{% url 'banners_adverts:index' %}</a>

Notice the {% url %} tag in listing 7 points to banners_adverts:index. The resolution process for the banners_adverts:index is the same outlined in the previous method example. If a user is navigating through the coffee-banners app instance (i.e. URL coffeebanners) then Django resolves banners_adverts:index to the coffee-banners instance index, if a user is navigating through the tea-banners app instance (i.e. URL teabanners) then Django resolves banners_adverts:index to the tea-banners instance index, and so on for any other number of instances. In case a user is navigating outside of a banners app instance (i.e. there is no app instance) then Django defaults to resolving banners_adverts:index to the last defined instance in urls.py which would be food-banners.

As you can see, the app_name attribute's purpose is to give Django app designers an internal mechanism by which to integrate logic that dynamically adapts to multiple instances of the same app. For this reason, it's not as widely used and can generally be foregone in most cases using the namespace and name attributes.