In chapter 1 you learned about the core building blocks in Django, including 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 url paths using basic strings and 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 paths

In the Set up content, understand Django urls, templates and apps section in Chapter 1, you learned how Django projects use the urls.py file to define url paths in two parts: a basic string that matches a url request and an action to take when said string is matched. For example, specifically in listing 1-20 you learned how an empty string '' represents a site's root path or homepage that can be set to serve a static template, as well as how a string like 'admin/' can match all url requests prefixed with /admin/ and be processed by yet another url file with more specific strings.

In most circumstances, Django uses the django.urls.path method to achieve this url path matching/action mechanism, however, Django also offers the django.urls.re_path method. So what's the difference between the django.urls.path and django.urls.re_path methods ? The path method is designed to perform matches against exact strings, whereas the re_path method is designed to perform matches against patterned strings based on regular expressions.

Exact urls with path and patterned urls with re_path

There's little explanation required to understand the path method because for the most part it works with exact url paths. For example, if you need to perform an action on the /contact/ url path you use the path('contact/',...) syntax and if you need to perform an action on the /contact/email/ url path you use the path('contact/email/',...) syntax. The note below briefly describes the exceptions for the path method working with exact paths, exceptions which are explained in detail later in this chapter.

Note Saying path works with exact paths deserves some clarification for its edge cases. You'll often find the path method chained to other path methods, so it might not be obvious you're working with exact urls because url parts are spread out across multiple url files, this is the case for the Django admin url path described earlier (e.g. path('admin/', admin.site.urls), where admin.site.urls contains other path declarations), as well as other examples presented in the Url consolidation and modularization section later in this chapter.

Another variation that doesn't precisely work with exact urls are path strings with url parameters. For example, the path('stores/<int:store_id>/',...) syntax defines a url parameter named store_id that matches paths like /stores/1/, /stores/2/ or any other url that begins with /stores/ and is followed by an integer. Although loosely speaking urls in the form /stores/<any_integer>/ can be considered exact, semantics aside, because path strings with url parameters also require special view method handling, path strings with url parameters are described in the Url parameters, extra options & query strings section later in this chapter.

The main limitation of the path method is it requires defining exact url paths or url parameters that must be handled by view methods -- the latter described in the previous note. So what if you want to handle more complex url paths ? For example, dynamic url paths that don't require url parameters to forgo the extra handling in view methods ? Enter the re_path method and regular expressions.

Regular expressions provide a powerful approach in all programming languages to determine patterns, but 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 require a fraction of the complexity illustrated in many regular expression books, it's important to take a closer look at the most common regular expressions patterns used by Django urls, as well as the most important behaviors of the re_path method.

re_path behaviors: Precedence rule (Granular urls first, broad urls last) and exact url patterns (Forgoing broad matching)

The order in which url paths are declared with path statements is irrelevant because this method operates on the premise of exact matches. For example, the path('contact/',...) statement is unequivocally different than the path('contact/email/',...) statement, so there's no way for the former syntax to be confused with the latter syntax even if the path statements are made in different order. This however is not the case for the re_path method which operates with regular expressions.

By design, regular expressions use a very specific syntax to indicate whether to match specific or broad patterns, a behavior that can have unintended consequences if you use the re_path method and don't take care of declaring more granular url regular expressions first and broader url regular expressions last. Since Django url resolution triggers the action of the first matching url statement -- whether it's a path or re_path statement -- you can end up never reaching an intended action if broad url regular expressions are placed first.

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 worked with regular expressions which have cryptic syntax. Listing 2-1 illustrates the right way to declare re_path url regular expressions statements, with more granular regular expressions toward the top and broad regular expressions toward the bottom.

Listing 2-1. Correct precedence for re_path url regular expressions

from django.urls import re_path
from django.views.generic import TemplateView

urlpatterns = [
    re_path(r'^about/index',TemplateView.as_view(template_name='index.html')),
    re_path(r'^about/',TemplateView.as_view(template_name='about.html')),
]
Tip To learn more about strings prefixed with r'' (a.k.a. raw strings) which are common in Python regular expressions, see Appendix A 'Strings, unicode and other annoying text behaviors'.

Based on listing 2-1, lets walk through what happens if Django receives a request for the url /about/index. Initially Django reads the first regular expression which says match ^about/index, therefore this is an exact match for the /about/index url and therefore control is sent to the index.html template.

Now let's walk through a request for the /about/ url. Initially Django reads the first ^about/index regular expression and determines it's too specific for the /about/ url and continues to the next url regular expression statement. The second re_path regular expression says match ^about/, therefore this is an exact match for the /about/ request url and therefore control is sent to the about.html template.

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 re_path statements -- 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 re_path url regular expressions.

Listing 2-2. Wrong precedence for re_path url regular expressions

from django.urls import re_path
from django.views.generic import TemplateView

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

]

The issue in listing 2-2 comes when a request is made for the /about/index url. Initially Django reads the first re_path regular expression statement ^about which turns out to be a broad match for the requested /about/index url and therefore control is sent right away to the about.html template! Before it reaches the next and likely intended ^about/index regular expression. The thing is the ^about/ regular expression simply says 'match anything that begins with about/', therefore a request made for the /about/index url produces a match, so you must be very careful when using re_path statements to declare more granular url regular expressions towards the top and broader url regular expressions towards the bottom.

I'll admit I intentionally used regular expressions that allowed broad url matching to prove my point. But in my experience, as Django projects grow you'll eventually face the need to use this type of broad url regular expressions -- but more on why this is so, shortly. Another possibility to avoid falling into this broad matching regular expression pitfall -- besides being careful with regular expression precedence/order -- is to use exact url regular expressions to remove any ambiguity introduced by the order of re_path url regular expression statements.

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 TemplateView

from django.urls import path, re_path

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

# Equivalent path() method statements
# urlpatterns = [
#     path('about/',TemplateView.as_view(template_name='about.html')),
#     path('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 /about/index url it only matches the last regular expression in listing 2-3 which says 'match ^about/index/$. However, it doesn't match the first ^/about/$ regular expression because this regular expression says match about/ exactly with nothing else after, since the $ indicates the end of the pattern.

In addition, notice toward the bottom of listing 2-3 the urlpatterns definition with path statements, which is equivalent to the urlpatterns definition one before it that uses re_path statements. It turns out that by using exact url regular expressions with re_path statements, you obtain identical behavior to that of path statements which also operate on the premise of exact matches. The takeaway here is the path method offers a more simplified approach to define exact urls than the re_path method which offers a more general purpose approach capable of defining both exact and broad matching url patterns.

Why use django.urls.path when you have the flexibility of django.urls.re_path ?

django.urls.path was created for simplicity. Prior to Django 2.0, the only way to define urls paths was through regular expressions and the django.conf.urls.url method, something that created an unnecessary hurdle for newcomers. Therefore the existence of both django.urls.path and django.urls.re_path is rooted in Django's past, with the former designed to welcome newcomers and the latter designed to support more powerful url patterns & Django old-timers accustomed to defining url paths with regular expressions.

Feel free to use whatever url definition method suites your project needs or personal tastes better.

While you might be tempted to think it's always best to declare all re_path regular expressions with the $ character to make them exact url regular expressions and avoid any problems, it's important to take a look at some beneficial cases where broad url regular expressions are helpful. If you plan to run the same logic on different urls -- a technique that's common in Search Engine Optimization (SEO) and A/B testing -- strict regular expressions ending with $ used by re_path statements or path statements that by design are strict, can lead to a lot 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 $ with re_path statements or just use path statements, I would still recommend you maintain the practice of keeping finer grained url patterns at the top and broader ones at the bottom, as this avoids the unexpected behaviors described in listing 2-2.

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 for django.urls.re_path and their regular expressions, with samples

Url regular expressionDescriptionSample urls
re_path(r'^$',.....)Empty string (Home page)Matches:
http://127.0.0.1/
re_path(r'^stores/',.....)Any trailing charactersMatches:
http://127.0.0.1/stores/
http://127.0.0.1/stores/long+string+with+12345
re_path(r'^about/contact/$',.....)Exact, no trailing charactersMatches:
http://127.0.0.1/about/contact/
Doesn't match:
http://127.0.0.1/about/
re_path(r'^stores/\d+/',....)NumberMatches:
http://127.0.0.1/stores/2/
http://127.0.0.1/stores/34/
Doesn't match:
http://127.0.0.1/stores/downtown/
re_path(r'^drinks/\D+/',.....)Non-digitsMatches:
http://127.0.0.1/drinks/mocha/
Doesn't match:
http://127.0.0.1/drinks/324/
re_path(r'^drinks/mocha|espresso/',.....)Word options, any trailing charactersMatches:
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/
re_path(r'^drinks/mocha$|espresso/$',.....)Word options exact, no trailing charactersMatches:
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/
re_path(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/
re_path(r'^stores/[-\w]+/',.....)Word characters or dashMatches:
http://127.0.0.1/san-diego/
re_path(r'^state/[A-Z]{2}/',.....)Two upper case lettersMatches:
http://127.0.0.1/CA/
Doesn't match:
http://127.0.0.1/Ca/
Django urls don't inspect url query strings

On certain urls -- those made by HTTP GET requests, common in HTML forms or REST services -- parameters are added to urls with ? followed by parameter_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 with the re_path method or basic string matching with the path method (e.g. /drinks/mocha/cold/large/ instead of /drinks/mocha/?type=cold&size=large.)

  1. http://www.apress.com/la/book/9781590594414