Regular expressions for Django Urls

Precedence rule

Django urls need to follow a certain order to work correctly. Broad URL match regular expressions need to be declared last and only after potentially conflicting more granular URL regular expressions. Listing 1 illustrates the right way to declare urls:

Listing 1 - Correct precedence for Django url regular expressions

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/index/',TemplateView.as_view(template_name='index.html')),
    url(r'^about/',TemplateView.as_view(template_name='about.html')),
]

Django attempts to match a requesting url on all declared regular expression, from bottom (i.e. last declared) to top (i.e. first declared). If Django receives a request for the url /about/index/ it initially matches the last regular expression which says 'match about/'.

Next, Django continues upward inspecting the regular expressions and reaches /about/index/ which is an exact match to the request url /about/index/ and therefore sends control to the index.html template.

If Django receives a request for the url /about/ it initially matches the last regular expression which says 'match about/' and continues inspecting the regular expressions upwards for a potential match. Because no match is found -- since about/index/ is a more granular regular expression -- Django sends control to the about.html template which was the only regular expression match.

The wrong way to declare Django urls is to place the broader regular expressions first, as illustrated in listing 2.

Listing 2 - Wrong precedence for Django url regular expressions

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/',TemplateView.as_view(template_name='about.html')),
    url(r'^about/index/',TemplateView.as_view(template_name='index.html')),
]

You could make the url regular expressions in listing 2 work as the ones in listing 1 (i.e. as expected), but you would need to make the regular expressions more strict. Listing 3 illustrates this process.

Listing 3 - Strict regular expressions with working precedence for Django url regular expressions

from django.views.generic import TemplateVieww

urlpatterns = [
    url(r'^about/$',TemplateView.as_view(template_name='about.html')),
    url(r'^about/index/$',TemplateView.as_view(template_name='index.html')),
]

In listing 3, all the regular expressions end with the $ character, which is the regular expression character for end of line. This means the regular expression urls only match exact patterns.

For example, if Django receives a request for the url /about/index/ it will only match the last regular expression in listing 3 which says 'match about/index'. However, it won't match the higher-up /about/$ statement because this regular expression says 'match about/ and nothing else to the end'.

As useful as the $ character to make stricter regular expressions looks, be sure to also analyze its drawbacks and understand if this the behavior you're looking for. For example, if you plan to use url SEO (Search Engine Optimization) techniques or simply allow broader url patterns to match the same url, making stricter regular expression with $ won't help. Requests for urls like /about/contact/ or /about/seo+words+on+url/ won't match any regular expression in listing 3, but they would work on the other two listings.

In the end, whether you opt to use stricter url regular expression that end with $, I would still recommend you attempt to keep finer grained url regular at the top and broader ones at the bottom, as it avoids strange matching behaviors (i.e. a url with a regular expression is never triggered as illustrated in listing 2).

Regular expression examples

The following is a list of regular expression for Django Urls, based on an application running on http://localhost/

Url regular expressionExample urls
url(r'^$',.....)
(Empty string)
Matches (Home page): http://localhost/
url(r'^stores/',.....)
(Word, any trailing characters)
Matches: http://localhost/stores/
Matches: http://localhost/stores/long+string+w+anything+12345
url(r'^stores/\d+',.....)
(Word and number)
Matches: http://localhost/stores/2/
Matches: http://localhost/stores/34/
Doesn't match: http://localhost/stores/downtown
url(r'^about/contact$',.....)
(Word, exact no trailing characters)
Matches: http://localhost/about/contact
Doesn't match: http://localhost/about
url(r'^drinks/\D+',.....)
(Non-digits)
Matches: http://localhost/drinks/mocha
Doesn't match: http://localhost/drinks/324
url(r'^drinks/mocha|espresso',.....)
(Word options, any trailing characters)
Matches: http://localhost/drinks/mocha
Matches: http://localhost/drinks/mochaccino
Matches: http://localhost/drinks/espresso
Doesn't match: http://localhost/drinks/soda
url(r'^drinks/mocha$|espresso$',.....)
(Word options, exact no trailing characters)
Matches: http://localhost/drinks/mocha
Doesn't match: http://localhost/drinks/mochaccino
Matches: http://localhost/drinks/espresso
Doesn't match: http://localhost/drinks/espressomacchiato