Url parameters, extra options & query strings

You just learned how to use the django.urls.path and django.urls.re_path methods to create urls for your Django applications. However, if you look back at the examples you'll notice all the information provided on the urls is discarded.

Sometimes it's helpful or even necessary to pass url information to the processing construct as a parameter. For example, if you have several urls like /drinks/mocha/, /drinks/espresso/ and /drinks/latte/, the last part of the url represents a drink name. Therefore it can be helpful or necessary to relay this url information to the processing template to display it or use it in some other way in a view method (e.g. query a database).

To relay this information a Django url path must treat this information as a parameter. Url parameters are declared differently for both django.urls.path and django.urls.re_path statements, with the former using Django path converters and the latter using standard Python regular expression named groups.

Url parameters with Django path converters and Python regular expression named groups

Path converters are a special Django construct specifically designed to work with django.urls.path statements, whereas named groups are a standard Python regular expression technique[2] employed in django.urls.re_path statements which use regular expressions, as described earlier and in table 2-1. Regular expression syntax for Django urls: Symbol (Meaning).

Url parameters can capture any part of a url, whether it's a string, a number or a special set of characters that has to be passed to a template or view method. Listing 2-4 illustrates two sets of django.urls.path and django.urls.re_path statements showing how to capture strings and numbers as url parameters.

Listing 2-4. Django url parameters to capture strings and numbers with django.urls.path and django.urls.re_path

from django.urls import path, re_path

# Match string after drinks/ prefix
path('drinks/<str:drink_name>/',...),
# Match one or more characters (non-digit regular expression) after drinks/ prefix
re_path(r'^drinks/(?P<drink_name>\D+)/',...),


# Match integer after stores/ prefix
path('stores/<int:store_id>/',...),
# Match one or more digits (digit regular expression) after stores/ prefix
re_path(r'^stores/(?P<store_id>\d+)/',...),

The first line in listing 2-4 is a django.urls.path statement to match a url prefixed with drinks/ followed by a string. The <str:drink_name> syntax represents a path converter, where str is the path converter type and drink_name is the url parameter name given to the matching value (e.g. a request for /drinks/mocha/ means drink_name=mocha). The second line is an equivalent django.urls.re_path statement that uses a regular expression named group (?P<drink_name>\D+) to perform that same match as the first line. In this case, the ?P<> syntax tells Django to treat this part of the django.urls.re_path regular expression as a named group and assign its value to a parameter named drink_name declared between <>. The final piece \D+ is a regular expression that represents one or more non-digit characters -- technically a string -- as described in table 2-1.

It's very important to understand that url parameters are only captured if the provided value matches a given path converter (e.g str for a string) or named group regular expression (e.g. \D+ for non-digits). If a url request doesn't match a given django.urls.path path converter or django.urls.re_path named group regular expression, Django moves onto the next django.urls.path or django.urls.re_path statement until it finds a matching django.urls.path or django.urls.re_path statement or returns a not found error, in this sense, Django's url path matching/action mechanism works just the same with or without url parameters.

Getting back to listing 2-4, the second django.urls.path statement is used to match a url prefixed with stores/ followed by an integer. The <int:store_id> syntax represents a path converter, where int is the path converter type and store_id is the url parameter name given to the matching value (e.g. a request for /stores/1/ means store_id=1). The second line is an equivalent django.urls.re_path statement that uses a regular expression named group (?P<store_id>\d+) to perform that same match as the thrid line. In this case, the ?P<> syntax tells Django to treat this part of the django.urls.re_path regular expression as a named group and assign its value to a parameter named store_id declared between <>. The final piece \d+ is a regular expression that represents one or more digits -- technically an integer -- as described in table 2-1.

There's an important difference you should be aware of between django.urls.path and django.urls.re_path statements that match something other than a string, such as an integer like the last examples in listing 2-4. Although the statements path('stores/<int:store_id>/',...), and re_path(r'^stores/(?P<store_id>\d+)/',...), both match urls in the form /stores/1/, the store_id parameter data type is different in each case. All parameters for django.urls.re_path statements are treated as strings, this means in a statement like re_path(r'^stores/(?P<store_id>\d+)/',...), the view or template that receives the parameter sees store_id='6', which means that in order to use store_id as an actual integer (e.g. for a mathematical operation) the store_id must be converted to an integer. On the other hand, all parameters in django.urls.path statements are automatically converted to their path convert type, which means in a statement like path('stores/<int:store_id>/',...) that uses an int path converter, the view or template that receives the parameter sees store_id=6, meaning that store_id is an actual integer data type. This last process is helpful because it avoids any additional conversion steps necessary to work with url parameters in views or templates, a process that's illustrated in the upcoming sections.

Note The only thing you shouldn't try to match as url parameters are url query strings -- snippets that are added to urls with ? followed by parameter_name=parameter_value separated by & (e.g./drinks/mocha/?type=cold&size=large). Url query strings in Django should always be processed in view methods, a process that's described in the upcoming section Url query string processing in Django view methods.

Just as the url parameter matching process for strings and numbers differs for both django.urls.path and django.urls.re_path statements -- as shown in listing 2-4 -- matching more complex url parameters is also different for django.urls.path and django.urls.re_path statements.

In order to create more elaborate url parameters for django.urls.re_path statements you must create more elaborate regular expressions. Since django.urls.re_path statements are based on regular expressions, it's simply a matter of incorporating a regular expression that matches a desired set of url characters as a regular expression named group (e.g. (?P<url_parameter_name>regular_expression)). Because regular expressions can have an endless amount of variations I won't attempt to describe them here, but you can refer to table 2-1 and table 2-2 for sample syntax on how to build elaborate regular expressions and incorporate them to match url parameters.

Creating elaborate url parameters for django.urls.path statements requires exploring Django path converters. By default, Django is equipped with five path converters presented in table 2-3.

Table 2-3. Django path converters for django.urls.path supported by default

Path converter typeDescriptionExample path converter
strMatches any non-empty string, excluding the path separator /
path('drinks/<str:drink_name>/',...)
Matches urls like /drinks/espresso/ and /drinks/latte/, where the drink_name parameter receives a value of espresso and latte, respectively, as a standard Python str data type.
intMatches zero or any positive integer
path('stores/<int:store_id>/',...)
Matches urls like /stores/1/ and /stores/435/, where the store_id parameter receives a value of 1 and 435, respectively, as a standard Python int data type.
slugMatches a slug. In Django, a slug is a normalized ASCII string consisting of lowercase letters, numbers and hyphens (e.g. The slug representation of the string Welcome to the #1 Coffeehouse! is welcome-to-the-1-coffeehouse)
path('about/<slug:page>/',...)
Matches urls like /about/contact-us/ and /about/serving-our-customers-since-2000/, where the page parameter receives a value of contact-us and serving-our-customers-since-2000, respectively, as a standard Python str data type.
uuidMatches a Universally Unique Identifier (UUID), which is a 16 byte number displayed in 5 groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (e.g. 550e8400-e29b-41d4-a716-446655440000)
path('user/<uuid:user_id>/',...)
Matches urls like /user/bae88fa0-a3e8-11e9-a2a3-2a2ae2dbcce4/, where the user_id parameter receives a value of bae88fa0-a3e8-11e9-a2a3-2a2ae2dbcce4, as a standard Python uuid.UUID data type.
django.urls.pathMatches any non-empty string, including the path separator /
path('seo/<path:landing>/',...)
Matches urls like seo/google/search/keyword/coffee/, where the landing parameter receives a value of google/search/keyword/coffee, as a standard Python str data type.

As you can see in table 2-3, in addition to the str and int path converters illustrated in listing 2-4, it's also possible to match more elaborate sets of url characters that include a slug, a uuid and a django.urls.path. But what happens if the path converters in table 2-4 aren't sufficient for your needs ? You need to create custom path converters which is the topic of the next section.

Custom path converters for django.urls.path statements

The process to create a custom Django path converter consists of two steps: creating a custom path converter class and registering said class with the django.urls.register_converter.

A Django path converter class is a standard Python class that must comply with the presence of a class attribute named regex, as well as the class methods to_python() and to_url. Listing 2-5 illustrates two custom path converter classes, one to match roman numerals and the other to match float numbers.

Listing 2-5. Django custom path converter classes to match roman numerals and floats as url parameters

# Contents coffeehouse/utils/converters.py

class RomanNumeralConverter:
    regex = '[MDCLXVImdclxvi]+'

    def to_python(self, value):
        return str(value)

    def to_url(self, value):
        return '{}'.format(value)


class FloatConverter:
    regex = '[\d\.\d]+'

    def to_python(self, value):
        return float(value)

    def to_url(self, value):
        return '{}'.format(value)    
Tip To learn more about Python classes, see Appendix A 'Classes and subclasses'.

The RomanNumeralConverter class in listing 2-5 begins with the regular expression regex='[MDCLXVImdclxvi]+' class attribute to define the allowed characters for the custom path converter, in this case, it's a simple regular expression that matches at least one occurrence of uppercase or lowercase roman numeral characters. Here you can use a regular expression as elaborate as needed to cover as many edge cases as required, but once again, exploring elaborate regular expressions is beyond the scope of Django. Next, the RomanNumeralConverter to_python method is used to convert the matching path value to a Python value, this is required to hand off the converted parameter value to a view or template. In this case, the to_python method simply casts the value to a str data type for simplicity, which is how the view or template will receive the value. Finally, the RomanNumeralConverter to_url method is used to convert the Python value back to a url representation, which in this case is a straightforward verbatim string format.

The FloatConverter class in listing 2-5 begins with the regular expression regex = '[\d\.\d]+' which tells Django to match at least one occurrence of a digit, followed by a . and/or another digit. Here again, we're using a very simple regular expression to match basic float numbers (e.g. 10.5, 6.2), which can be further tweaked to avoid edge cases or support more elaborate float numbers. Next, FloatConverter to_python method is used to convert the matching path value to a Python float value, which is how the view or template will receive the value. Finally, RomanNumeralConverter to_url method is used to convert the Python float value back to a url representation, which in this case is a verbatim string format.

Once you have custom converter classes, you can make use of them in Django urls by registering them with the django.urls.register_converter method, a process that's illustrated in listing 2-6.

Listing 2-6. Register Django custom path converter classes with django.urls.register_converter to use on urls

# Contents coffeehouse/urls.py

from django.contrib import admin
from django.urls import path, re_path, register_converter, include
from django.views.generic import TemplateView

from coffeehouse.utils import converters

register_converter(converters.RomanNumeralConverter, 'roman')
register_converter(converters.FloatConverter, 'float')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('<roman:year>/',TemplateView.as_view(template_name='homepage.html')),
    path('<float:float_number>/',TemplateView.as_view(template_name='homepage.html')), 
]

The register_converter method accepts two arguments: a custom converter class and a string value to reference a converter class in urls. You can see in listing 2-6, the RomanNumeralConverter class from listing 2-5 is given the 'roman' reference and the FloatConverter class form listing 2-5 is given the 'float' reference.

Next, you can observe two django.urls.path statements, the first path('<roman:year>/'... which tells Django to match a roman number -- based on the regexp attribute of the RomanNumeralConverter class -- and assign its matching value to the year parameter, as well as the path('<float:float_number>/'... which tells Django to match a float number -- based on the regexp attribute of the FloatConverter class -- and assign its matching value to the float_number parameter. In both cases, if a match occurs, control is rescinded to the homepage.html template where the url parameters are passed with their respective data type, a str in the case of year and a float in the case of float_number, per the custom path converter class method to_python illustrated in listing 2-5.

Url parameter access in Django templates and views

Once you've set up url parameters using either django.urls.path statements with path converters or django.urls.re_path statements with regular expression named groups, you'll want to access them in a Django template or view method. Listing 2-7 illustrates two variations.

Listing 2-7. Django url parameters accessed in templates and view methods

# Contents coffeehouse/urls.py
from coffeehouse.stores import views as stores_views
from django.urls import path

urlpatterns = [
    path('drinks/<str:drink_name>/',TemplateView.as_view(template_name='drinks/index.html')),
    path('stores/<int:store_id>/',stores_views.detail),
]

If a url match occurs for the path('drinks/<str:drink_name>/',...) statement in listing 2-7, the request is sent directly to the template drinks/index.html. Django provides access to all url parameters defined in this manner through a Django template context variable with the same name. Therefore to access a url parameter you would use the parameter name drink_type directly in the template. For example, to output the value of the drink_name parameter you would use the standard {{}} Django template syntax (e.g. {{drink_name}}).

Next, let's take a look at another variation of url parameters illustrated in listing 2-7 which sends control to a Django view method. If a url match occurs for the path('stores/<store_id>/',...) statement in listing 2-7, the request is sent directly to the Django view method coffeehouse.stores.views.detail. Where coffeehouse.stores is the package name, views.py the file inside the stores app and detail the name of the view method. Listing 2-8 illustrates the detail view method to access the store_id parameter.

Listing 2-8. Django view method in views.py to access url parameter

from django.shortcuts import render

def detail(request,store_id):
    # Access store_id url parameter with store_id variable
    return render(request,'stores/detail.html')

Notice in listing 2-8 how the detail method has two arguments. The first argument is a request object, which is always the same for all Django view methods. The second argument is the parameter passed by the url. It's important to note the names of url parameters must match the names of the method arguments. In this case, notice in listing 2-7 the parameter name is store_id and in listing 2-8 the method argument is also named store_id.

With access to the url parameter via the view method argument, the method can execute logic with the parameter (e.g. query a database) that can then be passed to a Django template for presentation, which in the case of listing 2-8 is the stores/detail.html template.

Caution Django url parameters with django.urls.re_path statements are always treated as strings, irrespective of the regular expression. For example, \d+ catches digits, but a value of one is treated as '1' (String), not 1 (Integer). This is particularly important if you plan to work with url parameters in view methods and do operations that require something other than strings.
Tip Django url parameters with django.urls.path statements are always treated as their path converter type. For example, <int:store_id> catches an integer and is passed as such to its processing construct (e.g. template or view methods). This is an implicit benefit of working with django.urls.path url parameters because they don't require explicit conversion from strings like django.urls.re_path url parameters.

Another option available for url parameters handled by view methods is to make them optional, which in turn allows you to leverage the same view method for multiple urls. Url parameters can be made optional by assigning a default value to a view method argument. Listing 2-9 shows a new url that calls the same view method (coffeehouse.stores.views.detail) but doesn't define a parameter.

Listing 2-9. Django urls with optional parameters leveraging the same view method

from coffeehouse.stores import views as stores_views
from django.urls import path

urlpatterns = [
    path('stores/<int:store_id>/',stores_views.detail),
    path('stores/',stores_views.detail),
]

If you called the url /stores/ without modifying the detail method in listing 2-9 you would get an error. The error occurs because the detail view method expects a store_id argument which isn't provided by the first url. To fix this problem, you can define a default value for the store_id in the view method, as illustrated in listing 2-10.

Listing 2-10 Django view method in views.py with default value

from django.shortcuts import render

def detail(request,store_id=1):
    # Access store_id with 'store_id' variable
    return render(request,'stores/detail.html')

Notice in listing 2-10 how the store_id argument has the assignment =1. This means the argument will have a default value of 1 in case the view method is called without store_id. This approach allows you to leverage the same view method to handle multiple urls with optional parameters.

In addition to accessing url parameters inside view methods, it's also possible to access extra options from the url definition. These extra options are defined inside a dictionary declared as the last argument in a url definition. After the view method declaration, you add a dictionary with the key-value pairs you wish to access inside the view method. The following snippet illustrates a modified version of the url statement in listing 2-9.

path('stores/',stores_views.detail,{'location':'headquarters'})

In this case, the location key becomes a url extra option that's passed as a parameter to the view method. Url extra options are accessed just like url parameters, so to access a url extra option inside a view method you need to modify the method signature to accept an argument with the same name as the url extra option. In this case, the method signature:

def detail(request,store_id=1):

needs to change to:

def detail(request,store_id=1,location=None):

Notice the location argument is made optional by assigning a default value of None.

In addition to treating parts of a url as parameters, it's also possible to define extra options in the url definition to access them in Django templates as context variables. These extra options are defined inside a dictionary declared as the last part of the url definition.

For example, look at the following modified url Django definition from listing 2-4:

path('drinks/<drink_name>'), TemplateView.as_view(template_name='drinks/index.html'), {'onsale':True}),

Notice how a dictionary with key-values is added at the end of the url definition. In this manner, the onsale key becomes a url extra option, which is passed to the underlying template as a context variable. Url extra options are accessed like url parameters as template context variables. So to output the onsale extra option you would use the {{onsale}} syntax.

Url query string processing in Django view methods

Finally, it's also possible to access url parameters separated by ? And & -- technically known as a query string -- inside Django view methods. These type of parameters can be accessed inside a view method using the request object.

Take for example the url /stores/1/?hours=sunday&map=flash, listing 2-11 illustrates how to extract the arguments from this url separated by ? and & using request.GET.

Listing 2-11. Django view method extracting url parameters with request.GET

from django.shortcuts import render

def detail(request,store_id=1,location=None):
    # Access store_id url parameter with 'store_id' variable and location url parameter with 'location' variable
    # Extract 'hours' or 'map' value appended to url as
    # ?hours=sunday&map=flash
    hours = request.GET.get('hours', '')
    map = request.GET.get('map', '')
    # 'hours' has value 'sunday' or '' if hours not in url
    # 'map' has value 'flash' or '' if map not in url
    return render(request,'stores/detail.html')

Listing 2-11 uses the syntax request.GET.get(<parameter>, ''). If the parameter is present in request.GET it extracts the value and assigns it to a variable for further usage, if the parameter is not present then the parameter variable is assigned a default empty value of ''-- you could equally use None or any other default value -- as this is part of Python's standard dictionary get() method syntax to obtain default values.

This last process is designed to extract parameters from an HTTP GET request, however, Django also supports the syntax request.POST.get to extract parameters from an HTTP POST request, which is described in greater detail in the chapter on Django forms and later in this chapter in the section on Django view method requests.

  1. https://docs.python.org/3/howto/regex.html#non-capturing-and-named-groups