Url parameters, extra options & query strings
You just learned how to use a wide variety of regular expressions to create urls for your Django applications. However, if you look back at listings 2-1, 2-2 and 2-3, you'll notice 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 (e.g. query a database). To relay this
information the url needs to treat this information as a
parameter.
To handle url parameters Django
uses Python's standard regular expression syntax for named
groups[2]. Listing 2-4 shows a url that
creates a parameter named drink_name
.
Listing 2-4. Django url parameter definition for access in templates
urlpatterns = [ url(r'^drinks/(?P<drink_name>\D+)/',TemplateView.as_view(template_name='drinks/index.html')), ]
Notice the
(?P<drink_name>\D+)
syntax in listing 2-4. The
?P<>
syntax tells Django to treat this part of
the regular expression as a named group and assign the value to a
parameter named drink_name
declared between
<>
. The final piece \D+
is a
regular expression to determine the matching value, in this case
the matching value is one or more non-digit characters, as
described in table 2-1.
It's very important you
understand a parameter is only captured if the provided value
matches the specified regular expression (e.g \D+ for non-digits ).
For example, for the url request /drinks/mocha/
the
value mocha
is assigned to the drink_name
parameter, but for a url like /drinks/123/
the regular
expression pattern doesn't match -- because 123
are
digits -- so no action is taken.
If a url match occurs in listing
2-4, the request is sent directly to the template
drinks/index.html
. Django provides access to all
parameters defined in this manner through a Django template context
variable with the same name. Therefore to access the 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}}
).
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:
url(r'^drinks/(?P<drink_name>\D+)', 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.
Next, let's take a look at another variation of url parameters illustrated in listing 2-5 which sends control to a Django view method.
Listing 2-5. Django url parameter definition for access in view methods in main urls.py file
# Project main urls.py from coffeehouse.stores import views as stores_views urlpatterns = patterns[ url(r'^stores/(?P<store_id>\d+)/',stores_views.detail), ]
Notice the
(?P<store_id>\d+)
syntax in listing 2-5 is
pretty similar to the one in 2-4. The thing that changes is the
parameter is now named store_id
and the regular
expression is \d+
to match digits. So for example, if
a request is made to the url /stores/1/
the value 1 is
assigned to the store_id
parameter and if a request is
made to a url like /stores/downtown/
the regular
expression pattern doesn't match -- because downtown
are letters not digits -- so no action is taken.
If a url match occurs for listing
2-5, 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-6
illustrates the detail
view method to access the
store_id
parameter.
Listing 2-6. Django view method in views.py to access url parameter
from django.shortcuts import render def detail(request,store_id): # Access store_id with 'store_id' variable return render(request,'stores/detail.html')
Notice in listing 2-6 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-5 the parameter name is store_id
and in
listing 2-6 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.
Caution Django url parameters 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.
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. Parameters can be made optional by assigning a default value
to a view method argument. Listing 2-7 shows a new url that calls
the same view method (coffeehouse.stores.views.detail
)
but doesn't define a parameter.
Listing 2-7. Django urls with optional parameters leveraging the same view method
from coffeehouse.stores import views as stores_views urlpatterns = patterns[ url(r'^stores/(?P<store_id>\d+)/',stores_views.detail), url(r'^stores/',stores_views.detail), ]
If you called the url
/stores/
without modifying the detail
method in listing 2-6 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-8.
Listing 2-8 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-8 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-7.
url(r'^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
.
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-9
illustrates how to extract the arguments from this url separated by
?
and &
using
request.GET
.
Listing 2-9. 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 param with 'store_id' variable and location param 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-9 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
support 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.