URL parameters in views

As described in the Django tutorial for views and the request handling docs, if you want to capture part of a URL to be used in a view function, you can do it by configuring your URLs.

Let’s say we have an e-commerce site where we want to display products on individual pages. We want /product/ to be the prefix for all these pages, and the next part to be the “slug” for the product — a URL-friendly version of the name (e.g white-t-shirt instead of “White T-Shirt”).

We can do that as follows:

# urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('products/<slug:slug>/', views.product_detail, name='product_detail'),
]
# views.py

def product_detail(request, slug):
    return TemplateResponse(request, 'shop/product_detail.html', {})

Note how the slug parameter has to be added to view function signature, as well as in the URL conf. In the URL pattern, the first slug is the path converter type. The second slug is the name of the parameter in the product_detail view, and we could have chosen something different.

If you don’t modify the view like this, it simply won’t work — you’ll get an exception, because Django will attempt to call your function with parameters that your function doesn’t accept, which is an error in Python.

If you are used to CBVs, this is one of the more obvious differences. With a CBV, you don’t have to modify the function signature — because there isn’t one to modify. But with the CBV you have to write more code to get hold of that parameter.

Be sure to check the Django docs about path converters for the different kind of things you might add into your URLs. If you are into type hints, also see below for tips on how you can enhance this pattern.

Otherwise, onto Displaying a single database object, where we will actually use the slug parameter.

Discussion: Generic code and function signatures

Django’s URL-to-function dispatching mechanism is very elegant and powerful, converting parts of the URL into a function parameter that’s just ready and waiting to be used.

With the path converters functionality added in 2.0 it got a whole lot better, because it will automatically convert things to the correct type for you, reducing the amount of type conversion you have to do in your function.

You can additionally make use of this by adding type hints:

def product_detail(request, name: str):
    pass  # etc

# OR
def product_detail(request, pk: int):
    pass  # etc

Now, you no longer have to check the URLconf to be sure of the type of the argument, it’s right there in your function.

The elegance of URL parameters as part of the function signature contrasts with CBVs, where you have to do extra work get to hold of the parameter:

name = self.kwargs['name']

Discussion: Type-checked parameters

Of course, if we add type hints, wouldn’t it be even cooler if we could automatically ensure that the URL configuration matched the view function, both in terms of names and types of arguments?

django-urlconfchecks will do exactly that! (Based on code I wrote, but nicely packaged by Ali Sayyah).

Unfortunately, you lose out here if you are using CBVs, because you don’t have a signature that you can decorate with type hints. The signature that is externally visible for your view is view(request, *args, **kwargs), so it is impossible for the above type-checking code to work.

The fundamental issue here is generic code. Generic code is useful precisely because of its breadth — it can be used in a wide range of situations. However, the downside of generic code is that it must cater for every situation, instead of just yours. So CBVs have to have a kwargs dictionary, which isn’t really what you wanted. Generic code by definition lacks the personal touch.

Of course, there can be times when the advantages outweigh the disadvantages. But make sure you know what you are missing!