There’s nothing “special” about URLconfs – like anything else in Django, they’re just Python code. You can take advantage of this in several ways, as described in the sections that follow.
Streamlining Function Imports
Consider this URLconf, which builds on the previous example
from django.conf.urls import include, url
from django.contrib import admin
from mysite.views import hello, current_datetime, hours_ahead
urlpatterns = [
url(r’^admin/’, include(admin.site.urls)),
url(r’^hello/$’, hello),
url(r’^time/$’, current_datetime),
url(r’^time/plus/(\d{1,2})/$’, hours_ahead),
]
As explained in earlier chapter, each entry in the URLconf includes its associated view function, passed directly as a function object. This means it’s necessary to import the view functions at the top of the module.
But as a Django application grows in complexity, its URLconf grows, too, and keeping those imports can be tedious to manage. (For each new view function, you have to remember to import it, and the import statement tends to get overly long if you use this approach.)
It’s possible to avoid that tedium by importing the views module itself. This example URLconf is equivalent to the previous one:
from django.conf.urls import include, url
from . import views
urlpatterns = [
url(r’^hello/$’, views.hello),
url(r’^time/$’, views.current_datetime),
url(r’^time/plus/(d{1,2})/$’, views.hours_ahead),
]
Special-Casing URLs in Debug Mode
Speaking of constructing urlpatterns dynamically, you might want to take advantage of this technique to alter your URLconf’s behavior while in Django’s debug mode. To do this, just check the value of the DEBUG setting at runtime, like so:
from django.conf import settings
from django.conf.urls import url
from . import views
urlpatterns = [
url(r’^$’, views.homepage),
url(r’^(\d{4})/([a-z]{3})/$’, views.archive_month),
]
if settings.DEBUG:
urlpatterns += [url(r’^debuginfo/$’, views.debug),]
In this example, the URL /debuginfo/ will only be available if your DEBUG
setting is set to True.
Named Groups
The above example used simple, non-named regular-expression groups (via parenthesis) to capture bits of the URL and pass them as positional arguments to a view.
In more advanced usage, it’s possible to use named regular-expression groups to capture URL bits and pass them as keyword arguments to a view.
In Python regular expressions, the syntax for named regular-expression groups is (?P<name>pattern), where name is the name of the group and pattern is some pattern to match.
For example, say we have a list of book reviews on our books site, and we want to retrieve reviews for certain dates, or date ranges.
Here’s a sample URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r’^reviews/2003/$’, views.special_case_2003),
url(r’^reviews/([0-9]{4})/$’, views.year_archive),
url(r’^reviews/([0-9]{4})/([0-9]{2})/$’, views.month_archive),
url(r’^reviews/([0-9]{4})/([0-9]{2})/([0-9]+)/$’, views.review_detail),
]
Notes
- To capture a value from the URL, just put parenthesis around it.
- There’s no need to add a leading slash, because every URL has that. For example, it’s ^reviews, not ^/review
- The ‘r’ in front of each regular expression string is optional but recommended. It tells Python that a string is “raw” – that nothing in the string should be escaped.
Example requests: - A request to /reviews/2005/03/ would match the third entry in the list. Django would call the function views.month_archive(request, ‘2005’, ’03’).
- /reviews/2005/3/ would not match any URL patterns, because the third entry in the list requires two digits for the month.
- /reviews/2003/ would match the first pattern in the list, not the second one, because the patterns are tested in order, and the first one is the first test to pass. Feel free to exploit the ordering to insert special cases like this.
- /reviews/2003 would not match any of these patterns, because each pattern requires that the URL end with a slash.
- /reviews/2003/03/03/ would match the final pattern. Django would call the function views.review_detail(request, ‘2003’, ’03’,
- ’03’).
Here’s the above example URLconf, rewritten to use named groups:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r’^reviews/2003/$’, views.special_case_2003),
url(r’^reviews/(?P<year>[0-9]{4})/$’, views.year_archive),
url(r’^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$’,
views.month_archive),
url(r’^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$’,
views.review_detail),
]
This accomplishes exactly the same thing as the previous example, with one subtle difference: The captured values are passed to view functions as keyword arguments rather than positional arguments.
For example:
- A request to /reviews/2005/03/ would call the function views.month_archive(request, year=’2005′, month=’03’), instead of views.month_archive(request, ‘2005’, ’03’).
- A request to /reviews/2003/03/03/ would call the function views.review_detail(request, year=’2003′, month=’03’, day=’03’).
In practice, this means your URLconfs are slightly more explicit and less prone to argument-order bugs – and you can reorder the arguments in your views’ function definitions. Of course, these benefits come at the cost of brevity; some developers find the named-group syntax ugly and too verbose.
The matching/grouping algorithm – Here’s the algorithm the URLconf parser follows, with respect to named groups vs. non-named groups in a regular expression:
- If there are any named arguments, it will use those, ignoring non-named arguments.
- Otherwise, it will pass all non-named arguments as positional arguments.
In both cases, any extra keyword arguments that have been given will also be passed to the view.
What the URLconf Searches Against
The URLconf searches against the requested URL, as a normal Python string. This does not include GET or POST parameters, or the domain name. For example, in a request to http://www.example.com/myapp/, the URLconf will look for myapp/. In a request to http://www.example.com/myapp/?page=3, the URLconf will look for myapp/. The URLconf doesn’t look at the request method. In other words, all request methods – POST, GET, HEAD, etc. – will be routed to the same function for the same URL.
Captured Arguments Are Always Strings
Each captured argument is sent to the view as a plain Python string, regardless of what sort of match the regular expression makes. For example, in this URLconf line:
url(r’^reviews/(?P<year>[0-9]{4})/$’, views.year_archive),
… the year argument to views.year_archive() will be a string, not an integer, even though the [0-9]{4} will only match integer strings.
Specifying Defaults for View Arguments
A convenient trick is to specify default parameters for your views’ arguments. Here’s an example URLconf:
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r’^reviews/$’, views.page),
url(r’^reviews/page(?P<num>[0-9]+)/$’, views.page),
]
# View (in reviews/views.py)
def page(request, num=”1″):
# Output the appropriate page of review entries, according to num.
In the above example, both URL patterns point to the same view – views.page – but the first pattern doesn’t capture anything from the URL. If the first pattern matches, the page() function will use its default argument for num, “1”. If the second pattern matches, page() will use whatever num value was captured by the regex.
Performance
Each regular expression in a urlpatterns is compiled the first time it’s accessed. This makes the system blazingly fast.
Keyword Arguments vs. Positional Arguments – A Python function can be called using keyword arguments or positional arguments – and, in some cases, both at the same time. In a keyword argument call, you specify the names of the arguments along with the values you’re passing. In a positional argument call, you simply pass the arguments without explicitly specifying which argument matches which value; the association is implicit in the arguments’ order.
For example, consider this simple function:
def sell(item, price, quantity):
print “Selling %s unit(s) of %s at %s” % (quantity, item, price)
To call it with positional arguments, you specify the arguments in the order in which they’re listed in the function definition:
sell(‘Socks’, ‘$2.50’, 6)
To call it with keyword arguments, you specify the names of the arguments along with the values. The following statements are equivalent:
sell(item=’Socks’, price=’$2.50′, quantity=6)
sell(item=’Socks’, quantity=6, price=’$2.50′)
sell(price=’$2.50′, item=’Socks’, quantity=6)
sell(price=’$2.50′, quantity=6, item=’Socks’)
sell(quantity=6, item=’Socks’, price=’$2.50′)
sell(quantity=6, price=’$2.50′, item=’Socks’)
Finally, you can mix keyword and positional arguments, as long as all positional arguments are listed before keyword arguments. The following statements are equivalent to the previous examples:
sell(‘Socks’, ‘$2.50’, quantity=6)
sell(‘Socks’, price=’$2.50′, quantity=6)
sell(‘Socks’, quantity=6, price=’$2.50′)
Error Handling
When Django can’t find a regex matching the requested URL, or when an exception is raised, Django will invoke an error-handling view. The views to use for these cases are specified by four variables. The variables are:
- handler404
- handler500
- handler403
- handler400
Their default values should suffice for most projects, but further customization is possible by assigning values to them. Such values can be set in your root URLconf. Setting these variables in any other URLconf will have no effect. Values must be callables, or strings representing the full Python import path to the view that should be called to handle the error condition at hand.