Url naming and namespaces

A project's internal links or url references (e.g. <a href='/'>Home Page</a>) tend to be hard-coded, whether it's in view methods to redirect users to certain locations or in templates to provide adequate user navigation. Hard-coding links can present a serious maintenance problem as a project grows, because it leads to links that are difficult to detect and fix. 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 definitions in urls.py. Listing 2-14 shows how to name a project's home page, as well as how to reference this url from a view method or template.

Listing 2-14 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 home page</a>

The url definition in listing 2-14 uses the regular expression r'^$' which translates into / or the home page, also known as the root directory. Notice the name attribute with the homepage value. By assigning the url a name you can use this value 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 2-14 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 2-14 makes use of the Django {% url %} tag, which attempts to look up a url by its first argument -- in this case homepage -- and substitute it accordingly.

This same naming and substitution process is available for more complex url definitions, such as those with parameters. Listing 2-14 shows the process for a url with parameters.

Listing 2-14. Django url with arguments using name

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

# Definition in view method
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse
def method(request):
    ....
    return HttpResponsePermanentRedirect(reverse('drink', args=(drink.name,)))

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

The url definition in listing 2-14 uses a more complex regular expression with a parameter that translates into urls in the form /drinks/latte/ or /drinks/espresso/. In this case, the url is given the argument name drink_name.

Because the url uses a parameter, the syntax for the reverse method and {% url %} tag are slightly different. The reverse method requires the url parameters be provided as a tuple to the args variable and the {% url %} tag requires the url arguments be provided as a list of values. Notice in listing 2-14 the parameters can equally be variables or hard-coded values, so long as it matches the url argument regular expression type -- which in this case is non-digits.

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 parameters and for the {% url %} tag you pass it a list of values.

Caution Beware of invalid url definitions with reverse and {% url %}. Django 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 the regular expression -- the application won't 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 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 two or three index pages ? Or if you have two 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 described earlier to consolidate urls.

Listing 2-15 illustrates a series of url definitions that make use of the namespace attribute with include.

Listing 2-15 - 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+)/$',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 2-15 starts with a set of include definitions typical of a main Django urls.py file. Notice both definitions use 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 2-15, 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 2-16 shows an example of nested namespace attributes.

Listing 2-16. Django urls.py with nested namespace attribute

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

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 2-16 differs from listing 2-15 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 2-16 we use 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 2-16, you can see how nested namespaces are used with the reverse method and {% url %} tag using a : to separate namespaces.

In 99% of Django urls you can use the name and namespace parameters just as they been described. However, the namespace parameter takes on special meaning when you deploy multiple instances of the same Django app in the same project.

Since Django apps are self-contained units with url definitions, it raises an edge case even if Django apps use url namespaces. What happens if a Django app uses namespace X, but you want to deploy the app two or three times in the same project ? How do you reference urls in each app, given they're all written to use namespace X ? This is where the term instance namespace and the app_name attribute come into the picture.

Let's walk through a scenario that uses multiple instances of the same Django app to illustrate this edge case associated with url namespaces. 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 of 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 2-17.

Listing 2-17 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 2-17 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 2-17.

Both the reverse method and {% url %} tag examples in listing 2-17 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 an edge-case associated with internal app logic that must be included to support multiple instances of a Django app. Now let's take a look at both a view and template scenario that illustrates this scenario and how the app_name attribute solves this problem

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 2-18 illustrates how to leverage the app_name attribute to dynamically determine where to make a redirect.

Listing 2-18 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 in listing 2-18 of the banners app sets the app_name attribute before declaring the urlpatterns value. Next, notice the reverse method in listing 2-18 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, it's dynamic! 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 instance a user 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 shown in listing 2-17.

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 2-19.

Listing 2-19 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 2-19 points to banners_adverts:index. The resolution process for the banners_adverts:index is the same outlined in the previous method example that uses the reverse method.

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 for named urls that dynamically adapt to multiple instances of the same app. For this reason, it's not as widely used for url naming and can be generally foregone in most cases in favor of just using the namespace and name attributes.