Django URL configuration
URL configuration (URLconf) acts as a map for the website that Django supports, linking URLs to the functions that should be called for those URLs.
We use this method to inform Django which function to execute when a specific URL is encountered.
URLconf setup
Basic format:
from django.conf.urls import url
urlpatterns = [
url(regex pattern, view function, parameters, alias),
]
Example:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
Parameters explanation:
- regex pattern: a string representing a regular expression
- view function: a callable object, typically a view function
- parameters: optional default parameters to pass to the view function (as a dictionary)
- alias: an optional name parameter
Note:
In Django 2.0, the routing system uses the following syntax (official documentation):
from django.urls import path, re_path
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
The re_path and url in version 2.0 work similarly.
Regular expression details
Basic configuration
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
Notes
- Elements in urlpatterns are matched in the order they appear from top to bottom, stopping at the first match.
- To capture a value from a URL, place it within parentheses (group matching).
- Do not add a leading backslash because each URL has one.
- The 'r' before each regex is optional but recommended.
Additional notes
# Whether to automatically redirect URLs without a trailing slash to ones with one
APPEND_SLASH=True
By default, Django's settings.py does not include APPEND_SLASH, but it defaults to True. This setting automatically adds a trailing slash to URLs.
For example, if you define urls.py as follows:
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^blog/$', views.blog),
]
Accessing http://www.example.com/blog will automatically redirect to http://www.example.com/blog/.
If APPEND_SLASH is set to False in settings.py, requesting http://www.example.com/blog will result in a page not found error.
Named group matching
The previous examples used simple grouping to capture values from the URL and pass them as positional arguments to the view.
In more advanced usage, named groups can be used to capture values from the URL and pass them as keyword arguments to the view.
In Python's regular expressions, named group syntax is (?P<name>pattern), where name is the group's name and pattern is the pattern to match.
Here is a rewrite of the previous URLconf using named groups:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
This implementation is identical to the previous example, except that captured values are passed as keyword arguments instead of positional arguments to the view function.
For instance, the URL /articles/2017/12/ would call the view function as follows:
views.month_archive(request, year="2017", month="12")
Using named groups can make your URLconf clearer and reduce errors related to parameter order. However, some developers find the named group syntax too cumbersome.
Choose whichever method suits your preference.
Location of URLconf matching
The URLconf looks for the request's URL as a normal Python string, excluding GET and POST parameters and the domain.
For example, for the request http://www.example.com/myapp/, the URLconf will look for /myapp/.
In the request http://www.example.com/myapp/?page=3, the URLconf will still look for /myapp/.
The URLconf does not check the request method. In other words, the same URL's POST, GET, and HEAD requests will all route to the same function.
Captured parameter are always strings
All captured parameters in the URLconf are passed as plain Python strings to the view, regardless of the regex used. For example, in the following URLconf line:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
The year parameter passed to the views.year_archive() function is always a string type.
Specifying default values in the view function
# urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# views.py
def page(request, num="1"):
pass
In this 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 parameter num="1". If the second pattern matches, page() will use the captured num value from the regex.
Including other URLconfs
# At any point, your urlpatterns can "include" other URLconf modules. This essentially "roots" a set of URLs below other ones.
# For example, here’s an excerpt of the URLconf for the Django website itself.
# It includes a number of other URLconfs:
from django.conf.urls import include, url
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^blog/', include('blog.urls')), # Can include other URLconfs files
]
Passing extra parameters to the view function (understanding)
URLconfs have a hook that allows passing a Python dictionary as additional parameters to the view function.
django.conf.urls.url() can accept an optional third parameter, which is a dictionary of additional keyword parameters to pass to the view function.
For example:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
In this example, for a request to /blog/2005/, Django will call views.year_archive(request, year='2005', foo='bar'). When parameters in the dictionary of extra parameters and the named keyword parameters captured from the URL have the same name, the dicsionary parameters take precedence over the URL-captured parameters.
Naming URLs and reverse URL resolution
A common requirement when using a Django project is to obtain the final form of a URL to embed it in generated content (URLs in views or displayed to users) or for server-side navigation (redirections). People strongly prefer not to hardcode these URLs (laborious, non-scalable, and error-prone) or design a separate URL generation mechanism that is unrelated to the URLconf, which can lead to outdated URLs. In other words, a DRY mechanism is needed. In addition to other benefits, it allows the designed URLs to be automatically updated without traversing the source code of the project to search and replace outdated URLs.
The initial information needed to obtain a URL is the identifier of the view processing it (e.g., name), and other necessary information to find the correct URL includes the type (positional or keyword) and values of the view parameters.
Django provides a way for the URL mapping to be the only place where the URL is designed. You fill out your URLconf, and then you can use it in two directions:
- According to the URL request from the user/browser, it calls the correct Django view and extracts the values needed for the parameters from the URL.
- According to the identifier of the Django view and the values of the parameters to be passed to it, get the associated URL.
The first use case is what we've been discussing in previous chapters. The second use case is called reverse URL resolution, reverse URL matching, reverse URL query, or simply URL reversal.
Wherever a URL is needed, Django provides different tools for URL reversal at different levels:
- In templates: use the url template tag.
- In Python code: use the django.core.urlresolvers.reverse() function.
- In higher-level code related to Django model instances: use the get_absolute_url() method.
Above all, you may not have understood much. (That's a rough translation of the official documentation.)
Let's simplify: you can assign names to your URL matching rules. Each URL matching pattern gets a name.
This way, you don't need to hardcode URL code anymore; you just call the current URL by name.
For example:
url(r'^home', views.home, name='home'), # Assign a name 'home' to my URL matching pattern
url(r'^index/(\d*)', views.index, name='index'), # Assign a name 'index' to my URL matching pattern
In templates, you can reference it like this:
{% url 'home' %}
In view functions, you can reference it like this:
from django.urls import reverse
reverse("index", args=("2018", ))
Example:
Consider the following URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
# ...
url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
# ...
]
According to this design, the archive URL for a year nnnn is /articles/nnnn/.
You can use the following method in template code to get them:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>
In Python code, use it like this:
from django.urls import reverse
from django.shortcuts import redirect
def redirect_to_year(request):
# ...
year = 2006
# ...
return redirect(reverse('news-year-archive', args=(year,)))
If for some reason, the URL for archiving articles by year needs to be adjusted, you will only need to modify the contents of the URLconf.
In some scenarios, a view is generic, so there is a one-to-many relationship between the URL and the view. For these cases, when reversing a URL, just the view name is not enough.
Note:
To perform the URL reversal in the above example, you will need to use a named URL pattern. The string used for the URL name can contain any characters you like. It is not limited to valid Python names.
When naming your URL patterns, ensure the names do not conflict with names from other applications. If your URL pattern is called comment and another application also has the same name, you cannot guarantee which URL will be inserted when you use this name in a template.
Adding a prefix, such as the application's name, to the URL name will reduce the possibility of conflicts. We recommend using myapp-comment instead of comment.
Namespace patterns
Even if different apps use the same URL name, namespace patterns allow you to uniquely reverse named URLs.
For example:
project's urls.py
from django.conf.urls import url, include
urlpatterns = [
url(r'^app01/', include('app01.urls', namespace='app01')),
url(r'^app02/', include('app02.urls', namespace='app02')),
]
app01's urls.py
from django.conf.urls import url
from app01 import views
app_name = 'app01'
urlpatterns = [
url(r'^(?P<pk><\d+>)/$', views.detail, name='detail')
]
app02's urls.py
from django.conf.urls import url
from app02 import views
app_name = 'app02'
urlpatterns = [
url(r'^(?P<pk><\d+>)/$', views.detail, name='detail')
]
Now, even if the URL names in my two apps are the same, I can get the correct URL when reversing URLs using the namespace name.
Syntax:
'namespace name:url name'
In templates:
{% url 'app01:detail' pk=12 pp=99 %}
In view functions:
v = reverse('app01:detail', kwargs={'pk':11})
This way, even if the URL names in the apps are the same, I can get the correct URL.