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.