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 byparameter_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 type | Description | Example path converter |
---|---|---|
str | Matches 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.
|
int | Matches 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.
|
slug | Matches 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.
|
uuid | Matches 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.path | Matches 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 withdjango.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 withdjango.urls.path
url parameters because they don't require explicit conversion from strings likedjango.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.