In Chapter 2, Views and URLconfs, we explained the basics of Django's view functions and URLconfs. This chapter goes into more detail about advanced functionality in those two pieces of the framework.
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.
Consider this URLconf, which builds on the example in Chapter 2, Views and URLconfs:
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 Chapter 2, Views and URLconfs, 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),
]
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
.
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 ^/reviews
.
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:
/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:
/reviews/2005/03/
would call the function views.month_archive(request,
year='2005',
month='03')
, instead of views.month_archive(request,
'2005',
'03')
./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 view's function definitions. Of course, these benefits come at the cost of brevity; some developers find the named-group syntax ugly and too verbose.
Here's the algorithm the URLconf parser follows, with respect to named groups vs. non-named groups in a regular expression:
In both cases, any extra keyword arguments that have been given will also be passed to the view.
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
, and so on-will be routed to the same function for the same URL.
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.
A convenient trick is to specify default parameters for your view's 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.
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')