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.

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