In chapter 1 you learned about the core building blocks in Django, inclduing what are views, models and urls. In this chapter, you'll learn more about Django urls which are the entry point into a Django application workflow.You'll learn how to create complex url regular expressions, how to use url values in view methods & templates, how to structure & manage urls and how to name urls.
After urls, Django views represent the next step in almost all Django workflows, where views are charged with inspecting requests, executing business logic, querying a database and validating data, as well as generating responses. In this chapter, you'll learn how to create Django views with optional parameters, the structure of view requests & responses, how to use middleware with views and how to create class based views.
Url regular expressions
Regular expressions provide a powerful approach in all programming languages to determine patterns. However, with power also comes complexity, to the point there are entire books written on the topic of regular expressions[1].
Although most Django urls will never exceed a fraction of the complexity illustrated in many regular expression books, it's important that you understand some of the underlying behaviors and most common patterns of regular expressions in Django urls.
Precedence rule: Granular urls first, broad urls last
Django urls need to follow a certain order and syntax to work correctly. Broad url regular expressions should be declared last and only after more granular url regular expressions.
This is because Django url regular expression matching doesn't use short-circuiting behavior, like a nested conditional statement (e.g. if/elif/elif/elif/else) were as soon as one condition is met, the remaining options are ignored. In Django urls if there's more than one matching regular expression for an incoming url request, it will be the top-most one's action that gets triggered. Precedence for matching url regular expressions is given from top (i.e. first declared) to bottom (i.e. last declared).
You shouldn't underestimate how easy it can be to introduce two url regular expressions that match the same pattern, particularly if you've never done regular expressions since the syntax can be cryptic. Listing 2-1 illustrates the right way to declare Django urls, with more granular regular expressions toward the top and broad regular expressions toward the bottom.
Listing 2-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')), ]
Based on listing 2-1, lets walk
through what happens if Django receives a request for the url
/about/index/
. Initially Django matches the last
regular expression, which says 'match ^about/
'. Next,
Django continues upward inspecting the regular expressions and
reaches 'match ^about/index/'
which is an exact match
to the request url /about/index/
and therefore
triggers this action to send control to the index.html
template.
Now let's walk through a request
for the url /about/
. Initially Django matches the last
regular expression which says 'match ^about/
'. Next,
Django continues upward inspecting the regular expressions for a
potential match. Because no match is found -- since 'match
^about/index/'
is a more granular regular expression
-- Django triggers the first action to send control to the
about.html
template which was the only regular
expression match.
As you can see, listing 2-1 produces what can be said to be expected behavior. But now let's invert the order of the url regular expressions, as shown in listing 2-2, and break down why declaring more granular regular expressions toward the bottom is the wrong way to declare Django url regular expressions.
Listing 2-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')), ]
The issue in listing 2-2 comes
when a request is made for the url /about/index/
.
Initially Django matches the last regular expression, which says
'match ^about/index/
'. However, Django continues
inspecting the regular expressions and reaches 'match
^about/'
which is a broader match to the request url
/about/index/
, but nevertheless a match! Therefore
Django triggers this action and sends control to the
about.html
template, instead of what was likely
expected to be the index.html
template from the first
match.
Exact url patterns: Forgoing broad matching
In the past section, I intentionally used regular expressions that allowed broad url matching. In my experience, as a Django project grows you'll eventually face the need to use this type of url regular expressions -- but more on why this is so, shortly.
As it turns out, it's possible to use exact url regular expressions. Exact url regular expressions remove any ambiguity introduced by the order in which Django url regular expression are declared.
Lets rework the url regular expressions from listing 2-2 and make them exact regular expressions so their order doesn't matter. Listing 2-3 illustrates exact regular expressions on basis of those in listing 2-2.
Listing 2-3. Exact regular expressions, where url order doesn't matter.
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')), ]
Notice the regular expressions in
listing 2-3 end with the $
character. This is the
regular expression symbol for end of line, which means the regular
expression urls only match an exact pattern.
For example, if Django receives a
request for the url /about/index/
it will only match
the last regular expression in listing 2-3 which says 'match
^about/index/$'
. However, it won't match the higher-up
^/about/$
regular expression because this regular
expression says match about/
exactly with
nothing else after, since the $
indicates the end of
the pattern.
However, as useful as the
$
character is to make stricter url regular
expressions, it's important you analyze its behavior. If you plan
to use url Search Engine Optimization (SEO), A/B testing techniques
or simply want to allow multiple urls to run the same action,
stricter regular expression with $
eventually require
more work.
For example, if you start to use
urls like /about/index/
,
/about/email/
,/about/address/
and they
all use the same template or view for processing, exact regular
expressions just make the amount of urls you declare larger.
Similarly, if you use A/B testing or SEO where lengthier variations
of the same url are processed in the same way (e.g.
/about/landing/a/
, /about/landing/b/
,
/about/the+coffeehouse+in+san+diego/
) broad url
matching is much simper than declaring exact url patterns.
In the end, whether you opt to
use exact url regular expression ending in $
, I would
still recommend you maintain the practice of keeping finer grained
url regular at the top and broader ones at the bottom, as this
avoids the unexpected behaviors described in listing 2-2 when more
than one regular expression matches a url request.
Common url patterns
Although url regular expressions can have limitless variations -- making it next to impossible to describe each possibility -- I'll provide examples on some of the most common url patterns you're more likely to use. Table 2-1 shows individual regular expression characters for Django urls and table 2-2 shows a series of more concrete examples with url patterns.
Table 2-1. Regular expression syntax for Django urls: Symbol (Meaning)
^ (Start of url) | $ (End of url) | \ (Escape for interpreted values) | | (Or) |
+ (1 or more occurrences) | ? (0 or 1 occurrences) | {n} (n occurrences) | {n,m} (Between n and m occurrences) |
[] (Character grouping) | (?P (Capture occurrence that matches regexp ___ and assign it to name | . (Any character) | \d+ (One or more digits). Note escape, without escape matches 'd+' literally] |
\D+ (One or more non-digits).Note escape, without escape matches 'D+' literally] | [a-zA-Z0-9_]+ (One or more word characters, letter lower-upper case, number or underscore) | \w+ (One or more word characters, equivalent to [a-zA-Z0-9_] ) Note escape, without escape matches 'w+' literally] | [-@\w]+ (One or more word character, dash or at sign). Note no escape for \w since it's enclosed in brackets (i.e. a grouping) |
Table 2-2. Common Django url patterns and their regular expressions, with samples
Url regular expression | Description | Sample urls |
---|---|---|
url(r'^$',.....) | Empty string (Home page) | Matches:
http://127.0.0.1/ |
url(r'^stores/',.....) | Any trailing characters | Matches:
http://127.0.0.1/stores/ http://127.0.0.1/stores/long+string+with+12345 |
url(r'^about/contact/$',.....) | Exact, no trailing characters | Matches:
http://127.0.0.1/about/contact/Doesn't match: http://127.0.0.1/about/ |
url(r'^stores/\d+/',....) | Number | Matches:
http://127.0.0.1/stores/2/ http://127.0.0.1/stores/34/Doesn't match: http://127.0.0.1/stores/downtown/ |
url(r'^drinks/\D+/',.....) | Non-digits | Matches:
http://127.0.0.1/drinks/mocha/Doesn't match: http://127.0.0.1/drinks/324/ |
url(r'^drinks/mocha|espresso/',.....) | Word options, any trailing characters | Matches:
http://127.0.0.1/drinks/mocha/ http://127.0.0.1/drinks/mochaccino/ http://127.0.0.1/drinks/espresso/Doesn't match: http://127.0.0.1/drinks/soda/ |
url(r'^drinks/mocha$|espresso/$',.....) | Word options exact, no trailing characters | Matches:
http://127.0.0.1/drinks/mocha/Doesn't match: http://127.0.0.1/drinks/mochaccino/Matches: http://127.0.0.1/drinks/espresso/Doesn't match: http://127.0.0.1/drinks/espressomacchiato/ |
url(r'^stores/\w+/',.....) | Word characters (Any letter lower-upper case, number or underscore) | Matches:
http://127.0.0.1/stores/sandiego/ http://127.0.0.1/stores/LA/ http://127.0.0.1/stores/1/Doesn't match: http://127.0.0.1/san-diego/ |
url(r'^stores/[-\w]+/',.....) | Word characters or dash | Matches:
http://127.0.0.1/san-diego/ |
url(r'^state/[A-Z]{2}/',.....) | Two upper case letters | Matches:
http://127.0.0.1/CA/Doesn't match: http://127.0.0.1/Ca/ |
On certain urls -- those made by HTTP GET requests, common in HTML forms or REST services -- parameters are added to urls with
?
followed byparameter_name=parameter_value
separated by&
(e.g./drinks/mocha/?type=cold&size=large
). These set of values are known as query strings and Django ignores them for the purpose of url pattern matching.If you need to make use of these values as url parameters -- a topic explored in the next section -- you can access these values in Django view methods through the request reference. Another alternative is to change the url structure to accommodate regular expressions (e.g.
/drinks/mocha/cold/large/
instead of/drinks/mocha/?type=cold&size=large
.)