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 and regular expressions

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 projects use the django.urls.path method to achieve 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 former is used to match simple url patterns, while the latter is used to match more complex url patterns. In addition to this difference, there's also a historical Django perspective for why there are two ways to declare url patterns. 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 made creating url patterns unnecessarily complex. Therefore, the creation of django.urls.path was intended to simplify url path creation via simple strings, whereas django.urls.re_path became a newer equivalent of the older django.conf.urls.url method to create more sophisticated url patterns with regular expressions.

django.urls.path behaviors

As already mentioned, the django.urls.path method is likely to be your go-to choice for defining Django urls, however, there are certain behaviors you need to be aware of when using the django.urls.path method, because although for the most part it works matching simple strings in url paths, there are certain edge cases where its behavior isn't as clear cut. Let's first explore two clear cut cases for exact matches with django.urls.path. For example, if you need to perform an action on the /contact/ url path can you use the path('contact/',...) syntax, whereas if you need to perform an action on the /contact/email/ url path you can use the path('contact/email/',...) syntax.

A django.urls.path variation that doesn't precisely work with exact urls are django.urls.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. Because urls with url parameters can have variable values depending on the url parameter type, these url definitions have a dynamic nature that can match various url values. Because the use of url parameters also requires special view method handling, django.urls.path strings with url parameters are described in the Url parameters, extra options & query strings section later in this chapter.

Another variation you'll often find the django.urls.path method used for is when it's chained to other django.urls.path methods, so it might not be obvious you're working with exact urls since url parts are spread out across multiple url files. You already saw one particular case for the Django admin site, where the url statement path('admin/', admin.site.urls) points toward admin.site.urls, which is another url file with more django.urls.path -- or django.urls.re_path -- declarations. So path('admin/', admin.site.urls) represents the initial part of the url path starting with /admin/ and the other parts of the url path are declared in admin.site.urls (e.g. as path('login/',...) to match the full path /admin/login; as path('logout/',...) to match the full path /admin/logout).

The use of chained django.urls.path statements can also surface an edge case related to url precedence, that was also mentioned earlier in listing 1-28. The last url declaration included in admin.site.urls uses the statement admin/(?P<url>.*)$ -- this is a url pattern for the django.urls.re_path method whose syntax will be described shortly -- where the .* syntax means match anything, therefore this declaration is a catch-all for any /admin url pattern that isn't declared before it.

Although catch-all url patterns are helpful for certain cases like SEO (Search Engine Optimization), they can also give way to url declarations that are never reached. In the case of path('admin/', admin.site.urls), because the last statement in admin.site.urls is a catch-all url, it means any statement below path('admin/', admin.site.urls) containing an admin prefix will never be reached, because the catch-all in admin.site.urls is invoked before. That's the reason why the path('admin/doc/', include('django.contrib.admindocs.urls')) statement in listing 1-28 must be declared prior to path('admin/', admin.site.urls), otherwise a call to the url /admin/doc would always be handled by the catch-all in admin.site.urls.

The concept of chained django.urls.path and django.urls.re_path url statements is explored further in a dedicated section Url consolidation and modularization later in this chapter.

django.urls.re_path behaviors

A few lines above, you saw how the django.urls.re_path method uses a slightly different syntax in the form admin/(?P<url>.*)$, which represents 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 django.urls.re_path method.

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 django.urls.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 django.urls.path or django.urls.re_path statement -- you can end up never reaching an intended action if broad url regular expressions are placed first.

  1. https://www.regular-expressions.info/