Fixed #28593 -- Added a simplified URL routing syntax per DEP 0201.
Thanks Aymeric Augustin for shepherding the DEP and patch review. Thanks Marten Kenbeek and Tim Graham for contributing to the code. Thanks Tom Christie, Shai Berger, and Tim Graham for the docs.
This commit is contained in:
parent
c4c128d67c
commit
df41b5a05d
1
AUTHORS
1
AUTHORS
|
@ -732,6 +732,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Simon Meers <simon@simonmeers.com>
|
Simon Meers <simon@simonmeers.com>
|
||||||
Simon Williams
|
Simon Williams
|
||||||
Simon Willison <simon@simonwillison.net>
|
Simon Willison <simon@simonwillison.net>
|
||||||
|
Sjoerd Job Postmus
|
||||||
Slawek Mikula <slawek dot mikula at gmail dot com>
|
Slawek Mikula <slawek dot mikula at gmail dot com>
|
||||||
sloonz <simon.lipp@insa-lyon.fr>
|
sloonz <simon.lipp@insa-lyon.fr>
|
||||||
smurf@smurf.noris.de
|
smurf@smurf.noris.de
|
||||||
|
|
|
@ -5,17 +5,17 @@ The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
Examples:
|
Examples:
|
||||||
Function views
|
Function views
|
||||||
1. Add an import: from my_app import views
|
1. Add an import: from my_app import views
|
||||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
Class-based views
|
Class-based views
|
||||||
1. Add an import: from other_app.views import Home
|
1. Add an import: from other_app.views import Home
|
||||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
Including another URLconf
|
Including another URLconf
|
||||||
1. Import the include() function: from django.conf.urls import url, include
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.urls import RegexURLPattern, RegexURLResolver, include
|
from django.urls import include, re_path
|
||||||
from django.views import defaults
|
from django.views import defaults
|
||||||
|
|
||||||
__all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url']
|
__all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url']
|
||||||
|
@ -10,11 +10,4 @@ handler500 = defaults.server_error
|
||||||
|
|
||||||
|
|
||||||
def url(regex, view, kwargs=None, name=None):
|
def url(regex, view, kwargs=None, name=None):
|
||||||
if isinstance(view, (list, tuple)):
|
return re_path(regex, view, kwargs, name)
|
||||||
# For include(...) processing.
|
|
||||||
urlconf_module, app_name, namespace = view
|
|
||||||
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
|
|
||||||
elif callable(view):
|
|
||||||
return RegexURLPattern(regex, view, kwargs, name)
|
|
||||||
else:
|
|
||||||
raise TypeError('view must be a callable or a list/tuple in the case of include().')
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.urls import LocalePrefixPattern, URLResolver, get_resolver, path
|
||||||
from django.urls import LocaleRegexURLResolver, get_resolver
|
|
||||||
from django.views.i18n import set_language
|
from django.views.i18n import set_language
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +12,12 @@ def i18n_patterns(*urls, prefix_default_language=True):
|
||||||
"""
|
"""
|
||||||
if not settings.USE_I18N:
|
if not settings.USE_I18N:
|
||||||
return list(urls)
|
return list(urls)
|
||||||
return [LocaleRegexURLResolver(list(urls), prefix_default_language=prefix_default_language)]
|
return [
|
||||||
|
URLResolver(
|
||||||
|
LocalePrefixPattern(prefix_default_language=prefix_default_language),
|
||||||
|
list(urls),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
|
@ -25,11 +29,11 @@ def is_language_prefix_patterns_used(urlconf):
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
for url_pattern in get_resolver(urlconf).url_patterns:
|
for url_pattern in get_resolver(urlconf).url_patterns:
|
||||||
if isinstance(url_pattern, LocaleRegexURLResolver):
|
if isinstance(url_pattern.pattern, LocalePrefixPattern):
|
||||||
return True, url_pattern.prefix_default_language
|
return True, url_pattern.pattern.prefix_default_language
|
||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^setlang/$', set_language, name='set_language'),
|
path('setlang/', set_language, name='set_language'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.urls import re_path
|
||||||
from django.views.static import serve
|
from django.views.static import serve
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,5 +23,5 @@ def static(prefix, view=serve, **kwargs):
|
||||||
# No-op if not in debug mode or a non-local prefix.
|
# No-op if not in debug mode or a non-local prefix.
|
||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
url(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), view, kwargs=kwargs),
|
re_path(r'^%s(?P<path>.*)$' % re.escape(prefix.lstrip('/')), view, kwargs=kwargs),
|
||||||
]
|
]
|
||||||
|
|
|
@ -567,7 +567,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
return inline_instances
|
return inline_instances
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
@ -578,14 +578,14 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
info = self.model._meta.app_label, self.model._meta.model_name
|
info = self.model._meta.app_label, self.model._meta.model_name
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info),
|
path('', wrap(self.changelist_view), name='%s_%s_changelist' % info),
|
||||||
url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info),
|
path('add/', wrap(self.add_view), name='%s_%s_add' % info),
|
||||||
url(r'^autocomplete/$', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info),
|
path('autocomplete/', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info),
|
||||||
url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info),
|
path('<path:object_id>/history/', wrap(self.history_view), name='%s_%s_history' % info),
|
||||||
url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info),
|
path('<path:object_id>/delete/', wrap(self.delete_view), name='%s_%s_delete' % info),
|
||||||
url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
|
path('<path:object_id>/change/', wrap(self.change_view), name='%s_%s_change' % info),
|
||||||
# For backwards compatibility (was the change url before 1.9)
|
# For backwards compatibility (was the change url before 1.9)
|
||||||
url(r'^(.+)/$', wrap(RedirectView.as_view(
|
path('<path:object_id>/', wrap(RedirectView.as_view(
|
||||||
pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
|
pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)
|
||||||
))),
|
))),
|
||||||
]
|
]
|
||||||
|
@ -1173,8 +1173,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
to_field = request.POST.get(TO_FIELD_VAR)
|
to_field = request.POST.get(TO_FIELD_VAR)
|
||||||
attr = str(to_field) if to_field else opts.pk.attname
|
attr = str(to_field) if to_field else opts.pk.attname
|
||||||
# Retrieve the `object_id` from the resolved pattern arguments.
|
value = request.resolver_match.kwargs['object_id']
|
||||||
value = request.resolver_match.args[0]
|
|
||||||
new_value = obj.serializable_value(attr)
|
new_value = obj.serializable_value(attr)
|
||||||
popup_response_data = json.dumps({
|
popup_response_data = json.dumps({
|
||||||
'action': 'change',
|
'action': 'change',
|
||||||
|
|
|
@ -196,11 +196,11 @@ class AdminSite:
|
||||||
class MyAdminSite(AdminSite):
|
class MyAdminSite(AdminSite):
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
urls += [
|
urls += [
|
||||||
url(r'^my_view/$', self.admin_view(some_view))
|
path('my_view/', self.admin_view(some_view))
|
||||||
]
|
]
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ class AdminSite:
|
||||||
return update_wrapper(inner, view)
|
return update_wrapper(inner, view)
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
from django.conf.urls import url, include
|
from django.urls import include, path, re_path
|
||||||
# Since this module gets imported in the application's root package,
|
# Since this module gets imported in the application's root package,
|
||||||
# it cannot import models from other applications at the module level,
|
# it cannot import models from other applications at the module level,
|
||||||
# and django.contrib.contenttypes.views imports ContentType.
|
# and django.contrib.contenttypes.views imports ContentType.
|
||||||
|
@ -244,15 +244,21 @@ class AdminSite:
|
||||||
|
|
||||||
# Admin-site-wide views.
|
# Admin-site-wide views.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', wrap(self.index), name='index'),
|
path('', wrap(self.index), name='index'),
|
||||||
url(r'^login/$', self.login, name='login'),
|
path('login/', self.login, name='login'),
|
||||||
url(r'^logout/$', wrap(self.logout), name='logout'),
|
path('logout/', wrap(self.logout), name='logout'),
|
||||||
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
|
path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
|
||||||
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
|
path(
|
||||||
name='password_change_done'),
|
'password_change/done/',
|
||||||
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
|
wrap(self.password_change_done, cacheable=True),
|
||||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
|
name='password_change_done',
|
||||||
name='view_on_site'),
|
),
|
||||||
|
path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
|
||||||
|
path(
|
||||||
|
'r/<int:content_type_id>/<path:object_id>/',
|
||||||
|
wrap(contenttype_views.shortcut),
|
||||||
|
name='view_on_site',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add in each model's views, and create a list of valid URLS for the
|
# Add in each model's views, and create a list of valid URLS for the
|
||||||
|
@ -260,7 +266,7 @@ class AdminSite:
|
||||||
valid_app_labels = []
|
valid_app_labels = []
|
||||||
for model, model_admin in self._registry.items():
|
for model, model_admin in self._registry.items():
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
|
path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
|
||||||
]
|
]
|
||||||
if model._meta.app_label not in valid_app_labels:
|
if model._meta.app_label not in valid_app_labels:
|
||||||
valid_app_labels.append(model._meta.app_label)
|
valid_app_labels.append(model._meta.app_label)
|
||||||
|
@ -270,7 +276,7 @@ class AdminSite:
|
||||||
if valid_app_labels:
|
if valid_app_labels:
|
||||||
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
|
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(regex, wrap(self.app_index), name='app_list'),
|
re_path(regex, wrap(self.app_index), name='app_list'),
|
||||||
]
|
]
|
||||||
return urlpatterns
|
return urlpatterns
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,50 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.admindocs import views
|
from django.contrib.admindocs import views
|
||||||
|
from django.urls import path, re_path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$',
|
path(
|
||||||
|
'',
|
||||||
views.BaseAdminDocsView.as_view(template_name='admin_doc/index.html'),
|
views.BaseAdminDocsView.as_view(template_name='admin_doc/index.html'),
|
||||||
name='django-admindocs-docroot'),
|
name='django-admindocs-docroot',
|
||||||
url(r'^bookmarklets/$',
|
),
|
||||||
|
path(
|
||||||
|
'bookmarklets/',
|
||||||
views.BookmarkletsView.as_view(),
|
views.BookmarkletsView.as_view(),
|
||||||
name='django-admindocs-bookmarklets'),
|
name='django-admindocs-bookmarklets',
|
||||||
url(r'^tags/$',
|
),
|
||||||
|
path(
|
||||||
|
'tags/',
|
||||||
views.TemplateTagIndexView.as_view(),
|
views.TemplateTagIndexView.as_view(),
|
||||||
name='django-admindocs-tags'),
|
name='django-admindocs-tags',
|
||||||
url(r'^filters/$',
|
),
|
||||||
|
path(
|
||||||
|
'filters/',
|
||||||
views.TemplateFilterIndexView.as_view(),
|
views.TemplateFilterIndexView.as_view(),
|
||||||
name='django-admindocs-filters'),
|
name='django-admindocs-filters',
|
||||||
url(r'^views/$',
|
),
|
||||||
|
path(
|
||||||
|
'views/',
|
||||||
views.ViewIndexView.as_view(),
|
views.ViewIndexView.as_view(),
|
||||||
name='django-admindocs-views-index'),
|
name='django-admindocs-views-index',
|
||||||
url(r'^views/(?P<view>[^/]+)/$',
|
),
|
||||||
|
path(
|
||||||
|
'views/<view>/',
|
||||||
views.ViewDetailView.as_view(),
|
views.ViewDetailView.as_view(),
|
||||||
name='django-admindocs-views-detail'),
|
name='django-admindocs-views-detail',
|
||||||
url(r'^models/$',
|
),
|
||||||
|
path(
|
||||||
|
'models/',
|
||||||
views.ModelIndexView.as_view(),
|
views.ModelIndexView.as_view(),
|
||||||
name='django-admindocs-models-index'),
|
name='django-admindocs-models-index',
|
||||||
url(r'^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$',
|
),
|
||||||
|
re_path(
|
||||||
|
r'^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$',
|
||||||
views.ModelDetailView.as_view(),
|
views.ModelDetailView.as_view(),
|
||||||
name='django-admindocs-models-detail'),
|
name='django-admindocs-models-detail',
|
||||||
url(r'^templates/(?P<template>.*)/$',
|
),
|
||||||
|
path(
|
||||||
|
'templates/<path:template>/',
|
||||||
views.TemplateDetailView.as_view(),
|
views.TemplateDetailView.as_view(),
|
||||||
name='django-admindocs-templates'),
|
name='django-admindocs-templates',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -401,13 +401,12 @@ def extract_views_from_urlpatterns(urlpatterns, base='', namespace=None):
|
||||||
continue
|
continue
|
||||||
views.extend(extract_views_from_urlpatterns(
|
views.extend(extract_views_from_urlpatterns(
|
||||||
patterns,
|
patterns,
|
||||||
base + p.regex.pattern,
|
base + str(p.pattern),
|
||||||
(namespace or []) + (p.namespace and [p.namespace] or [])
|
(namespace or []) + (p.namespace and [p.namespace] or [])
|
||||||
))
|
))
|
||||||
elif hasattr(p, 'callback'):
|
elif hasattr(p, 'callback'):
|
||||||
try:
|
try:
|
||||||
views.append((p.callback, base + p.regex.pattern,
|
views.append((p.callback, base + str(p.pattern), namespace, p.name))
|
||||||
namespace, p.name))
|
|
||||||
except ViewDoesNotExist:
|
except ViewDoesNotExist:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.options import IS_POPUP_VAR
|
from django.contrib.admin.options import IS_POPUP_VAR
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
|
@ -12,7 +11,7 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.db import router, transaction
|
from django.db import router, transaction
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import reverse
|
from django.urls import path, reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import gettext, gettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
|
@ -81,8 +80,8 @@ class UserAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
return [
|
return [
|
||||||
url(
|
path(
|
||||||
r'^(.+)/password/$',
|
'<id>/password/',
|
||||||
self.admin_site.admin_view(self.user_change_password),
|
self.admin_site.admin_view(self.user_change_password),
|
||||||
name='auth_user_password_change',
|
name='auth_user_password_change',
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,19 +3,18 @@
|
||||||
# It is also provided as a convenience to those who want to deploy these URLs
|
# It is also provided as a convenience to those who want to deploy these URLs
|
||||||
# elsewhere.
|
# elsewhere.
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.auth import views
|
from django.contrib.auth import views
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^login/$', views.LoginView.as_view(), name='login'),
|
path('login/', views.LoginView.as_view(), name='login'),
|
||||||
url(r'^logout/$', views.LogoutView.as_view(), name='logout'),
|
path('logout/', views.LogoutView.as_view(), name='logout'),
|
||||||
|
|
||||||
url(r'^password_change/$', views.PasswordChangeView.as_view(), name='password_change'),
|
path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
|
||||||
url(r'^password_change/done/$', views.PasswordChangeDoneView.as_view(), name='password_change_done'),
|
path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),
|
||||||
|
|
||||||
url(r'^password_reset/$', views.PasswordResetView.as_view(), name='password_reset'),
|
path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
|
||||||
url(r'^password_reset/done/$', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
|
path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
|
||||||
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
|
path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
|
||||||
views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
|
path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
|
||||||
url(r'^reset/done/$', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.flatpages import views
|
from django.contrib.flatpages import views
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<url>.*)$', views.flatpage, name='django.contrib.flatpages.views.flatpage'),
|
path('<path:url>', views.flatpage, name='django.contrib.flatpages.views.flatpage'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -81,13 +81,13 @@ def get_warning_for_invalid_pattern(pattern):
|
||||||
"have a prefix string as the first element.".format(pattern)
|
"have a prefix string as the first element.".format(pattern)
|
||||||
)
|
)
|
||||||
elif isinstance(pattern, tuple):
|
elif isinstance(pattern, tuple):
|
||||||
hint = "Try using url() instead of a tuple."
|
hint = "Try using path() instead of a tuple."
|
||||||
else:
|
else:
|
||||||
hint = None
|
hint = None
|
||||||
|
|
||||||
return [Error(
|
return [Error(
|
||||||
"Your URL pattern {!r} is invalid. Ensure that urlpatterns is a list "
|
"Your URL pattern {!r} is invalid. Ensure that urlpatterns is a list "
|
||||||
"of url() instances.".format(pattern),
|
"of path() and/or re_path() instances.".format(pattern),
|
||||||
hint=hint,
|
hint=hint,
|
||||||
id="urls.E004",
|
id="urls.E004",
|
||||||
)]
|
)]
|
||||||
|
|
|
@ -1329,7 +1329,7 @@ def url(parser, token):
|
||||||
|
|
||||||
{% url "url_name" name1=value1 name2=value2 %}
|
{% url "url_name" name1=value1 name2=value2 %}
|
||||||
|
|
||||||
The first argument is a django.conf.urls.url() name. Other arguments are
|
The first argument is a URL pattern name. Other arguments are
|
||||||
space-separated values that will be filled in place of positional and
|
space-separated values that will be filled in place of positional and
|
||||||
keyword arguments in the URL. Don't mix positional and keyword arguments.
|
keyword arguments in the URL. Don't mix positional and keyword arguments.
|
||||||
All arguments for the URL must be present.
|
All arguments for the URL must be present.
|
||||||
|
@ -1337,12 +1337,12 @@ def url(parser, token):
|
||||||
For example, if you have a view ``app_name.views.client_details`` taking
|
For example, if you have a view ``app_name.views.client_details`` taking
|
||||||
the client's id and the corresponding line in a URLconf looks like this::
|
the client's id and the corresponding line in a URLconf looks like this::
|
||||||
|
|
||||||
url('^client/(\d+)/$', views.client_details, name='client-detail-view')
|
path('client/<int:id>/', views.client_details, name='client-detail-view')
|
||||||
|
|
||||||
and this app's URLconf is included into the project's URLconf under some
|
and this app's URLconf is included into the project's URLconf under some
|
||||||
path::
|
path::
|
||||||
|
|
||||||
url('^clients/', include('app_name.urls'))
|
path('clients/', include('app_name.urls'))
|
||||||
|
|
||||||
then in a template you can create a link for a certain client like this::
|
then in a template you can create a link for a certain client like this::
|
||||||
|
|
||||||
|
@ -1359,7 +1359,7 @@ def url(parser, token):
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
if len(bits) < 2:
|
if len(bits) < 2:
|
||||||
raise TemplateSyntaxError("'%s' takes at least one argument, the name of a url()." % bits[0])
|
raise TemplateSyntaxError("'%s' takes at least one argument, a URL pattern name." % bits[0])
|
||||||
viewname = parser.compile_filter(bits[1])
|
viewname = parser.compile_filter(bits[1])
|
||||||
args = []
|
args = []
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
|
@ -3,19 +3,21 @@ from .base import (
|
||||||
is_valid_path, resolve, reverse, reverse_lazy, set_script_prefix,
|
is_valid_path, resolve, reverse, reverse_lazy, set_script_prefix,
|
||||||
set_urlconf, translate_url,
|
set_urlconf, translate_url,
|
||||||
)
|
)
|
||||||
from .conf import include
|
from .conf import include, path, re_path
|
||||||
|
from .converters import register_converter
|
||||||
from .exceptions import NoReverseMatch, Resolver404
|
from .exceptions import NoReverseMatch, Resolver404
|
||||||
from .resolvers import (
|
from .resolvers import (
|
||||||
LocaleRegexProvider, LocaleRegexURLResolver, RegexURLPattern,
|
LocalePrefixPattern, ResolverMatch, URLPattern, URLResolver,
|
||||||
RegexURLResolver, ResolverMatch, get_ns_resolver, get_resolver,
|
get_ns_resolver, get_resolver,
|
||||||
)
|
)
|
||||||
from .utils import get_callable, get_mod_func
|
from .utils import get_callable, get_mod_func
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'LocaleRegexProvider', 'LocaleRegexURLResolver', 'NoReverseMatch',
|
'LocalePrefixPattern', 'NoReverseMatch', 'URLPattern',
|
||||||
'RegexURLPattern', 'RegexURLResolver', 'Resolver404', 'ResolverMatch',
|
'URLResolver', 'Resolver404', 'ResolverMatch', 'clear_script_prefix',
|
||||||
'clear_script_prefix', 'clear_url_caches', 'get_callable', 'get_mod_func',
|
'clear_url_caches', 'get_callable', 'get_mod_func', 'get_ns_resolver',
|
||||||
'get_ns_resolver', 'get_resolver', 'get_script_prefix', 'get_urlconf',
|
'get_resolver', 'get_script_prefix', 'get_urlconf', 'include',
|
||||||
'include', 'is_valid_path', 'resolve', 'reverse', 'reverse_lazy',
|
'is_valid_path', 'path', 're_path', 'register_converter', 'resolve',
|
||||||
'set_script_prefix', 'set_urlconf', 'translate_url',
|
'reverse', 'reverse_lazy', 'set_script_prefix', 'set_urlconf',
|
||||||
|
'translate_url',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
"""Functions for use in URLsconfs."""
|
"""Functions for use in URLsconfs."""
|
||||||
|
from functools import partial
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from .resolvers import LocaleRegexURLResolver
|
from .resolvers import (
|
||||||
|
LocalePrefixPattern, RegexPattern, RoutePattern, URLPattern, URLResolver,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def include(arg, namespace=None):
|
def include(arg, namespace=None):
|
||||||
|
@ -43,8 +46,32 @@ def include(arg, namespace=None):
|
||||||
# testcases will break).
|
# testcases will break).
|
||||||
if isinstance(patterns, (list, tuple)):
|
if isinstance(patterns, (list, tuple)):
|
||||||
for url_pattern in patterns:
|
for url_pattern in patterns:
|
||||||
if isinstance(url_pattern, LocaleRegexURLResolver):
|
pattern = getattr(url_pattern, 'pattern', None)
|
||||||
|
if isinstance(pattern, LocalePrefixPattern):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'Using i18n_patterns in an included URLconf is not allowed.'
|
'Using i18n_patterns in an included URLconf is not allowed.'
|
||||||
)
|
)
|
||||||
return (urlconf_module, app_name, namespace)
|
return (urlconf_module, app_name, namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def _path(route, view, kwargs=None, name=None, Pattern=None):
|
||||||
|
if isinstance(view, (list, tuple)):
|
||||||
|
# For include(...) processing.
|
||||||
|
pattern = Pattern(route, is_endpoint=False)
|
||||||
|
urlconf_module, app_name, namespace = view
|
||||||
|
return URLResolver(
|
||||||
|
pattern,
|
||||||
|
urlconf_module,
|
||||||
|
kwargs,
|
||||||
|
app_name=app_name,
|
||||||
|
namespace=namespace,
|
||||||
|
)
|
||||||
|
elif callable(view):
|
||||||
|
pattern = Pattern(route, name=name, is_endpoint=True)
|
||||||
|
return URLPattern(pattern, view, kwargs, name)
|
||||||
|
else:
|
||||||
|
raise TypeError('view must be a callable or a list/tuple in the case of include().')
|
||||||
|
|
||||||
|
|
||||||
|
path = partial(_path, Pattern=RoutePattern)
|
||||||
|
re_path = partial(_path, Pattern=RegexPattern)
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.utils import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
class IntConverter:
|
||||||
|
regex = '[0-9]+'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class StringConverter:
|
||||||
|
regex = '[^/]+'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class UUIDConverter:
|
||||||
|
regex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return uuid.UUID(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class SlugConverter(StringConverter):
|
||||||
|
regex = '[-a-zA-Z0-9_]+'
|
||||||
|
|
||||||
|
|
||||||
|
class PathConverter(StringConverter):
|
||||||
|
regex = '.+'
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_CONVERTERS = {
|
||||||
|
'int': IntConverter(),
|
||||||
|
'path': PathConverter(),
|
||||||
|
'slug': SlugConverter(),
|
||||||
|
'str': StringConverter(),
|
||||||
|
'uuid': UUIDConverter(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
REGISTERED_CONVERTERS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_converter(converter, type_name):
|
||||||
|
REGISTERED_CONVERTERS[type_name] = converter()
|
||||||
|
get_converters.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache.lru_cache(maxsize=None)
|
||||||
|
def get_converters():
|
||||||
|
converters = {}
|
||||||
|
converters.update(DEFAULT_CONVERTERS)
|
||||||
|
converters.update(REGISTERED_CONVERTERS)
|
||||||
|
return converters
|
||||||
|
|
||||||
|
|
||||||
|
def get_converter(raw_converter):
|
||||||
|
return get_converters()[raw_converter]
|
|
@ -21,6 +21,7 @@ from django.utils.http import RFC3986_SUBDELIMS
|
||||||
from django.utils.regex_helper import normalize
|
from django.utils.regex_helper import normalize
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
|
||||||
|
from .converters import get_converter
|
||||||
from .exceptions import NoReverseMatch, Resolver404
|
from .exceptions import NoReverseMatch, Resolver404
|
||||||
from .utils import get_callable
|
from .utils import get_callable
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ def get_resolver(urlconf=None):
|
||||||
if urlconf is None:
|
if urlconf is None:
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
urlconf = settings.ROOT_URLCONF
|
urlconf = settings.ROOT_URLCONF
|
||||||
return RegexURLResolver(r'^/', urlconf)
|
return URLResolver(RegexPattern(r'^/'), urlconf)
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=None)
|
@functools.lru_cache(maxsize=None)
|
||||||
|
@ -72,11 +73,14 @@ def get_ns_resolver(ns_pattern, resolver):
|
||||||
# Build a namespaced resolver for the given parent URLconf pattern.
|
# Build a namespaced resolver for the given parent URLconf pattern.
|
||||||
# This makes it possible to have captured parameters in the parent
|
# This makes it possible to have captured parameters in the parent
|
||||||
# URLconf pattern.
|
# URLconf pattern.
|
||||||
ns_resolver = RegexURLResolver(ns_pattern, resolver.url_patterns)
|
ns_resolver = URLResolver(RegexPattern(ns_pattern), resolver.url_patterns)
|
||||||
return RegexURLResolver(r'^/', [ns_resolver])
|
return URLResolver(RegexPattern(r'^/'), [ns_resolver])
|
||||||
|
|
||||||
|
|
||||||
class LocaleRegexDescriptor:
|
class LocaleRegexDescriptor:
|
||||||
|
def __init__(self, attr):
|
||||||
|
self.attr = attr
|
||||||
|
|
||||||
def __get__(self, instance, cls=None):
|
def __get__(self, instance, cls=None):
|
||||||
"""
|
"""
|
||||||
Return a compiled regular expression based on the active language.
|
Return a compiled regular expression based on the active language.
|
||||||
|
@ -86,46 +90,23 @@ class LocaleRegexDescriptor:
|
||||||
# As a performance optimization, if the given regex string is a regular
|
# As a performance optimization, if the given regex string is a regular
|
||||||
# string (not a lazily-translated string proxy), compile it once and
|
# string (not a lazily-translated string proxy), compile it once and
|
||||||
# avoid per-language compilation.
|
# avoid per-language compilation.
|
||||||
if isinstance(instance._regex, str):
|
pattern = getattr(instance, self.attr)
|
||||||
instance.__dict__['regex'] = self._compile(instance._regex)
|
if isinstance(pattern, str):
|
||||||
|
instance.__dict__['regex'] = instance._compile(pattern)
|
||||||
return instance.__dict__['regex']
|
return instance.__dict__['regex']
|
||||||
language_code = get_language()
|
language_code = get_language()
|
||||||
if language_code not in instance._regex_dict:
|
if language_code not in instance._regex_dict:
|
||||||
instance._regex_dict[language_code] = self._compile(str(instance._regex))
|
instance._regex_dict[language_code] = instance._compile(str(pattern))
|
||||||
return instance._regex_dict[language_code]
|
return instance._regex_dict[language_code]
|
||||||
|
|
||||||
def _compile(self, regex):
|
|
||||||
"""
|
|
||||||
Compile and return the given regular expression.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return re.compile(regex)
|
|
||||||
except re.error as e:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
'"%s" is not a valid regular expression: %s' % (regex, e)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LocaleRegexProvider:
|
|
||||||
"""
|
|
||||||
A mixin to provide a default regex property which can vary by active
|
|
||||||
language.
|
|
||||||
"""
|
|
||||||
def __init__(self, regex):
|
|
||||||
# regex is either a string representing a regular expression, or a
|
|
||||||
# translatable string (using gettext_lazy) representing a regular
|
|
||||||
# expression.
|
|
||||||
self._regex = regex
|
|
||||||
self._regex_dict = {}
|
|
||||||
|
|
||||||
regex = LocaleRegexDescriptor()
|
|
||||||
|
|
||||||
|
class CheckURLMixin:
|
||||||
def describe(self):
|
def describe(self):
|
||||||
"""
|
"""
|
||||||
Format the URL pattern for display in warning messages.
|
Format the URL pattern for display in warning messages.
|
||||||
"""
|
"""
|
||||||
description = "'{}'".format(self.regex.pattern)
|
description = "'{}'".format(self)
|
||||||
if getattr(self, 'name', False):
|
if self.name:
|
||||||
description += " [name='{}']".format(self.name)
|
description += " [name='{}']".format(self.name)
|
||||||
return description
|
return description
|
||||||
|
|
||||||
|
@ -138,9 +119,9 @@ class LocaleRegexProvider:
|
||||||
# Skip check as it can be useful to start a URL pattern with a slash
|
# Skip check as it can be useful to start a URL pattern with a slash
|
||||||
# when APPEND_SLASH=False.
|
# when APPEND_SLASH=False.
|
||||||
return []
|
return []
|
||||||
if (regex_pattern.startswith('/') or regex_pattern.startswith('^/')) and not regex_pattern.endswith('/'):
|
if any(regex_pattern.startswith(x) for x in ('/', '^/', '^\/')) and not regex_pattern.endswith('/'):
|
||||||
warning = Warning(
|
warning = Warning(
|
||||||
"Your URL pattern {} has a regex beginning with a '/'. Remove this "
|
"Your URL pattern {} has a route beginning with a '/'. Remove this "
|
||||||
"slash as it is unnecessary. If this pattern is targeted in an "
|
"slash as it is unnecessary. If this pattern is targeted in an "
|
||||||
"include(), ensure the include() pattern has a trailing '/'.".format(
|
"include(), ensure the include() pattern has a trailing '/'.".format(
|
||||||
self.describe()
|
self.describe()
|
||||||
|
@ -152,37 +133,17 @@ class LocaleRegexProvider:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class RegexURLPattern(LocaleRegexProvider):
|
class RegexPattern(CheckURLMixin):
|
||||||
def __init__(self, regex, callback, default_args=None, name=None):
|
regex = LocaleRegexDescriptor('_regex')
|
||||||
LocaleRegexProvider.__init__(self, regex)
|
|
||||||
self.callback = callback # the view
|
def __init__(self, regex, name=None, is_endpoint=False):
|
||||||
self.default_args = default_args or {}
|
self._regex = regex
|
||||||
|
self._regex_dict = {}
|
||||||
|
self._is_endpoint = is_endpoint
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.converters = {}
|
||||||
|
|
||||||
def __repr__(self):
|
def match(self, path):
|
||||||
return '<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern)
|
|
||||||
|
|
||||||
def check(self):
|
|
||||||
warnings = self._check_pattern_name()
|
|
||||||
if not warnings:
|
|
||||||
warnings = self._check_pattern_startswith_slash()
|
|
||||||
return warnings
|
|
||||||
|
|
||||||
def _check_pattern_name(self):
|
|
||||||
"""
|
|
||||||
Check that the pattern name does not contain a colon.
|
|
||||||
"""
|
|
||||||
if self.name is not None and ":" in self.name:
|
|
||||||
warning = Warning(
|
|
||||||
"Your URL pattern {} has a name including a ':'. Remove the colon, to "
|
|
||||||
"avoid ambiguous namespace references.".format(self.describe()),
|
|
||||||
id="urls.W003",
|
|
||||||
)
|
|
||||||
return [warning]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def resolve(self, path):
|
|
||||||
match = self.regex.search(path)
|
match = self.regex.search(path)
|
||||||
if match:
|
if match:
|
||||||
# If there are any named groups, use those as kwargs, ignoring
|
# If there are any named groups, use those as kwargs, ignoring
|
||||||
|
@ -190,9 +151,190 @@ class RegexURLPattern(LocaleRegexProvider):
|
||||||
# positional arguments.
|
# positional arguments.
|
||||||
kwargs = match.groupdict()
|
kwargs = match.groupdict()
|
||||||
args = () if kwargs else match.groups()
|
args = () if kwargs else match.groups()
|
||||||
# In both cases, pass any extra_kwargs as **kwargs.
|
return path[match.end():], args, kwargs
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
warnings = []
|
||||||
|
warnings.extend(self._check_pattern_startswith_slash())
|
||||||
|
if not self._is_endpoint:
|
||||||
|
warnings.extend(self._check_include_trailing_dollar())
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
def _check_include_trailing_dollar(self):
|
||||||
|
regex_pattern = self.regex.pattern
|
||||||
|
if regex_pattern.endswith('$') and not regex_pattern.endswith(r'\$'):
|
||||||
|
return [Warning(
|
||||||
|
"Your URL pattern {} uses include with a route ending with a '$'. "
|
||||||
|
"Remove the dollar from the route to avoid problems including "
|
||||||
|
"URLs.".format(self.describe()),
|
||||||
|
id='urls.W001',
|
||||||
|
)]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _compile(self, regex):
|
||||||
|
"""Compile and return the given regular expression."""
|
||||||
|
try:
|
||||||
|
return re.compile(regex)
|
||||||
|
except re.error as e:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'"%s" is not a valid regular expression: %s' % (regex, e)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._regex
|
||||||
|
|
||||||
|
|
||||||
|
_PATH_PARAMETER_COMPONENT_RE = re.compile(
|
||||||
|
'<(?:(?P<converter>[^>:]+):)?(?P<parameter>\w+)>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _route_to_regex(route, is_endpoint=False):
|
||||||
|
"""
|
||||||
|
Convert a path pattern into a regular expression. Return the regular
|
||||||
|
expression and a dictionary mapping the capture names to the converters.
|
||||||
|
For example, 'foo/<int:pk>' returns '^foo\\/(?P<pk>[0-9]+)'
|
||||||
|
and {'pk': <django.urls.converters.IntConverter>}.
|
||||||
|
"""
|
||||||
|
original_route = route
|
||||||
|
parts = ['^']
|
||||||
|
converters = {}
|
||||||
|
while True:
|
||||||
|
match = _PATH_PARAMETER_COMPONENT_RE.search(route)
|
||||||
|
if not match:
|
||||||
|
parts.append(re.escape(route))
|
||||||
|
break
|
||||||
|
parts.append(re.escape(route[:match.start()]))
|
||||||
|
route = route[match.end():]
|
||||||
|
parameter = match.group('parameter')
|
||||||
|
if not parameter.isidentifier():
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"URL route '%s' uses parameter name %r which isn't a valid "
|
||||||
|
"Python identifier." % (original_route, parameter)
|
||||||
|
)
|
||||||
|
raw_converter = match.group('converter')
|
||||||
|
if raw_converter is None:
|
||||||
|
# If a converter isn't specified, the default is `str`.
|
||||||
|
raw_converter = 'str'
|
||||||
|
try:
|
||||||
|
converter = get_converter(raw_converter)
|
||||||
|
except KeyError as e:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"URL route '%s' uses invalid converter %s." % (original_route, e)
|
||||||
|
)
|
||||||
|
converters[parameter] = converter
|
||||||
|
parts.append('(?P<' + parameter + '>' + converter.regex + ')')
|
||||||
|
if is_endpoint:
|
||||||
|
parts.append('$')
|
||||||
|
return ''.join(parts), converters
|
||||||
|
|
||||||
|
|
||||||
|
class RoutePattern(CheckURLMixin):
|
||||||
|
regex = LocaleRegexDescriptor('_route')
|
||||||
|
|
||||||
|
def __init__(self, route, name=None, is_endpoint=False):
|
||||||
|
self._route = route
|
||||||
|
self._regex_dict = {}
|
||||||
|
self._is_endpoint = is_endpoint
|
||||||
|
self.name = name
|
||||||
|
self.converters = _route_to_regex(str(route), is_endpoint)[1]
|
||||||
|
|
||||||
|
def match(self, path):
|
||||||
|
match = self.regex.search(path)
|
||||||
|
if match:
|
||||||
|
# RoutePattern doesn't allow non-named groups so args are ignored.
|
||||||
|
kwargs = match.groupdict()
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
converter = self.converters[key]
|
||||||
|
try:
|
||||||
|
kwargs[key] = converter.to_python(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
return path[match.end():], (), kwargs
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return self._check_pattern_startswith_slash()
|
||||||
|
|
||||||
|
def _compile(self, route):
|
||||||
|
return re.compile(_route_to_regex(route, self._is_endpoint)[0])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._route
|
||||||
|
|
||||||
|
|
||||||
|
class LocalePrefixPattern:
|
||||||
|
def __init__(self, prefix_default_language=True):
|
||||||
|
self.prefix_default_language = prefix_default_language
|
||||||
|
self.converters = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def regex(self):
|
||||||
|
# This is only used by reverse() and cached in _reverse_dict.
|
||||||
|
return re.compile(self.language_prefix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def language_prefix(self):
|
||||||
|
language_code = get_language() or settings.LANGUAGE_CODE
|
||||||
|
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return '%s/' % language_code
|
||||||
|
|
||||||
|
def match(self, path):
|
||||||
|
language_prefix = self.language_prefix
|
||||||
|
if path.startswith(language_prefix):
|
||||||
|
return path[len(language_prefix):], (), {}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
return "'{}'".format(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.language_prefix
|
||||||
|
|
||||||
|
|
||||||
|
class URLPattern:
|
||||||
|
def __init__(self, pattern, callback, default_args=None, name=None):
|
||||||
|
self.pattern = pattern
|
||||||
|
self.callback = callback # the view
|
||||||
|
self.default_args = default_args or {}
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, self.pattern.describe())
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
warnings = self._check_pattern_name()
|
||||||
|
warnings.extend(self.pattern.check())
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
def _check_pattern_name(self):
|
||||||
|
"""
|
||||||
|
Check that the pattern name does not contain a colon.
|
||||||
|
"""
|
||||||
|
if self.pattern.name is not None and ":" in self.pattern.name:
|
||||||
|
warning = Warning(
|
||||||
|
"Your URL pattern {} has a name including a ':'. Remove the colon, to "
|
||||||
|
"avoid ambiguous namespace references.".format(self.pattern.describe()),
|
||||||
|
id="urls.W003",
|
||||||
|
)
|
||||||
|
return [warning]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve(self, path):
|
||||||
|
match = self.pattern.match(path)
|
||||||
|
if match:
|
||||||
|
new_path, args, kwargs = match
|
||||||
|
# Pass any extra_kwargs as **kwargs.
|
||||||
kwargs.update(self.default_args)
|
kwargs.update(self.default_args)
|
||||||
return ResolverMatch(self.callback, args, kwargs, self.name)
|
return ResolverMatch(self.callback, args, kwargs, self.pattern.name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def lookup_str(self):
|
def lookup_str(self):
|
||||||
|
@ -210,9 +352,9 @@ class RegexURLPattern(LocaleRegexProvider):
|
||||||
return callback.__module__ + "." + callback.__qualname__
|
return callback.__module__ + "." + callback.__qualname__
|
||||||
|
|
||||||
|
|
||||||
class RegexURLResolver(LocaleRegexProvider):
|
class URLResolver:
|
||||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||||
LocaleRegexProvider.__init__(self, regex)
|
self.pattern = pattern
|
||||||
# urlconf_name is the dotted Python path to the module defining
|
# urlconf_name is the dotted Python path to the module defining
|
||||||
# urlpatterns. It may also be an object with an urlpatterns attribute
|
# urlpatterns. It may also be an object with an urlpatterns attribute
|
||||||
# or urlpatterns itself.
|
# or urlpatterns itself.
|
||||||
|
@ -238,33 +380,17 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
urlconf_repr = repr(self.urlconf_name)
|
urlconf_repr = repr(self.urlconf_name)
|
||||||
return '<%s %s (%s:%s) %s>' % (
|
return '<%s %s (%s:%s) %s>' % (
|
||||||
self.__class__.__name__, urlconf_repr, self.app_name,
|
self.__class__.__name__, urlconf_repr, self.app_name,
|
||||||
self.namespace, self.regex.pattern,
|
self.namespace, self.pattern.describe(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
warnings = self._check_include_trailing_dollar()
|
warnings = []
|
||||||
for pattern in self.url_patterns:
|
for pattern in self.url_patterns:
|
||||||
warnings.extend(check_resolver(pattern))
|
warnings.extend(check_resolver(pattern))
|
||||||
if not warnings:
|
if not warnings:
|
||||||
warnings = self._check_pattern_startswith_slash()
|
warnings = self.pattern.check()
|
||||||
return warnings
|
return warnings
|
||||||
|
|
||||||
def _check_include_trailing_dollar(self):
|
|
||||||
"""
|
|
||||||
Check that include is not used with a regex ending with a dollar.
|
|
||||||
"""
|
|
||||||
regex_pattern = self.regex.pattern
|
|
||||||
if regex_pattern.endswith('$') and not regex_pattern.endswith(r'\$'):
|
|
||||||
warning = Warning(
|
|
||||||
"Your URL pattern {} uses include with a regex ending with a '$'. "
|
|
||||||
"Remove the dollar from the regex to avoid problems including "
|
|
||||||
"URLs.".format(self.describe()),
|
|
||||||
id="urls.W001",
|
|
||||||
)
|
|
||||||
return [warning]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _populate(self):
|
def _populate(self):
|
||||||
# Short-circuit if called recursively in this thread to prevent
|
# Short-circuit if called recursively in this thread to prevent
|
||||||
# infinite recursion. Concurrent threads may call this at the same
|
# infinite recursion. Concurrent threads may call this at the same
|
||||||
|
@ -277,46 +403,51 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
namespaces = {}
|
namespaces = {}
|
||||||
apps = {}
|
apps = {}
|
||||||
language_code = get_language()
|
language_code = get_language()
|
||||||
for pattern in reversed(self.url_patterns):
|
try:
|
||||||
if isinstance(pattern, RegexURLPattern):
|
for url_pattern in reversed(self.url_patterns):
|
||||||
self._callback_strs.add(pattern.lookup_str)
|
p_pattern = url_pattern.pattern.regex.pattern
|
||||||
p_pattern = pattern.regex.pattern
|
|
||||||
if p_pattern.startswith('^'):
|
if p_pattern.startswith('^'):
|
||||||
p_pattern = p_pattern[1:]
|
p_pattern = p_pattern[1:]
|
||||||
if isinstance(pattern, RegexURLResolver):
|
if isinstance(url_pattern, URLPattern):
|
||||||
if pattern.namespace:
|
self._callback_strs.add(url_pattern.lookup_str)
|
||||||
namespaces[pattern.namespace] = (p_pattern, pattern)
|
bits = normalize(url_pattern.pattern.regex.pattern)
|
||||||
if pattern.app_name:
|
lookups.appendlist(
|
||||||
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
|
url_pattern.callback,
|
||||||
|
(bits, p_pattern, url_pattern.default_args, url_pattern.pattern.converters)
|
||||||
|
)
|
||||||
|
if url_pattern.name is not None:
|
||||||
|
lookups.appendlist(
|
||||||
|
url_pattern.name,
|
||||||
|
(bits, p_pattern, url_pattern.default_args, url_pattern.pattern.converters)
|
||||||
|
)
|
||||||
|
else: # url_pattern is a URLResolver.
|
||||||
|
url_pattern._populate()
|
||||||
|
if url_pattern.app_name:
|
||||||
|
apps.setdefault(url_pattern.app_name, []).append(url_pattern.namespace)
|
||||||
|
namespaces[url_pattern.namespace] = (p_pattern, url_pattern)
|
||||||
else:
|
else:
|
||||||
parent_pat = pattern.regex.pattern
|
for name in url_pattern.reverse_dict:
|
||||||
for name in pattern.reverse_dict:
|
for matches, pat, defaults, converters in url_pattern.reverse_dict.getlist(name):
|
||||||
for matches, pat, defaults in pattern.reverse_dict.getlist(name):
|
new_matches = normalize(p_pattern + pat)
|
||||||
new_matches = normalize(parent_pat + pat)
|
|
||||||
lookups.appendlist(
|
lookups.appendlist(
|
||||||
name,
|
name,
|
||||||
(
|
(
|
||||||
new_matches,
|
new_matches,
|
||||||
p_pattern + pat,
|
p_pattern + pat,
|
||||||
dict(defaults, **pattern.default_kwargs),
|
dict(defaults, **url_pattern.default_kwargs),
|
||||||
|
dict(self.pattern.converters, **converters)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
|
for namespace, (prefix, sub_pattern) in url_pattern.namespace_dict.items():
|
||||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||||
for app_name, namespace_list in pattern.app_dict.items():
|
for app_name, namespace_list in url_pattern.app_dict.items():
|
||||||
apps.setdefault(app_name, []).extend(namespace_list)
|
apps.setdefault(app_name, []).extend(namespace_list)
|
||||||
if not getattr(pattern._local, 'populating', False):
|
self._callback_strs.update(url_pattern._callback_strs)
|
||||||
pattern._populate()
|
|
||||||
self._callback_strs.update(pattern._callback_strs)
|
|
||||||
else:
|
|
||||||
bits = normalize(p_pattern)
|
|
||||||
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
|
|
||||||
if pattern.name is not None:
|
|
||||||
lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
|
|
||||||
self._reverse_dict[language_code] = lookups
|
|
||||||
self._namespace_dict[language_code] = namespaces
|
self._namespace_dict[language_code] = namespaces
|
||||||
self._app_dict[language_code] = apps
|
self._app_dict[language_code] = apps
|
||||||
|
self._reverse_dict[language_code] = lookups
|
||||||
self._populated = True
|
self._populated = True
|
||||||
|
finally:
|
||||||
self._local.populating = False
|
self._local.populating = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -348,9 +479,9 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
def resolve(self, path):
|
def resolve(self, path):
|
||||||
path = str(path) # path may be a reverse_lazy object
|
path = str(path) # path may be a reverse_lazy object
|
||||||
tried = []
|
tried = []
|
||||||
match = self.regex.search(path)
|
match = self.pattern.match(path)
|
||||||
if match:
|
if match:
|
||||||
new_path = path[match.end():]
|
new_path, args, kwargs = match
|
||||||
for pattern in self.url_patterns:
|
for pattern in self.url_patterns:
|
||||||
try:
|
try:
|
||||||
sub_match = pattern.resolve(new_path)
|
sub_match = pattern.resolve(new_path)
|
||||||
|
@ -363,15 +494,14 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
else:
|
else:
|
||||||
if sub_match:
|
if sub_match:
|
||||||
# Merge captured arguments in match with submatch
|
# Merge captured arguments in match with submatch
|
||||||
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
|
sub_match_dict = dict(kwargs, **self.default_kwargs)
|
||||||
|
# Update the sub_match_dict with the kwargs from the sub_match.
|
||||||
sub_match_dict.update(sub_match.kwargs)
|
sub_match_dict.update(sub_match.kwargs)
|
||||||
|
|
||||||
# If there are *any* named groups, ignore all non-named groups.
|
# If there are *any* named groups, ignore all non-named groups.
|
||||||
# Otherwise, pass all non-named arguments as positional arguments.
|
# Otherwise, pass all non-named arguments as positional arguments.
|
||||||
sub_match_args = sub_match.args
|
sub_match_args = sub_match.args
|
||||||
if not sub_match_dict:
|
if not sub_match_dict:
|
||||||
sub_match_args = match.groups() + sub_match.args
|
sub_match_args = args + sub_match.args
|
||||||
|
|
||||||
return ResolverMatch(
|
return ResolverMatch(
|
||||||
sub_match.func,
|
sub_match.func,
|
||||||
sub_match_args,
|
sub_match_args,
|
||||||
|
@ -421,20 +551,18 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
||||||
if args and kwargs:
|
if args and kwargs:
|
||||||
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
||||||
text_args = [str(v) for v in args]
|
|
||||||
text_kwargs = {k: str(v) for (k, v) in kwargs.items()}
|
|
||||||
|
|
||||||
if not self._populated:
|
if not self._populated:
|
||||||
self._populate()
|
self._populate()
|
||||||
|
|
||||||
possibilities = self.reverse_dict.getlist(lookup_view)
|
possibilities = self.reverse_dict.getlist(lookup_view)
|
||||||
|
|
||||||
for possibility, pattern, defaults in possibilities:
|
for possibility, pattern, defaults, converters in possibilities:
|
||||||
for result, params in possibility:
|
for result, params in possibility:
|
||||||
if args:
|
if args:
|
||||||
if len(args) != len(params):
|
if len(args) != len(params):
|
||||||
continue
|
continue
|
||||||
candidate_subs = dict(zip(params, text_args))
|
candidate_subs = dict(zip(params, args))
|
||||||
else:
|
else:
|
||||||
if set(kwargs).symmetric_difference(params).difference(defaults):
|
if set(kwargs).symmetric_difference(params).difference(defaults):
|
||||||
continue
|
continue
|
||||||
|
@ -445,16 +573,23 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
break
|
break
|
||||||
if not matches:
|
if not matches:
|
||||||
continue
|
continue
|
||||||
candidate_subs = text_kwargs
|
candidate_subs = kwargs
|
||||||
|
# Convert the candidate subs to text using Converter.to_url().
|
||||||
|
text_candidate_subs = {}
|
||||||
|
for k, v in candidate_subs.items():
|
||||||
|
if k in converters:
|
||||||
|
text_candidate_subs[k] = converters[k].to_url(v)
|
||||||
|
else:
|
||||||
|
text_candidate_subs[k] = str(v)
|
||||||
# WSGI provides decoded URLs, without %xx escapes, and the URL
|
# WSGI provides decoded URLs, without %xx escapes, and the URL
|
||||||
# resolver operates on such URLs. First substitute arguments
|
# resolver operates on such URLs. First substitute arguments
|
||||||
# without quoting to build a decoded URL and look for a match.
|
# without quoting to build a decoded URL and look for a match.
|
||||||
# Then, if we have a match, redo the substitution with quoted
|
# Then, if we have a match, redo the substitution with quoted
|
||||||
# arguments in order to return a properly encoded URL.
|
# arguments in order to return a properly encoded URL.
|
||||||
candidate_pat = _prefix.replace('%', '%%') + result
|
candidate_pat = _prefix.replace('%', '%%') + result
|
||||||
if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % candidate_subs):
|
if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % text_candidate_subs):
|
||||||
# safe characters from `pchar` definition of RFC 3986
|
# safe characters from `pchar` definition of RFC 3986
|
||||||
url = quote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@')
|
url = quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@')
|
||||||
# Don't allow construction of scheme relative urls.
|
# Don't allow construction of scheme relative urls.
|
||||||
if url.startswith('//'):
|
if url.startswith('//'):
|
||||||
url = '/%%2F%s' % url[2:]
|
url = '/%%2F%s' % url[2:]
|
||||||
|
@ -468,7 +603,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
else:
|
else:
|
||||||
lookup_view_s = lookup_view
|
lookup_view_s = lookup_view
|
||||||
|
|
||||||
patterns = [pattern for (possibility, pattern, defaults) in possibilities]
|
patterns = [pattern for (_, pattern, _, _) in possibilities]
|
||||||
if patterns:
|
if patterns:
|
||||||
if args:
|
if args:
|
||||||
arg_msg = "arguments '%s'" % (args,)
|
arg_msg = "arguments '%s'" % (args,)
|
||||||
|
@ -486,29 +621,3 @@ class RegexURLResolver(LocaleRegexProvider):
|
||||||
"a valid view function or pattern name." % {'view': lookup_view_s}
|
"a valid view function or pattern name." % {'view': lookup_view_s}
|
||||||
)
|
)
|
||||||
raise NoReverseMatch(msg)
|
raise NoReverseMatch(msg)
|
||||||
|
|
||||||
|
|
||||||
class LocaleRegexURLResolver(RegexURLResolver):
|
|
||||||
"""
|
|
||||||
A URL resolver that always matches the active language code as URL prefix.
|
|
||||||
|
|
||||||
Rather than taking a regex argument, we just override the ``regex``
|
|
||||||
function to always return the active language-code as regex.
|
|
||||||
"""
|
|
||||||
def __init__(
|
|
||||||
self, urlconf_name, default_kwargs=None, app_name=None, namespace=None,
|
|
||||||
prefix_default_language=True,
|
|
||||||
):
|
|
||||||
super().__init__(None, urlconf_name, default_kwargs, app_name, namespace)
|
|
||||||
self.prefix_default_language = prefix_default_language
|
|
||||||
|
|
||||||
@property
|
|
||||||
def regex(self):
|
|
||||||
language_code = get_language() or settings.LANGUAGE_CODE
|
|
||||||
if language_code not in self._regex_dict:
|
|
||||||
if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
|
|
||||||
regex_string = ''
|
|
||||||
else:
|
|
||||||
regex_string = '^%s/' % language_code
|
|
||||||
self._regex_dict[language_code] = re.compile(regex_string)
|
|
||||||
return self._regex_dict[language_code]
|
|
||||||
|
|
|
@ -612,7 +612,7 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_
|
||||||
(only year is mandatory). Raise a 404 for an invalid date.
|
(only year is mandatory). Raise a 404 for an invalid date.
|
||||||
"""
|
"""
|
||||||
format = year_format + delim + month_format + delim + day_format
|
format = year_format + delim + month_format + delim + day_format
|
||||||
datestr = year + delim + month + delim + day
|
datestr = str(year) + delim + str(month) + delim + str(day)
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(datestr, format).date()
|
return datetime.datetime.strptime(datestr, format).date()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
{% for pattern in urlpatterns %}
|
{% for pattern in urlpatterns %}
|
||||||
<li>
|
<li>
|
||||||
{% for pat in pattern %}
|
{% for pat in pattern %}
|
||||||
{{ pat.regex.pattern }}
|
{{ pat.pattern }}
|
||||||
{% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %}
|
{% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -147,7 +147,7 @@ details on these changes.
|
||||||
``django.utils.feedgenerator.RssFeed`` will be removed in favor of
|
``django.utils.feedgenerator.RssFeed`` will be removed in favor of
|
||||||
``content_type``.
|
``content_type``.
|
||||||
|
|
||||||
* The ``app_name`` argument to :func:`~django.conf.urls.include()` will be
|
* The ``app_name`` argument to ``django.conf.urls.include()`` will be
|
||||||
removed.
|
removed.
|
||||||
|
|
||||||
* Support for passing a 3-tuple as the first argument to ``include()`` will
|
* Support for passing a 3-tuple as the first argument to ``include()`` will
|
||||||
|
@ -786,10 +786,9 @@ details on these changes.
|
||||||
``django.contrib.gis.utils`` will be removed.
|
``django.contrib.gis.utils`` will be removed.
|
||||||
|
|
||||||
* ``django.conf.urls.defaults`` will be removed. The functions
|
* ``django.conf.urls.defaults`` will be removed. The functions
|
||||||
:func:`~django.conf.urls.include`, ``patterns()`` and
|
``include()``, ``patterns()``, and ``url()``, plus
|
||||||
:func:`~django.conf.urls.url` plus :data:`~django.conf.urls.handler404`,
|
:data:`~django.conf.urls.handler404` and :data:`~django.conf.urls.handler500`
|
||||||
:data:`~django.conf.urls.handler500`, are now available through
|
are now available through ``django.conf.urls``.
|
||||||
:mod:`django.conf.urls` .
|
|
||||||
|
|
||||||
* The functions ``setup_environ()`` and ``execute_manager()`` will be removed
|
* The functions ``setup_environ()`` and ``execute_manager()`` will be removed
|
||||||
from :mod:`django.core.management`. This also means that the old (pre-1.4)
|
from :mod:`django.core.management`. This also means that the old (pre-1.4)
|
||||||
|
|
|
@ -191,31 +191,30 @@ example above:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: mysite/news/urls.py
|
:filename: mysite/news/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^articles/([0-9]{4})/$', views.year_archive),
|
path('articles/<int:year>/', views.year_archive),
|
||||||
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
|
path('articles/<int:year>/<int:month>/', views.month_archive),
|
||||||
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
|
path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
The code above maps URLs, as simple :ref:`regular expressions <regex-howto>`,
|
The code above maps URL paths to Python callback functions ("views"). The path
|
||||||
to the location of Python callback functions ("views"). The regular expressions
|
strings use parameter tags to "capture" values from the URLs. When a user
|
||||||
use parenthesis to "capture" values from the URLs. When a user requests a page,
|
requests a page, Django runs through each path, in order, and stops at the
|
||||||
Django runs through each pattern, in order, and stops at the first one that
|
first one that matches the requested URL. (If none of them matches, Django
|
||||||
matches the requested URL. (If none of them matches, Django calls a
|
calls a special-case 404 view.) This is blazingly fast, because the paths are
|
||||||
special-case 404 view.) This is blazingly fast, because the regular expressions
|
compiled into regular expressions at load time.
|
||||||
are compiled at load time.
|
|
||||||
|
|
||||||
Once one of the regexes matches, Django calls the given view, which is a Python
|
Once one of the URL patterns matches, Django calls the given view, which is a
|
||||||
function. Each view gets passed a request object -- which contains request
|
Python function. Each view gets passed a request object -- which contains
|
||||||
metadata -- and the values captured in the regex.
|
request metadata -- and the values captured in the pattern.
|
||||||
|
|
||||||
For example, if a user requested the URL "/articles/2005/05/39323/", Django
|
For example, if a user requested the URL "/articles/2005/05/39323/", Django
|
||||||
would call the function ``news.views.article_detail(request,
|
would call the function ``news.views.article_detail(request,
|
||||||
'2005', '05', '39323')``.
|
year=2005, month=5, pk=39323)``.
|
||||||
|
|
||||||
Write your views
|
Write your views
|
||||||
================
|
================
|
||||||
|
|
|
@ -165,7 +165,7 @@ this. For a small app like polls, this process isn't too difficult.
|
||||||
|
|
||||||
2. Include the polls URLconf in your project urls.py like this::
|
2. Include the polls URLconf in your project urls.py like this::
|
||||||
|
|
||||||
url(r'^polls/', include('polls.urls')),
|
path('polls/', include('polls.urls')),
|
||||||
|
|
||||||
3. Run `python manage.py migrate` to create the polls models.
|
3. Run `python manage.py migrate` to create the polls models.
|
||||||
|
|
||||||
|
|
|
@ -274,55 +274,45 @@ In the ``polls/urls.py`` file include the following code:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
]
|
]
|
||||||
|
|
||||||
The next step is to point the root URLconf at the ``polls.urls`` module. In
|
The next step is to point the root URLconf at the ``polls.urls`` module. In
|
||||||
``mysite/urls.py``, add an import for ``django.conf.urls.include`` and insert
|
``mysite/urls.py``, add an import for ``django.urls.include`` and insert an
|
||||||
an :func:`~django.conf.urls.include` in the ``urlpatterns`` list, so you have:
|
:func:`~django.urls.include` in the ``urlpatterns`` list, so you have:
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: mysite/urls.py
|
:filename: mysite/urls.py
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^polls/', include('polls.urls')),
|
path('polls/', include('polls.urls')),
|
||||||
url(r'^admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
The :func:`~django.conf.urls.include` function allows referencing other
|
The :func:`~django.urls.include` function allows referencing other URLconfs.
|
||||||
URLconfs. Note that the regular expressions for the
|
Whenever Django encounters :func:`~django.urls.include`, it chops off whatever
|
||||||
:func:`~django.conf.urls.include` function doesn't have a ``$`` (end-of-string
|
part of the URL matched up to that point and sends the remaining string to the
|
||||||
match character) but rather a trailing slash. Whenever Django encounters
|
included URLconf for further processing.
|
||||||
:func:`~django.conf.urls.include`, it chops off whatever part of the URL
|
|
||||||
matched up to that point and sends the remaining string to the included URLconf
|
|
||||||
for further processing.
|
|
||||||
|
|
||||||
The idea behind :func:`~django.conf.urls.include` is to make it easy to
|
The idea behind :func:`~django.urls.include` is to make it easy to
|
||||||
plug-and-play URLs. Since polls are in their own URLconf
|
plug-and-play URLs. Since polls are in their own URLconf
|
||||||
(``polls/urls.py``), they can be placed under "/polls/", or under
|
(``polls/urls.py``), they can be placed under "/polls/", or under
|
||||||
"/fun_polls/", or under "/content/polls/", or any other path root, and the
|
"/fun_polls/", or under "/content/polls/", or any other path root, and the
|
||||||
app will still work.
|
app will still work.
|
||||||
|
|
||||||
.. admonition:: When to use :func:`~django.conf.urls.include()`
|
.. admonition:: When to use :func:`~django.urls.include()`
|
||||||
|
|
||||||
You should always use ``include()`` when you include other URL patterns.
|
You should always use ``include()`` when you include other URL patterns.
|
||||||
``admin.site.urls`` is the only exception to this.
|
``admin.site.urls`` is the only exception to this.
|
||||||
|
|
||||||
.. admonition:: Doesn't match what you see?
|
|
||||||
|
|
||||||
If you're seeing ``include(admin.site.urls)`` instead of just
|
|
||||||
``admin.site.urls``, you're probably using a version of Django that
|
|
||||||
doesn't match this tutorial version. You'll want to either switch to the
|
|
||||||
older tutorial or the newer Django version.
|
|
||||||
|
|
||||||
You have now wired an ``index`` view into the URLconf. Lets verify it's
|
You have now wired an ``index`` view into the URLconf. Lets verify it's
|
||||||
working, run the following command:
|
working, run the following command:
|
||||||
|
|
||||||
|
@ -334,56 +324,39 @@ Go to http://localhost:8000/polls/ in your browser, and you should see the
|
||||||
text "*Hello, world. You're at the polls index.*", which you defined in the
|
text "*Hello, world. You're at the polls index.*", which you defined in the
|
||||||
``index`` view.
|
``index`` view.
|
||||||
|
|
||||||
The :func:`~django.conf.urls.url` function is passed four arguments, two
|
The :func:`~django.urls.path` function is passed four arguments, two required:
|
||||||
required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``.
|
``route`` and ``view``, and two optional: ``kwargs``, and ``name``.
|
||||||
At this point, it's worth reviewing what these arguments are for.
|
At this point, it's worth reviewing what these arguments are for.
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: regex
|
:func:`~django.urls.path` argument: ``route``
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
The term "regex" is a commonly used short form meaning "regular expression",
|
``route`` is a string that contains a URL pattern. When processing a request,
|
||||||
which is a syntax for matching patterns in strings, or in this case, url
|
Django starts at the first pattern in ``urlpatterns`` and makes its way down
|
||||||
patterns. Django starts at the first regular expression and makes its way down
|
the list, comparing the requested URL against each pattern until it finds one
|
||||||
the list, comparing the requested URL against each regular expression until it
|
that matches.
|
||||||
finds one that matches.
|
|
||||||
|
|
||||||
Note that these regular expressions do not search GET and POST parameters, or
|
Patterns don't search GET and POST parameters, or the domain name. For example,
|
||||||
the domain name. For example, in a request to
|
in a request to ``https://www.example.com/myapp/``, the URLconf will look for
|
||||||
``https://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a
|
``myapp/``. In a request to ``https://www.example.com/myapp/?page=3``, the
|
||||||
request to ``https://www.example.com/myapp/?page=3``, the URLconf will also
|
URLconf will also look for ``myapp/``.
|
||||||
look for ``myapp/``.
|
|
||||||
|
|
||||||
If you need help with regular expressions, see `Wikipedia's entry`_ and the
|
:func:`~django.urls.path` argument: ``view``
|
||||||
documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
|
|
||||||
Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however,
|
|
||||||
you don't need to be an expert on regular expressions, as you really only need
|
|
||||||
to know how to capture simple patterns. In fact, complex regexes can have poor
|
|
||||||
lookup performance, so you probably shouldn't rely on the full power of regexes.
|
|
||||||
|
|
||||||
Finally, a performance note: these regular expressions are compiled the first
|
|
||||||
time the URLconf module is loaded. They're super fast (as long as the lookups
|
|
||||||
aren't too complex as noted above).
|
|
||||||
|
|
||||||
.. _Wikipedia's entry: https://en.wikipedia.org/wiki/Regular_expression
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: view
|
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
When Django finds a regular expression match, Django calls the specified view
|
When Django finds a matching pattern, it calls the specified view function with
|
||||||
function, with an :class:`~django.http.HttpRequest` object as the first
|
an :class:`~django.http.HttpRequest` object as the first argument and any
|
||||||
argument and any “captured” values from the regular expression as other
|
"captured" values from the route as keyword arguments. We'll give an example
|
||||||
arguments. If the regex uses simple captures, values are passed as positional
|
of this in a bit.
|
||||||
arguments; if it uses named captures, values are passed as keyword arguments.
|
|
||||||
We'll give an example of this in a bit.
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: kwargs
|
:func:`~django.urls.path` argument: ``kwargs``
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
Arbitrary keyword arguments can be passed in a dictionary to the target view. We
|
Arbitrary keyword arguments can be passed in a dictionary to the target view. We
|
||||||
aren't going to use this feature of Django in the tutorial.
|
aren't going to use this feature of Django in the tutorial.
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: name
|
:func:`~django.urls.path` argument: ``name``
|
||||||
---------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
Naming your URL lets you refer to it unambiguously from elsewhere in Django,
|
Naming your URL lets you refer to it unambiguously from elsewhere in Django,
|
||||||
especially from within templates. This powerful feature allows you to make
|
especially from within templates. This powerful feature allows you to make
|
||||||
|
|
|
@ -53,10 +53,10 @@ A URL pattern is simply the general form of a URL - for example:
|
||||||
``/newsarchive/<year>/<month>/``.
|
``/newsarchive/<year>/<month>/``.
|
||||||
|
|
||||||
To get from a URL to a view, Django uses what are known as 'URLconfs'. A
|
To get from a URL to a view, Django uses what are known as 'URLconfs'. A
|
||||||
URLconf maps URL patterns (described as regular expressions) to views.
|
URLconf maps URL patterns to views.
|
||||||
|
|
||||||
This tutorial provides basic instruction in the use of URLconfs, and you can
|
This tutorial provides basic instruction in the use of URLconfs, and you can
|
||||||
refer to :mod:`django.urls` for more information.
|
refer to :doc:`/topics/http/urls` for more information.
|
||||||
|
|
||||||
Writing more views
|
Writing more views
|
||||||
==================
|
==================
|
||||||
|
@ -78,24 +78,24 @@ slightly different, because they take an argument:
|
||||||
return HttpResponse("You're voting on question %s." % question_id)
|
return HttpResponse("You're voting on question %s." % question_id)
|
||||||
|
|
||||||
Wire these new views into the ``polls.urls`` module by adding the following
|
Wire these new views into the ``polls.urls`` module by adding the following
|
||||||
:func:`~django.conf.urls.url` calls:
|
:func:`~django.urls.path` calls:
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ex: /polls/
|
# ex: /polls/
|
||||||
url(r'^$', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
# ex: /polls/5/
|
# ex: /polls/5/
|
||||||
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
path('<int:question_id>/', views.detail, name='detail'),
|
||||||
# ex: /polls/5/results/
|
# ex: /polls/5/results/
|
||||||
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
|
path('<int:question_id>/results/', views.results, name='results'),
|
||||||
# ex: /polls/5/vote/
|
# ex: /polls/5/vote/
|
||||||
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
path('<int:question_id>/vote/', views.vote, name='vote'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Take a look in your browser, at "/polls/34/". It'll run the ``detail()``
|
Take a look in your browser, at "/polls/34/". It'll run the ``detail()``
|
||||||
|
@ -106,26 +106,24 @@ placeholder results and voting pages.
|
||||||
When somebody requests a page from your website -- say, "/polls/34/", Django
|
When somebody requests a page from your website -- say, "/polls/34/", Django
|
||||||
will load the ``mysite.urls`` Python module because it's pointed to by the
|
will load the ``mysite.urls`` Python module because it's pointed to by the
|
||||||
:setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
|
:setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
|
||||||
and traverses the regular expressions in order. After finding the match at
|
and traverses the patterns in order. After finding the match at ``'polls/'``,
|
||||||
``'^polls/'``, it strips off the matching text (``"polls/"``) and sends the
|
it strips off the matching text (``"polls/"``) and sends the remaining text --
|
||||||
remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further
|
``"34/"`` -- to the 'polls.urls' URLconf for further processing. There it
|
||||||
processing. There it matches ``r'^(?P<question_id>[0-9]+)/$'``, resulting in a
|
matches ``'<int:question_id>/'``, resulting in a call to the ``detail()`` view
|
||||||
call to the ``detail()`` view like so::
|
like so::
|
||||||
|
|
||||||
detail(request=<HttpRequest object>, question_id='34')
|
detail(request=<HttpRequest object>, question_id=34)
|
||||||
|
|
||||||
The ``question_id='34'`` part comes from ``(?P<question_id>[0-9]+)``. Using parentheses
|
The ``question_id=34`` part comes from ``<int:question_id>``. Using angle
|
||||||
around a pattern "captures" the text matched by that pattern and sends it as an
|
brackets "captures" part of the URL and sends it as a keyword argument to the
|
||||||
argument to the view function; ``?P<question_id>`` defines the name that will
|
view function. The ``:question_id>`` part of the string defines the name that
|
||||||
be used to identify the matched pattern; and ``[0-9]+`` is a regular expression to
|
will be used to identify the matched pattern, and the ``<int:`` part is a
|
||||||
match a sequence of digits (i.e., a number).
|
converter that determines what patterns should match this part of the URL path.
|
||||||
|
|
||||||
Because the URL patterns are regular expressions, there really is no limit on
|
There's no need to add URL cruft such as ``.html`` -- unless you want to, in
|
||||||
what you can do with them. And there's no need to add URL cruft such as
|
which case you can do something like this::
|
||||||
``.html`` -- unless you want to, in which case you can do something like
|
|
||||||
this::
|
|
||||||
|
|
||||||
url(r'^polls/latest\.html$', views.index),
|
path('polls/latest.html', views.index),
|
||||||
|
|
||||||
But, don't do that. It's silly.
|
But, don't do that. It's silly.
|
||||||
|
|
||||||
|
@ -388,7 +386,7 @@ template, the link was partially hardcoded like this:
|
||||||
|
|
||||||
The problem with this hardcoded, tightly-coupled approach is that it becomes
|
The problem with this hardcoded, tightly-coupled approach is that it becomes
|
||||||
challenging to change URLs on projects with a lot of templates. However, since
|
challenging to change URLs on projects with a lot of templates. However, since
|
||||||
you defined the name argument in the :func:`~django.conf.urls.url` functions in
|
you defined the name argument in the :func:`~django.urls.path` functions in
|
||||||
the ``polls.urls`` module, you can remove a reliance on specific URL paths
|
the ``polls.urls`` module, you can remove a reliance on specific URL paths
|
||||||
defined in your url configurations by using the ``{% url %}`` template tag:
|
defined in your url configurations by using the ``{% url %}`` template tag:
|
||||||
|
|
||||||
|
@ -402,7 +400,7 @@ defined below::
|
||||||
|
|
||||||
...
|
...
|
||||||
# the 'name' value as called by the {% url %} template tag
|
# the 'name' value as called by the {% url %} template tag
|
||||||
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
path('<int:question_id>/', views.detail, name='detail'),
|
||||||
...
|
...
|
||||||
|
|
||||||
If you want to change the URL of the polls detail view to something else,
|
If you want to change the URL of the polls detail view to something else,
|
||||||
|
@ -411,7 +409,7 @@ template (or templates) you would change it in ``polls/urls.py``::
|
||||||
|
|
||||||
...
|
...
|
||||||
# added the word 'specifics'
|
# added the word 'specifics'
|
||||||
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
path('specifics/<int:question_id>/', views.detail, name='detail'),
|
||||||
...
|
...
|
||||||
|
|
||||||
Namespacing URL names
|
Namespacing URL names
|
||||||
|
@ -430,16 +428,16 @@ file, go ahead and add an ``app_name`` to set the application namespace:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'polls'
|
app_name = 'polls'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
|
path('<int:question_id>/', views.detail, name='detail'),
|
||||||
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
|
path('<int:question_id>/results/', views.results, name='results'),
|
||||||
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
path('<int:question_id>/vote/', views.vote, name='vote'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Now change your ``polls/index.html`` template from:
|
Now change your ``polls/index.html`` template from:
|
||||||
|
|
|
@ -61,7 +61,7 @@ created a URLconf for the polls application that includes this line:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
path('<int:question_id>/vote/', views.vote, name='vote'),
|
||||||
|
|
||||||
We also created a dummy implementation of the ``vote()`` function. Let's
|
We also created a dummy implementation of the ``vote()`` function. Let's
|
||||||
create a real version. Add the following to ``polls/views.py``:
|
create a real version. Add the following to ``polls/views.py``:
|
||||||
|
@ -237,20 +237,20 @@ First, open the ``polls/urls.py`` URLconf and change it like so:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'polls'
|
app_name = 'polls'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
|
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
|
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
|
||||||
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
|
path('<int:question_id>/vote/', views.vote, name='vote'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that the name of the matched pattern in the regexes of the second and third
|
Note that the name of the matched pattern in the path strings of the second and
|
||||||
patterns has changed from ``<question_id>`` to ``<pk>``.
|
third patterns has changed from ``<question_id>`` to ``<pk>``.
|
||||||
|
|
||||||
Amend views
|
Amend views
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -444,18 +444,18 @@ URLs
|
||||||
The following checks are performed on your URL configuration:
|
The following checks are performed on your URL configuration:
|
||||||
|
|
||||||
* **urls.W001**: Your URL pattern ``<pattern>`` uses
|
* **urls.W001**: Your URL pattern ``<pattern>`` uses
|
||||||
:func:`~django.conf.urls.include` with a ``regex`` ending with a
|
:func:`~django.urls.include` with a ``route`` ending with a ``$``. Remove the
|
||||||
``$``. Remove the dollar from the ``regex`` to avoid problems
|
dollar from the ``route`` to avoid problems including URLs.
|
||||||
including URLs.
|
* **urls.W002**: Your URL pattern ``<pattern>`` has a ``route`` beginning with
|
||||||
* **urls.W002**: Your URL pattern ``<pattern>`` has a ``regex``
|
a ``/``. Remove this slash as it is unnecessary. If this pattern is targeted
|
||||||
beginning with a ``/``. Remove this slash as it is unnecessary.
|
in an :func:`~django.urls.include`, ensure the :func:`~django.urls.include`
|
||||||
If this pattern is targeted in an :func:`~django.conf.urls.include`, ensure
|
pattern has a trailing ``/``.
|
||||||
the :func:`~django.conf.urls.include` pattern has a trailing ``/``.
|
|
||||||
* **urls.W003**: Your URL pattern ``<pattern>`` has a ``name``
|
* **urls.W003**: Your URL pattern ``<pattern>`` has a ``name``
|
||||||
including a ``:``. Remove the colon, to avoid ambiguous namespace
|
including a ``:``. Remove the colon, to avoid ambiguous namespace
|
||||||
references.
|
references.
|
||||||
* **urls.E004**: Your URL pattern ``<pattern>`` is invalid. Ensure that
|
* **urls.E004**: Your URL pattern ``<pattern>`` is invalid. Ensure that
|
||||||
``urlpatterns`` is a list of :func:`~django.conf.urls.url()` instances.
|
``urlpatterns`` is a list of :func:`~django.urls.path` and/or
|
||||||
|
:func:`~django.urls.re_path` instances.
|
||||||
* **urls.W005**: URL namespace ``<namespace>`` isn't unique. You may not be
|
* **urls.W005**: URL namespace ``<namespace>`` isn't unique. You may not be
|
||||||
able to reverse all URLs in this namespace.
|
able to reverse all URLs in this namespace.
|
||||||
* **urls.E006**: The :setting:`MEDIA_URL`/ :setting:`STATIC_URL` setting must
|
* **urls.E006**: The :setting:`MEDIA_URL`/ :setting:`STATIC_URL` setting must
|
||||||
|
|
|
@ -40,12 +40,12 @@ MRO is an acronym for Method Resolution Order.
|
||||||
|
|
||||||
**Example urls.py**::
|
**Example urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import MyView
|
from myapp.views import MyView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^mine/$', MyView.as_view(), name='my-view'),
|
path('mine/', MyView.as_view(), name='my-view'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Attributes**
|
**Attributes**
|
||||||
|
@ -144,12 +144,12 @@ MRO is an acronym for Method Resolution Order.
|
||||||
|
|
||||||
**Example urls.py**::
|
**Example urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import HomePageView
|
from myapp.views import HomePageView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', HomePageView.as_view(), name='home'),
|
path('', HomePageView.as_view(), name='home'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Context**
|
**Context**
|
||||||
|
@ -208,15 +208,15 @@ MRO is an acronym for Method Resolution Order.
|
||||||
|
|
||||||
**Example urls.py**::
|
**Example urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from django.views.generic.base import RedirectView
|
from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
from article.views import ArticleCounterRedirectView, ArticleDetail
|
from article.views import ArticleCounterRedirectView, ArticleDetail
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^counter/(?P<pk>[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
|
path('counter/<int:pk>/', ArticleCounterRedirectView.as_view(), name='article-counter'),
|
||||||
url(r'^details/(?P<pk>[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'),
|
path('details/<int:pk>/', ArticleDetail.as_view(), name='article-detail'),
|
||||||
url(r'^go-to-django/$', RedirectView.as_view(url='https://djangoproject.com'), name='go-to-django'),
|
path('go-to-django/', RedirectView.as_view(url='https://djangoproject.com'), name='go-to-django'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Attributes**
|
**Attributes**
|
||||||
|
|
|
@ -63,13 +63,13 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from django.views.generic.dates import ArchiveIndexView
|
from django.views.generic.dates import ArchiveIndexView
|
||||||
|
|
||||||
from myapp.models import Article
|
from myapp.models import Article
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^archive/$',
|
path('archive/',
|
||||||
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
|
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
|
||||||
name="article_archive"),
|
name="article_archive"),
|
||||||
]
|
]
|
||||||
|
@ -162,12 +162,12 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import ArticleYearArchiveView
|
from myapp.views import ArticleYearArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<year>[0-9]{4})/$',
|
path('<int:year>/',
|
||||||
ArticleYearArchiveView.as_view(),
|
ArticleYearArchiveView.as_view(),
|
||||||
name="article_year_archive"),
|
name="article_year_archive"),
|
||||||
]
|
]
|
||||||
|
@ -254,19 +254,19 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import ArticleMonthArchiveView
|
from myapp.views import ArticleMonthArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Example: /2012/aug/
|
|
||||||
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/$',
|
|
||||||
ArticleMonthArchiveView.as_view(),
|
|
||||||
name="archive_month"),
|
|
||||||
# Example: /2012/08/
|
# Example: /2012/08/
|
||||||
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$',
|
path('<int:year>/<int:month>/',
|
||||||
ArticleMonthArchiveView.as_view(month_format='%m'),
|
ArticleMonthArchiveView.as_view(month_format='%m'),
|
||||||
name="archive_month_numeric"),
|
name="archive_month_numeric"),
|
||||||
|
# Example: /2012/aug/
|
||||||
|
path('<int:year>/<str:month>/',
|
||||||
|
ArticleMonthArchiveView.as_view(),
|
||||||
|
name="archive_month"),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Example myapp/article_archive_month.html**:
|
**Example myapp/article_archive_month.html**:
|
||||||
|
@ -356,13 +356,13 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import ArticleWeekArchiveView
|
from myapp.views import ArticleWeekArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Example: /2012/week/23/
|
# Example: /2012/week/23/
|
||||||
url(r'^(?P<year>[0-9]{4})/week/(?P<week>[0-9]+)/$',
|
path('<int:year>/week/<int:week>/',
|
||||||
ArticleWeekArchiveView.as_view(),
|
ArticleWeekArchiveView.as_view(),
|
||||||
name="archive_week"),
|
name="archive_week"),
|
||||||
]
|
]
|
||||||
|
@ -468,13 +468,13 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import ArticleDayArchiveView
|
from myapp.views import ArticleDayArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Example: /2012/nov/10/
|
# Example: /2012/nov/10/
|
||||||
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/$',
|
path('<int:year>/<str:month>/<int:day>/',
|
||||||
ArticleDayArchiveView.as_view(),
|
ArticleDayArchiveView.as_view(),
|
||||||
name="archive_day"),
|
name="archive_day"),
|
||||||
]
|
]
|
||||||
|
@ -541,12 +541,12 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.views import ArticleTodayArchiveView
|
from myapp.views import ArticleTodayArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^today/$',
|
path('today/',
|
||||||
ArticleTodayArchiveView.as_view(),
|
ArticleTodayArchiveView.as_view(),
|
||||||
name="archive_today"),
|
name="archive_today"),
|
||||||
]
|
]
|
||||||
|
@ -591,11 +591,11 @@ views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from django.views.generic.dates import DateDetailView
|
from django.views.generic.dates import DateDetailView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<pk>[0-9]+)/$',
|
path('<int:year>/<str:month>/<int:day>/<int:pk>/',
|
||||||
DateDetailView.as_view(model=Article, date_field="pub_date"),
|
DateDetailView.as_view(model=Article, date_field="pub_date"),
|
||||||
name="archive_date_detail"),
|
name="archive_date_detail"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -54,12 +54,12 @@ many projects they are typically the most commonly used views.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from article.views import ArticleDetailView
|
from article.views import ArticleDetailView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<slug>[-\w]+)/$', ArticleDetailView.as_view(), name='article-detail'),
|
path('<slug>/', ArticleDetailView.as_view(), name='article-detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Example myapp/article_detail.html**:
|
**Example myapp/article_detail.html**:
|
||||||
|
@ -123,12 +123,12 @@ many projects they are typically the most commonly used views.
|
||||||
|
|
||||||
**Example myapp/urls.py**::
|
**Example myapp/urls.py**::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from article.views import ArticleListView
|
from article.views import ArticleListView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', ArticleListView.as_view(), name='article-list'),
|
path('', ArticleListView.as_view(), name='article-list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Example myapp/article_list.html**:
|
**Example myapp/article_list.html**:
|
||||||
|
|
|
@ -26,7 +26,7 @@ A class-based view is deployed into a URL pattern using the
|
||||||
:meth:`~django.views.generic.base.View.as_view()` classmethod::
|
:meth:`~django.views.generic.base.View.as_view()` classmethod::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^view/$', MyView.as_view(size=42)),
|
path('view/', MyView.as_view(size=42)),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. admonition:: Thread safety with view arguments
|
.. admonition:: Thread safety with view arguments
|
||||||
|
|
|
@ -15,7 +15,7 @@ Multiple object mixins
|
||||||
* Use the ``page`` parameter in the URLconf. For example, this is what
|
* Use the ``page`` parameter in the URLconf. For example, this is what
|
||||||
your URLconf might look like::
|
your URLconf might look like::
|
||||||
|
|
||||||
url(r'^objects/page(?P<page>[0-9]+)/$', PaginatedView.as_view()),
|
path('objects/page<int:page>/', PaginatedView.as_view()),
|
||||||
|
|
||||||
* Pass the page number via the ``page`` query-string parameter. For
|
* Pass the page number via the ``page`` query-string parameter. For
|
||||||
example, a URL would look like this::
|
example, a URL would look like this::
|
||||||
|
|
|
@ -19,9 +19,9 @@ To activate the :mod:`~django.contrib.admindocs`, you will need to do
|
||||||
the following:
|
the following:
|
||||||
|
|
||||||
* Add :mod:`django.contrib.admindocs` to your :setting:`INSTALLED_APPS`.
|
* Add :mod:`django.contrib.admindocs` to your :setting:`INSTALLED_APPS`.
|
||||||
* Add ``url(r'^admin/doc/', include('django.contrib.admindocs.urls'))`` to
|
* Add ``path('admin/doc/', include('django.contrib.admindocs.urls'))`` to
|
||||||
your ``urlpatterns``. Make sure it's included *before* the
|
your ``urlpatterns``. Make sure it's included *before* the
|
||||||
``r'^admin/'`` entry, so that requests to ``/admin/doc/`` don't get
|
``'admin/'`` entry, so that requests to ``/admin/doc/`` don't get
|
||||||
handled by the latter entry.
|
handled by the latter entry.
|
||||||
* Install the docutils Python module (http://docutils.sf.net/).
|
* Install the docutils Python module (http://docutils.sf.net/).
|
||||||
* **Optional:** Using the admindocs bookmarklets requires
|
* **Optional:** Using the admindocs bookmarklets requires
|
||||||
|
|
|
@ -1587,11 +1587,15 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
that ModelAdmin in the same way as a URLconf. Therefore you can extend
|
that ModelAdmin in the same way as a URLconf. Therefore you can extend
|
||||||
them as documented in :doc:`/topics/http/urls`::
|
them as documented in :doc:`/topics/http/urls`::
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
class MyModelAdmin(admin.ModelAdmin):
|
class MyModelAdmin(admin.ModelAdmin):
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
my_urls = [
|
my_urls = [
|
||||||
url(r'^my_view/$', self.my_view),
|
path('my_view/', self.my_view),
|
||||||
]
|
]
|
||||||
return my_urls + urls
|
return my_urls + urls
|
||||||
|
|
||||||
|
@ -1643,13 +1647,13 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urls = super().get_urls()
|
urls = super().get_urls()
|
||||||
my_urls = [
|
my_urls = [
|
||||||
url(r'^my_view/$', self.admin_site.admin_view(self.my_view))
|
path('my_view/', self.admin_site.admin_view(self.my_view))
|
||||||
]
|
]
|
||||||
return my_urls + urls
|
return my_urls + urls
|
||||||
|
|
||||||
Notice the wrapped view in the fifth line above::
|
Notice the wrapped view in the fifth line above::
|
||||||
|
|
||||||
url(r'^my_view/$', self.admin_site.admin_view(self.my_view))
|
path('my_view/', self.admin_site.admin_view(self.my_view))
|
||||||
|
|
||||||
This wrapping will protect ``self.my_view`` from unauthorized access and
|
This wrapping will protect ``self.my_view`` from unauthorized access and
|
||||||
will apply the :func:`django.views.decorators.cache.never_cache` decorator to
|
will apply the :func:`django.views.decorators.cache.never_cache` decorator to
|
||||||
|
@ -1659,7 +1663,7 @@ templates used by the :class:`ModelAdmin` views:
|
||||||
performed, you can pass a ``cacheable=True`` argument to
|
performed, you can pass a ``cacheable=True`` argument to
|
||||||
``AdminSite.admin_view()``::
|
``AdminSite.admin_view()``::
|
||||||
|
|
||||||
url(r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True))
|
path('my_view/', self.admin_site.admin_view(self.my_view, cacheable=True))
|
||||||
|
|
||||||
``ModelAdmin`` views have ``model_admin`` attributes. Other
|
``ModelAdmin`` views have ``model_admin`` attributes. Other
|
||||||
``AdminSite`` views have ``admin_site`` attributes.
|
``AdminSite`` views have ``admin_site`` attributes.
|
||||||
|
@ -2767,17 +2771,17 @@ Hooking ``AdminSite`` instances into your URLconf
|
||||||
The last step in setting up the Django admin is to hook your ``AdminSite``
|
The last step in setting up the Django admin is to hook your ``AdminSite``
|
||||||
instance into your URLconf. Do this by pointing a given URL at the
|
instance into your URLconf. Do this by pointing a given URL at the
|
||||||
``AdminSite.urls`` method. It is not necessary to use
|
``AdminSite.urls`` method. It is not necessary to use
|
||||||
:func:`~django.conf.urls.include()`.
|
:func:`~django.urls.include()`.
|
||||||
|
|
||||||
In this example, we register the default ``AdminSite`` instance
|
In this example, we register the default ``AdminSite`` instance
|
||||||
``django.contrib.admin.site`` at the URL ``/admin/`` ::
|
``django.contrib.admin.site`` at the URL ``/admin/`` ::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. _customizing-adminsite:
|
.. _customizing-adminsite:
|
||||||
|
@ -2809,12 +2813,12 @@ update :file:`myproject/urls.py` to reference your :class:`AdminSite` subclass.
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: myproject/urls.py
|
:filename: myproject/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from myapp.admin import admin_site
|
from myapp.admin import admin_site
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^myadmin/', admin_site.urls),
|
path('myadmin/', admin_site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that you may not want autodiscovery of ``admin`` modules when using your
|
Note that you may not want autodiscovery of ``admin`` modules when using your
|
||||||
|
@ -2838,12 +2842,12 @@ separate versions of the admin site -- using the ``AdminSite`` instances
|
||||||
respectively::
|
respectively::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from myproject.admin import basic_site, advanced_site
|
from myproject.admin import basic_site, advanced_site
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^basic-admin/', basic_site.urls),
|
path('basic-admin/', basic_site.urls),
|
||||||
url(r'^advanced-admin/', advanced_site.urls),
|
path('advanced-admin/', advanced_site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
``AdminSite`` instances take a single argument to their constructor, their
|
``AdminSite`` instances take a single argument to their constructor, their
|
||||||
|
@ -2879,23 +2883,23 @@ your URLconf. Specifically, add these four patterns::
|
||||||
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
url(
|
path(
|
||||||
r'^admin/password_reset/$',
|
'admin/password_reset/',
|
||||||
auth_views.PasswordResetView.as_view(),
|
auth_views.PasswordResetView.as_view(),
|
||||||
name='admin_password_reset',
|
name='admin_password_reset',
|
||||||
),
|
),
|
||||||
url(
|
path(
|
||||||
r'^admin/password_reset/done/$',
|
'admin/password_reset/done/',
|
||||||
auth_views.PasswordResetDoneView.as_view(),
|
auth_views.PasswordResetDoneView.as_view(),
|
||||||
name='password_reset_done',
|
name='password_reset_done',
|
||||||
),
|
),
|
||||||
url(
|
path(
|
||||||
r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
|
'reset/<uidb64>/<token>/',
|
||||||
auth_views.PasswordResetConfirmView.as_view(),
|
auth_views.PasswordResetConfirmView.as_view(),
|
||||||
name='password_reset_confirm',
|
name='password_reset_confirm',
|
||||||
),
|
),
|
||||||
url(
|
path(
|
||||||
r'^reset/done/$',
|
'reset/done/',
|
||||||
auth_views.PasswordResetCompleteView.as_view(),
|
auth_views.PasswordResetCompleteView.as_view(),
|
||||||
name='password_reset_complete',
|
name='password_reset_complete',
|
||||||
),
|
),
|
||||||
|
|
|
@ -47,7 +47,7 @@ Then either:
|
||||||
3. Add an entry in your URLconf. For example::
|
3. Add an entry in your URLconf. For example::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^pages/', include('django.contrib.flatpages.urls')),
|
path('pages/', include('django.contrib.flatpages.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
or:
|
or:
|
||||||
|
@ -74,7 +74,7 @@ There are several ways to include the flat pages in your URLconf. You can
|
||||||
dedicate a particular path to flat pages::
|
dedicate a particular path to flat pages::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^pages/', include('django.contrib.flatpages.urls')),
|
path('pages/', include('django.contrib.flatpages.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
You can also set it up as a "catchall" pattern. In this case, it is important
|
You can also set it up as a "catchall" pattern. In this case, it is important
|
||||||
|
@ -84,7 +84,7 @@ to place the pattern at the end of the other urlpatterns::
|
||||||
|
|
||||||
# Your other patterns here
|
# Your other patterns here
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^(?P<url>.*/)$', views.flatpage),
|
path('<path:url>', views.flatpage),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
@ -100,8 +100,8 @@ tag::
|
||||||
from django.contrib.flatpages import views
|
from django.contrib.flatpages import views
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^about-us/$', views.flatpage, {'url': '/about-us/'}, name='about'),
|
path('about-us/', views.flatpage, {'url': '/about-us/'}, name='about'),
|
||||||
url(r'^license/$', views.flatpage, {'url': '/license/'}, name='license'),
|
path('license/', views.flatpage, {'url': '/license/'}, name='license'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Using the middleware
|
Using the middleware
|
||||||
|
@ -345,15 +345,15 @@ Example
|
||||||
|
|
||||||
Here's an example of a URLconf using :class:`FlatPageSitemap`::
|
Here's an example of a URLconf using :class:`FlatPageSitemap`::
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.flatpages.sitemaps import FlatPageSitemap
|
from django.contrib.flatpages.sitemaps import FlatPageSitemap
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
# the sitemap
|
# the sitemap
|
||||||
url(r'^sitemap\.xml$', sitemap,
|
path('sitemap.xml', sitemap,
|
||||||
{'sitemaps': {'flatpages': FlatPageSitemap}},
|
{'sitemaps': {'flatpages': FlatPageSitemap}},
|
||||||
name='django.contrib.sitemaps.views.sitemap'),
|
name='django.contrib.sitemaps.views.sitemap'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -716,11 +716,11 @@ Let's dive right in. Create a file called ``admin.py`` inside the
|
||||||
|
|
||||||
Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows::
|
Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows::
|
||||||
|
|
||||||
from django.conf.urls import url, include
|
|
||||||
from django.contrib.gis import admin
|
from django.contrib.gis import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
||||||
Create an admin user:
|
Create an admin user:
|
||||||
|
|
|
@ -56,7 +56,7 @@ To activate sitemap generation on your Django site, add this line to your
|
||||||
|
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
|
|
||||||
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
|
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
|
||||||
name='django.contrib.sitemaps.views.sitemap')
|
name='django.contrib.sitemaps.views.sitemap')
|
||||||
|
|
||||||
This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`.
|
This tells Django to build a sitemap when a client accesses :file:`/sitemap.xml`.
|
||||||
|
@ -283,9 +283,9 @@ Example
|
||||||
Here's an example of a :doc:`URLconf </topics/http/urls>` using
|
Here's an example of a :doc:`URLconf </topics/http/urls>` using
|
||||||
:class:`GenericSitemap`::
|
:class:`GenericSitemap`::
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.sitemaps import GenericSitemap
|
from django.contrib.sitemaps import GenericSitemap
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
|
from django.urls import path
|
||||||
from blog.models import Entry
|
from blog.models import Entry
|
||||||
|
|
||||||
info_dict = {
|
info_dict = {
|
||||||
|
@ -298,7 +298,7 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
# the sitemap
|
# the sitemap
|
||||||
url(r'^sitemap\.xml$', sitemap,
|
path('sitemap.xml', sitemap,
|
||||||
{'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}},
|
{'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}},
|
||||||
name='django.contrib.sitemaps.views.sitemap'),
|
name='django.contrib.sitemaps.views.sitemap'),
|
||||||
]
|
]
|
||||||
|
@ -328,8 +328,8 @@ the ``location`` method of the sitemap. For example::
|
||||||
return reverse(item)
|
return reverse(item)
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
from .sitemaps import StaticViewSitemap
|
from .sitemaps import StaticViewSitemap
|
||||||
from . import views
|
from . import views
|
||||||
|
@ -339,11 +339,11 @@ the ``location`` method of the sitemap. For example::
|
||||||
}
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.main, name='main'),
|
path('', views.main, name='main'),
|
||||||
url(r'^about/$', views.about, name='about'),
|
path('about/', views.about, name='about'),
|
||||||
url(r'^license/$', views.license, name='license'),
|
path('license/', views.license, name='license'),
|
||||||
# ...
|
# ...
|
||||||
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
|
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
|
||||||
name='django.contrib.sitemaps.views.sitemap')
|
name='django.contrib.sitemaps.views.sitemap')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -367,8 +367,8 @@ Here's what the relevant URLconf lines would look like for the example above::
|
||||||
from django.contrib.sitemaps import views
|
from django.contrib.sitemaps import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sitemap\.xml$', views.index, {'sitemaps': sitemaps}),
|
path('sitemap.xml', views.index, {'sitemaps': sitemaps}),
|
||||||
url(r'^sitemap-(?P<section>.+)\.xml$', views.sitemap, {'sitemaps': sitemaps},
|
path('sitemap-<section>.xml', views.sitemap, {'sitemaps': sitemaps},
|
||||||
name='django.contrib.sitemaps.views.sitemap'),
|
name='django.contrib.sitemaps.views.sitemap'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -389,10 +389,10 @@ with a caching decorator -- you must name your sitemap view and pass
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sitemap\.xml$',
|
path('sitemap.xml',
|
||||||
cache_page(86400)(sitemaps_views.index),
|
cache_page(86400)(sitemaps_views.index),
|
||||||
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
|
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
|
||||||
url(r'^sitemap-(?P<section>.+)\.xml$',
|
path('sitemap-<section>.xml',
|
||||||
cache_page(86400)(sitemaps_views.sitemap),
|
cache_page(86400)(sitemaps_views.sitemap),
|
||||||
{'sitemaps': sitemaps}, name='sitemaps'),
|
{'sitemaps': sitemaps}, name='sitemaps'),
|
||||||
]
|
]
|
||||||
|
@ -408,11 +408,11 @@ parameter to the ``sitemap`` and ``index`` views via the URLconf::
|
||||||
from django.contrib.sitemaps import views
|
from django.contrib.sitemaps import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^custom-sitemap\.xml$', views.index, {
|
path('custom-sitemap.xml', views.index, {
|
||||||
'sitemaps': sitemaps,
|
'sitemaps': sitemaps,
|
||||||
'template_name': 'custom_sitemap.html'
|
'template_name': 'custom_sitemap.html'
|
||||||
}),
|
}),
|
||||||
url(r'^custom-sitemap-(?P<section>.+)\.xml$', views.sitemap, {
|
path('custom-sitemap-<section>.xml', views.sitemap, {
|
||||||
'sitemaps': sitemaps,
|
'sitemaps': sitemaps,
|
||||||
'template_name': 'custom_sitemap.html'
|
'template_name': 'custom_sitemap.html'
|
||||||
}, name='django.contrib.sitemaps.views.sitemap'),
|
}, name='django.contrib.sitemaps.views.sitemap'),
|
||||||
|
|
|
@ -462,10 +462,11 @@ primary URL configuration::
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles import views
|
from django.contrib.staticfiles import views
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^static/(?P<path>.*)$', views.serve),
|
re_path(r'^static/(?P<path>.*)$', views.serve),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note, the beginning of the pattern (``r'^static/'``) should be your
|
Note, the beginning of the pattern (``r'^static/'``) should be your
|
||||||
|
|
|
@ -77,12 +77,12 @@ a feed of the latest five news items::
|
||||||
To connect a URL to this feed, put an instance of the Feed object in
|
To connect a URL to this feed, put an instance of the Feed object in
|
||||||
your :doc:`URLconf </topics/http/urls>`. For example::
|
your :doc:`URLconf </topics/http/urls>`. For example::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from myproject.feeds import LatestEntriesFeed
|
from myproject.feeds import LatestEntriesFeed
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ...
|
# ...
|
||||||
url(r'^latest/feed/$', LatestEntriesFeed()),
|
path('latest/feed/', LatestEntriesFeed()),
|
||||||
# ...
|
# ...
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ The police beat feeds could be accessible via URLs like this:
|
||||||
|
|
||||||
These can be matched with a :doc:`URLconf </topics/http/urls>` line such as::
|
These can be matched with a :doc:`URLconf </topics/http/urls>` line such as::
|
||||||
|
|
||||||
url(r'^beats/(?P<beat_id>[0-9]+)/rss/$', BeatFeed()),
|
path('beats/<int:beat_id>/rss/', BeatFeed()),
|
||||||
|
|
||||||
Like a view, the arguments in the URL are passed to the ``get_object()``
|
Like a view, the arguments in the URL are passed to the ``get_object()``
|
||||||
method along with the request object.
|
method along with the request object.
|
||||||
|
@ -366,13 +366,13 @@ Here's a full example::
|
||||||
|
|
||||||
And the accompanying URLconf::
|
And the accompanying URLconf::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
|
from myproject.feeds import RssSiteNewsFeed, AtomSiteNewsFeed
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ...
|
# ...
|
||||||
url(r'^sitenews/rss/$', RssSiteNewsFeed()),
|
path('sitenews/rss/', RssSiteNewsFeed()),
|
||||||
url(r'^sitenews/atom/$', AtomSiteNewsFeed()),
|
path('sitenews/atom/', AtomSiteNewsFeed()),
|
||||||
# ...
|
# ...
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1115,11 +1115,11 @@ hard-code URLs in your templates::
|
||||||
|
|
||||||
{% url 'some-url-name' v1 v2 %}
|
{% url 'some-url-name' v1 v2 %}
|
||||||
|
|
||||||
The first argument is a :func:`~django.conf.urls.url` ``name``. It can be a
|
The first argument is a :ref:`URL pattern name <naming-url-patterns>`. It can
|
||||||
quoted literal or any other context variable. Additional arguments are optional
|
be a quoted literal or any other context variable. Additional arguments are
|
||||||
and should be space-separated values that will be used as arguments in the URL.
|
optional and should be space-separated values that will be used as arguments in
|
||||||
The example above shows passing positional arguments. Alternatively you may
|
the URL. The example above shows passing positional arguments. Alternatively
|
||||||
use keyword syntax::
|
you may use keyword syntax::
|
||||||
|
|
||||||
{% url 'some-url-name' arg1=v1 arg2=v2 %}
|
{% url 'some-url-name' arg1=v1 arg2=v2 %}
|
||||||
|
|
||||||
|
@ -1132,14 +1132,14 @@ takes a client ID (here, ``client()`` is a method inside the views file
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
('^client/([0-9]+)/$', app_views.client, name='app-views-client')
|
path('client/<int:id>/', app_views.client, name='app-views-client')
|
||||||
|
|
||||||
If this app's URLconf is included into the project's URLconf under a path
|
If this app's URLconf is included into the project's URLconf under a path
|
||||||
such as this:
|
such as this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
('^clients/', include('project_name.app_name.urls'))
|
path('clients/', include('project_name.app_name.urls'))
|
||||||
|
|
||||||
...then, in a template, you can create a link to this view like this::
|
...then, in a template, you can create a link to this view like this::
|
||||||
|
|
||||||
|
@ -1179,8 +1179,8 @@ by the context as to the current application.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Don't forget to put quotes around the :func:`~django.conf.urls.url`
|
Don't forget to put quotes around the URL pattern ``name``, otherwise the
|
||||||
``name``, otherwise the value will be interpreted as a context variable!
|
value will be interpreted as a context variable!
|
||||||
|
|
||||||
.. templatetag:: verbatim
|
.. templatetag:: verbatim
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ callable view object. For example, given the following ``url``::
|
||||||
|
|
||||||
from news import views
|
from news import views
|
||||||
|
|
||||||
url(r'^archive/$', views.archive, name='news-archive')
|
path('archive/', views.archive, name='news-archive')
|
||||||
|
|
||||||
you can use any of the following to reverse the URL::
|
you can use any of the following to reverse the URL::
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,79 @@
|
||||||
.. module:: django.urls.conf
|
.. module:: django.urls.conf
|
||||||
:synopsis: Functions for use in URLconfs.
|
:synopsis: Functions for use in URLconfs.
|
||||||
|
|
||||||
.. currentmodule:: django.conf.urls
|
.. currentmodule:: django.urls
|
||||||
|
|
||||||
|
``path()``
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. function:: path(route, view, kwargs=None, name=None)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Returns an element for inclusion in ``urlpatterns``. For example::
|
||||||
|
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('index/', views.index, name='main-view'),
|
||||||
|
path('bio/<username>/', views.bio, name='bio'),
|
||||||
|
path('articles/<slug:title>/', views.article, name='article-detail'),
|
||||||
|
path('articles/<slug:title>/<int:section>/', views.section, name='article-section'),
|
||||||
|
path('weblog/', include('blog.urls')),
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
The ``route`` argument should be a string or
|
||||||
|
:func:`~django.utils.translation.gettext_lazy()` (see
|
||||||
|
:ref:`translating-urlpatterns`) that contains a URL pattern. The string
|
||||||
|
may contain angle brackets (like ``<username>`` above) to capture part of the
|
||||||
|
URL and send it as a keyword argument to the view. The angle brackets may
|
||||||
|
include a converter specification (like the ``int`` part of ``<int:section>``)
|
||||||
|
which limits the characters matched and may also change the type of the
|
||||||
|
variable passed to the view. For example, ``<int:section>`` matches a string
|
||||||
|
of decimal digits and converts the value to an ``int``. See
|
||||||
|
:ref:`how-django-processes-a-request` for more details.
|
||||||
|
|
||||||
|
The ``view`` argument is a view function or the result of
|
||||||
|
:meth:`~django.views.generic.base.View.as_view` for class-based views. It can
|
||||||
|
also be an :func:`django.urls.include`.
|
||||||
|
|
||||||
|
The ``kwargs`` argument allows you to pass additional arguments to the view
|
||||||
|
function or method. See :ref:`views-extra-options` for an example.
|
||||||
|
|
||||||
|
See :ref:`Naming URL patterns <naming-url-patterns>` for why the ``name``
|
||||||
|
argument is useful.
|
||||||
|
|
||||||
|
``re_path()``
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. function:: re_path(route, view, kwargs=None, name=None)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Returns an element for inclusion in ``urlpatterns``. For example::
|
||||||
|
|
||||||
|
from django.urls import include, re_path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'^index/$', views.index, name='index'),
|
||||||
|
re_path(r'^bio/(?P<username>\w+)/$', views.bio, name='bio'),
|
||||||
|
re_path(r'^weblog/', include('blog.urls')),
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
The ``route`` argument should be a string or
|
||||||
|
:func:`~django.utils.translation.gettext_lazy()` (see
|
||||||
|
:ref:`translating-urlpatterns`) that contains a regular expression compatible
|
||||||
|
with Python's :py:mod:`re` module. Strings typically use raw string syntax
|
||||||
|
(``r''``) so that they can contain sequences like ``\d`` without the need to
|
||||||
|
escape the backslash with another backslash. When a match is made, captured
|
||||||
|
groups from the regular expression are passed to the view -- as named arguments
|
||||||
|
if the groups are named, and as positional arguments otherwise. The values are
|
||||||
|
passed as strings, without any type conversion.
|
||||||
|
|
||||||
|
The ``view``, ``kwargs`` and ``name`` arguments are the same as for
|
||||||
|
:func:`~django.urls.path()`.
|
||||||
|
|
||||||
``include()``
|
``include()``
|
||||||
=============
|
=============
|
||||||
|
@ -30,7 +102,7 @@
|
||||||
:arg module: URLconf module (or module name)
|
:arg module: URLconf module (or module name)
|
||||||
:arg namespace: Instance namespace for the URL entries being included
|
:arg namespace: Instance namespace for the URL entries being included
|
||||||
:type namespace: string
|
:type namespace: string
|
||||||
:arg pattern_list: Iterable of :func:`django.conf.urls.url` instances
|
:arg pattern_list: Iterable of :func:`~django.urls.path` and/or :func:`~django.urls.re_path` instances.
|
||||||
:arg app_namespace: Application namespace for the URL entries being included
|
:arg app_namespace: Application namespace for the URL entries being included
|
||||||
:type app_namespace: string
|
:type app_namespace: string
|
||||||
:arg instance_namespace: Instance namespace for the URL entries being included
|
:arg instance_namespace: Instance namespace for the URL entries being included
|
||||||
|
@ -43,6 +115,20 @@ See :ref:`including-other-urlconfs` and :ref:`namespaces-and-include`.
|
||||||
In older versions, this function is located in ``django.conf.urls``. The
|
In older versions, this function is located in ``django.conf.urls``. The
|
||||||
old location still works for backwards compatibility.
|
old location still works for backwards compatibility.
|
||||||
|
|
||||||
|
``register_converter()``
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. function:: register_converter(converter, type_name)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
The function for registering a converter for use in :func:`~django.urls.path()`
|
||||||
|
``route``\s.
|
||||||
|
|
||||||
|
The ``converter`` argument is a converter class, and ``type_name`` is the
|
||||||
|
converter name to use in path patterns. See
|
||||||
|
:ref:`registering-custom-path-converters` for an example.
|
||||||
|
|
||||||
==================================================
|
==================================================
|
||||||
``django.conf.urls`` functions for use in URLconfs
|
``django.conf.urls`` functions for use in URLconfs
|
||||||
==================================================
|
==================================================
|
||||||
|
@ -68,32 +154,8 @@ Helper function to return a URL pattern for serving files in debug mode::
|
||||||
|
|
||||||
.. function:: url(regex, view, kwargs=None, name=None)
|
.. function:: url(regex, view, kwargs=None, name=None)
|
||||||
|
|
||||||
``urlpatterns`` should be a list of ``url()`` instances. For example::
|
This function is an alias to :func:`django.urls.re_path()`. It's likely to be
|
||||||
|
deprecated in a future release.
|
||||||
from django.conf.urls import include, url
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^index/$', index_view, name='main-view'),
|
|
||||||
url(r'^weblog/', include('blog.urls')),
|
|
||||||
...
|
|
||||||
]
|
|
||||||
|
|
||||||
The ``regex`` parameter should be a string or
|
|
||||||
:func:`~django.utils.translation.gettext_lazy()` (see
|
|
||||||
:ref:`translating-urlpatterns`) that contains a regular expression compatible
|
|
||||||
with Python's :py:mod:`re` module. Strings typically use raw string syntax
|
|
||||||
(``r''``) so that they can contain sequences like ``\d`` without the need to
|
|
||||||
escape the backslash with another backslash.
|
|
||||||
|
|
||||||
The ``view`` parameter is a view function or the result of
|
|
||||||
:meth:`~django.views.generic.base.View.as_view` for class-based views. It can
|
|
||||||
also be an :func:`include`.
|
|
||||||
|
|
||||||
The ``kwargs`` parameter allows you to pass additional arguments to the view
|
|
||||||
function or method. See :ref:`views-extra-options` for an example.
|
|
||||||
|
|
||||||
See :ref:`Naming URL patterns <naming-url-patterns>` for why the ``name``
|
|
||||||
parameter is useful.
|
|
||||||
|
|
||||||
``handler400``
|
``handler400``
|
||||||
==============
|
==============
|
||||||
|
|
|
@ -823,7 +823,7 @@ appropriate entities.
|
||||||
from django.utils.translation import pgettext_lazy
|
from django.utils.translation import pgettext_lazy
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(format_lazy(r'{person}/(?P<pk>\d+)/$', person=pgettext_lazy('URL', 'person')),
|
path(format_lazy('{person}/<int:pk>/', person=pgettext_lazy('URL', 'person')),
|
||||||
PersonDetailView.as_view()),
|
PersonDetailView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,14 @@ built-in handling for user-uploaded files, but you can have Django serve your
|
||||||
:setting:`MEDIA_ROOT` by appending something like this to your URLconf::
|
:setting:`MEDIA_ROOT` by appending something like this to your URLconf::
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import re_path
|
||||||
from django.views.static import serve
|
from django.views.static import serve
|
||||||
|
|
||||||
# ... the rest of your URLconf goes here ...
|
# ... the rest of your URLconf goes here ...
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^media/(?P<path>.*)$', serve, {
|
re_path(r'^media/(?P<path>.*)$', serve, {
|
||||||
'document_root': settings.MEDIA_ROOT,
|
'document_root': settings.MEDIA_ROOT,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1229,9 +1229,8 @@ disable this backward-compatibility shim and deprecation warning.
|
||||||
``django.conf.urls.defaults``
|
``django.conf.urls.defaults``
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
Until Django 1.3, the functions :func:`~django.conf.urls.include`,
|
Until Django 1.3, the ``include()``, ``patterns()``, and ``url()`` functions,
|
||||||
``patterns()`` and :func:`~django.conf.urls.url` plus
|
plus :data:`~django.conf.urls.handler404` and :data:`~django.conf.urls.handler500`
|
||||||
:data:`~django.conf.urls.handler404`, :data:`~django.conf.urls.handler500`
|
|
||||||
were located in a ``django.conf.urls.defaults`` module.
|
were located in a ``django.conf.urls.defaults`` module.
|
||||||
|
|
||||||
In Django 1.4, they live in :mod:`django.conf.urls`.
|
In Django 1.4, they live in :mod:`django.conf.urls`.
|
||||||
|
|
|
@ -657,7 +657,7 @@ URLs
|
||||||
* The application namespace can now be set using an ``app_name`` attribute
|
* The application namespace can now be set using an ``app_name`` attribute
|
||||||
on the included module or object. It can also be set by passing a 2-tuple
|
on the included module or object. It can also be set by passing a 2-tuple
|
||||||
of (<list of patterns>, <application namespace>) as the first argument to
|
of (<list of patterns>, <application namespace>) as the first argument to
|
||||||
:func:`~django.conf.urls.include`.
|
``include()``.
|
||||||
|
|
||||||
* System checks have been added for common URL pattern mistakes.
|
* System checks have been added for common URL pattern mistakes.
|
||||||
|
|
||||||
|
@ -1233,8 +1233,8 @@ extending. This change necessitated a new template loader API. The old
|
||||||
Details about the new API can be found :ref:`in the template loader
|
Details about the new API can be found :ref:`in the template loader
|
||||||
documentation <custom-template-loaders>`.
|
documentation <custom-template-loaders>`.
|
||||||
|
|
||||||
Passing a 3-tuple or an ``app_name`` to :func:`~django.conf.urls.include()`
|
Passing a 3-tuple or an ``app_name`` to ``include()``
|
||||||
---------------------------------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
|
||||||
The instance namespace part of passing a tuple as an argument to ``include()``
|
The instance namespace part of passing a tuple as an argument to ``include()``
|
||||||
has been replaced by passing the ``namespace`` argument to ``include()``. For
|
has been replaced by passing the ``namespace`` argument to ``include()``. For
|
||||||
|
|
|
@ -46,6 +46,32 @@ be compatible with Django 2.0.
|
||||||
What's new in Django 2.0
|
What's new in Django 2.0
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
Simplified URL routing syntax
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The new :func:`django.urls.path()` function allows a simpler, more readable URL
|
||||||
|
routing syntax. For example, this example from previous Django releases::
|
||||||
|
|
||||||
|
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
|
||||||
|
|
||||||
|
could be written as::
|
||||||
|
|
||||||
|
path('articles/<int:year>/', views.year_archive),
|
||||||
|
|
||||||
|
The new syntax supports type coercion of URL parameters. In the example, the
|
||||||
|
view will receive the ``year`` keyword argument as an integer rather than as
|
||||||
|
a string.
|
||||||
|
|
||||||
|
The ``django.conf.urls.url()`` function from previous versions is now available
|
||||||
|
as :func:`django.urls.re_path`, however, the old location remains for backwards
|
||||||
|
compatibility, without an imminent deprecation. The old
|
||||||
|
``django.conf.urls.include()`` function is now importable from ``django.urls``
|
||||||
|
so you can use ``from django.urls import include, path, re_path`` in your
|
||||||
|
URLconfs.
|
||||||
|
|
||||||
|
The :doc:`/topics/http/urls` document is rewritten to feature the new syntax
|
||||||
|
and provide more details.
|
||||||
|
|
||||||
Mobile-friendly ``contrib.admin``
|
Mobile-friendly ``contrib.admin``
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|
|
@ -502,7 +502,7 @@ The ``login_required`` decorator
|
||||||
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
url(r'^accounts/login/$', auth_views.LoginView.as_view()),
|
path('accounts/login/', auth_views.LoginView.as_view()),
|
||||||
|
|
||||||
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
|
The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
|
||||||
names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
|
names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
|
||||||
|
@ -896,7 +896,7 @@ easiest way is to include the provided URLconf in ``django.contrib.auth.urls``
|
||||||
in your own URLconf, for example::
|
in your own URLconf, for example::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^', include('django.contrib.auth.urls')),
|
path('', include('django.contrib.auth.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
This will include the following URL patterns::
|
This will include the following URL patterns::
|
||||||
|
@ -919,7 +919,7 @@ your URLconf::
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url('^change-password/$', auth_views.PasswordChangeView.as_view()),
|
path('change-password/', auth_views.PasswordChangeView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
The views have optional arguments you can use to alter the behavior of the
|
The views have optional arguments you can use to alter the behavior of the
|
||||||
|
@ -928,8 +928,8 @@ provide the ``template_name`` argument. A way to do this is to provide keyword
|
||||||
arguments in the URLconf, these will be passed on to the view. For example::
|
arguments in the URLconf, these will be passed on to the view. For example::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
path(
|
||||||
'^change-password/$',
|
'change-password/',
|
||||||
auth_views.PasswordChangeView.as_view(template_name='change-password.html'),
|
auth_views.PasswordChangeView.as_view(template_name='change-password.html'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1035,7 +1035,7 @@ implementation details see :ref:`using-the-views`.
|
||||||
the ``as_view`` method in your URLconf. For example, this URLconf line would
|
the ``as_view`` method in your URLconf. For example, this URLconf line would
|
||||||
use :file:`myapp/login.html` instead::
|
use :file:`myapp/login.html` instead::
|
||||||
|
|
||||||
url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='myapp/login.html')),
|
path('accounts/login/', auth_views.LoginView.as_view(template_name='myapp/login.html')),
|
||||||
|
|
||||||
You can also specify the name of the ``GET`` field which contains the URL
|
You can also specify the name of the ``GET`` field which contains the URL
|
||||||
to redirect to after login using ``redirect_field_name``. By default, the
|
to redirect to after login using ``redirect_field_name``. By default, the
|
||||||
|
|
|
@ -591,7 +591,7 @@ multiple URLs point at the same view, each URL will be cached separately.
|
||||||
Continuing the ``my_view`` example, if your URLconf looks like this::
|
Continuing the ``my_view`` example, if your URLconf looks like this::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^foo/([0-9]{1,2})/$', my_view),
|
path('foo/<int:code>/', my_view),
|
||||||
]
|
]
|
||||||
|
|
||||||
then requests to ``/foo/1/`` and ``/foo/23/`` will be cached separately, as
|
then requests to ``/foo/1/`` and ``/foo/23/`` will be cached separately, as
|
||||||
|
@ -637,7 +637,7 @@ Doing so is easy: simply wrap the view function with ``cache_page`` when you
|
||||||
refer to it in the URLconf. Here's the old URLconf from earlier::
|
refer to it in the URLconf. Here's the old URLconf from earlier::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^foo/([0-9]{1,2})/$', my_view),
|
path('foo/<int:code>/', my_view),
|
||||||
]
|
]
|
||||||
|
|
||||||
Here's the same thing, with ``my_view`` wrapped in ``cache_page``::
|
Here's the same thing, with ``my_view`` wrapped in ``cache_page``::
|
||||||
|
@ -645,7 +645,7 @@ Here's the same thing, with ``my_view`` wrapped in ``cache_page``::
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
|
path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. templatetag:: cache
|
.. templatetag:: cache
|
||||||
|
|
|
@ -117,11 +117,11 @@ Now we need to define a view::
|
||||||
Finally hook that view into your urls::
|
Finally hook that view into your urls::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from books.views import PublisherList
|
from books.views import PublisherList
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^publishers/$', PublisherList.as_view()),
|
path('publishers/', PublisherList.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
That's all the Python code we need to write. We still need to write a template,
|
That's all the Python code we need to write. We still need to write a template,
|
||||||
|
@ -332,11 +332,11 @@ various useful things are stored on ``self``; as well as the request
|
||||||
Here, we have a URLconf with a single captured group::
|
Here, we have a URLconf with a single captured group::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from books.views import PublisherBookList
|
from books.views import PublisherBookList
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
|
path('books/<publisher>/', PublisherBookList.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
Next, we'll write the ``PublisherBookList`` view itself::
|
Next, we'll write the ``PublisherBookList`` view itself::
|
||||||
|
@ -351,7 +351,7 @@ Next, we'll write the ``PublisherBookList`` view itself::
|
||||||
template_name = 'books/books_by_publisher.html'
|
template_name = 'books/books_by_publisher.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.publisher = get_object_or_404(Publisher, name=self.args[0])
|
self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
|
||||||
return Book.objects.filter(publisher=self.publisher)
|
return Book.objects.filter(publisher=self.publisher)
|
||||||
|
|
||||||
As you can see, it's quite easy to add more logic to the queryset selection;
|
As you can see, it's quite easy to add more logic to the queryset selection;
|
||||||
|
@ -398,12 +398,12 @@ updated.
|
||||||
First, we'd need to add an author detail bit in the URLconf to point to a
|
First, we'd need to add an author detail bit in the URLconf to point to a
|
||||||
custom view::
|
custom view::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from books.views import AuthorDetailView
|
from books.views import AuthorDetailView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#...
|
#...
|
||||||
url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
|
path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Then we'd write our new view -- ``get_object`` is the method that retrieves the
|
Then we'd write our new view -- ``get_object`` is the method that retrieves the
|
||||||
|
|
|
@ -149,14 +149,14 @@ Finally, we hook these new views into the URLconf:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: urls.py
|
:filename: urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
|
from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ...
|
# ...
|
||||||
url(r'author/add/$', AuthorCreate.as_view(), name='author-add'),
|
path('author/add/', AuthorCreate.as_view(), name='author-add'),
|
||||||
url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author-update'),
|
path('author/<int:pk>/', AuthorUpdate.as_view(), name='author-update'),
|
||||||
url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author-delete'),
|
path('author/<int:pk>/delete/', AuthorDelete.as_view(), name='author-delete'),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
|
@ -38,11 +38,11 @@ URLconf. If you're only changing a few simple attributes on a class-based view,
|
||||||
you can simply pass them into the
|
you can simply pass them into the
|
||||||
:meth:`~django.views.generic.base.View.as_view` method call itself::
|
:meth:`~django.views.generic.base.View.as_view` method call itself::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^about/$', TemplateView.as_view(template_name="about.html")),
|
path('about/', TemplateView.as_view(template_name="about.html")),
|
||||||
]
|
]
|
||||||
|
|
||||||
Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will
|
Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will
|
||||||
|
@ -75,11 +75,11 @@ class method instead, which provides a function-like entry to class-based
|
||||||
views::
|
views::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from some_app.views import AboutView
|
from some_app.views import AboutView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^about/$', AboutView.as_view()),
|
path('about/', AboutView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,11 +100,11 @@ preferable to ask the API when the most recent book was published.
|
||||||
|
|
||||||
We map the URL to book list view in the URLconf::
|
We map the URL to book list view in the URLconf::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from books.views import BookListView
|
from books.views import BookListView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^books/$', BookListView.as_view()),
|
path('books/', BookListView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
And the view::
|
And the view::
|
||||||
|
|
|
@ -89,11 +89,11 @@ request to a matching method if one is defined, or raises
|
||||||
:class:`~django.http.HttpResponseNotAllowed` if not::
|
:class:`~django.http.HttpResponseNotAllowed` if not::
|
||||||
|
|
||||||
# urls.py
|
# urls.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from myapp.views import MyView
|
from myapp.views import MyView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^about/$', MyView.as_view()),
|
path('about/', MyView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ Another option is to configure class attributes as keyword arguments to the
|
||||||
:meth:`~django.views.generic.base.View.as_view` call in the URLconf::
|
:meth:`~django.views.generic.base.View.as_view` call in the URLconf::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^about/$', GreetingView.as_view(greeting="G'day")),
|
path('about/', GreetingView.as_view(greeting="G'day")),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
@ -245,8 +245,8 @@ The easiest place to do this is in the URLconf where you deploy your view::
|
||||||
from .views import VoteView
|
from .views import VoteView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))),
|
path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
|
||||||
url(r'^vote/$', permission_required('polls.can_vote')(VoteView.as_view())),
|
path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
|
||||||
]
|
]
|
||||||
|
|
||||||
This approach applies the decorator on a per-instance basis. If you
|
This approach applies the decorator on a per-instance basis. If you
|
||||||
|
|
|
@ -258,12 +258,12 @@ We can hook this into our URLs easily enough:
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: urls.py
|
:filename: urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from books.views import RecordInterest
|
from books.views import RecordInterest
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#...
|
#...
|
||||||
url(r'^author/(?P<pk>[0-9]+)/interest/$', RecordInterest.as_view(), name='author-interest'),
|
path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note the ``pk`` named group, which
|
Note the ``pk`` named group, which
|
||||||
|
|
|
@ -19,8 +19,7 @@ Overview
|
||||||
|
|
||||||
To design URLs for an app, you create a Python module informally called a
|
To design URLs for an app, you create a Python module informally called a
|
||||||
**URLconf** (URL configuration). This module is pure Python code and is a
|
**URLconf** (URL configuration). This module is pure Python code and is a
|
||||||
simple mapping between URL patterns (simple regular expressions) to Python
|
mapping between URL path expressions to Python functions (your views).
|
||||||
functions (your views).
|
|
||||||
|
|
||||||
This mapping can be as short or as long as needed. It can reference other
|
This mapping can be as short or as long as needed. It can reference other
|
||||||
mappings. And, because it's pure Python code, it can be constructed
|
mappings. And, because it's pure Python code, it can be constructed
|
||||||
|
@ -45,25 +44,26 @@ algorithm the system follows to determine which Python code to execute:
|
||||||
:setting:`ROOT_URLCONF` setting.
|
:setting:`ROOT_URLCONF` setting.
|
||||||
|
|
||||||
2. Django loads that Python module and looks for the variable
|
2. Django loads that Python module and looks for the variable
|
||||||
``urlpatterns``. This should be a Python list of :func:`django.conf.urls.url`
|
``urlpatterns``. This should be a Python list of :func:`django.urls.path`
|
||||||
instances.
|
and/or :func:`django.urls.re_path` instances.
|
||||||
|
|
||||||
3. Django runs through each URL pattern, in order, and stops at the first
|
3. Django runs through each URL pattern, in order, and stops at the first
|
||||||
one that matches the requested URL.
|
one that matches the requested URL.
|
||||||
|
|
||||||
4. Once one of the regexes matches, Django imports and calls the given view,
|
4. Once one of the URL patterns matches, Django imports and calls the given
|
||||||
which is a simple Python function (or a :doc:`class-based view
|
view, which is a simple Python function (or a :doc:`class-based view
|
||||||
</topics/class-based-views/index>`). The view gets passed the following
|
</topics/class-based-views/index>`). The view gets passed the following
|
||||||
arguments:
|
arguments:
|
||||||
|
|
||||||
* An instance of :class:`~django.http.HttpRequest`.
|
* An instance of :class:`~django.http.HttpRequest`.
|
||||||
* If the matched regular expression returned no named groups, then the
|
* If the matched URL pattern returned no named groups, then the
|
||||||
matches from the regular expression are provided as positional arguments.
|
matches from the regular expression are provided as positional arguments.
|
||||||
* The keyword arguments are made up of any named groups matched by the
|
* The keyword arguments are made up of any named parts matched by the
|
||||||
regular expression, overridden by any arguments specified in the optional
|
path expression, overridden by any arguments specified in the optional
|
||||||
``kwargs`` argument to :func:`django.conf.urls.url`.
|
``kwargs`` argument to :func:`django.urls.path` or
|
||||||
|
:func:`django.urls.re_path`.
|
||||||
|
|
||||||
5. If no regex matches, or if an exception is raised during any
|
5. If no URL pattern matches, or if an exception is raised during any
|
||||||
point in this process, Django invokes an appropriate
|
point in this process, Django invokes an appropriate
|
||||||
error-handling view. See `Error handling`_ below.
|
error-handling view. See `Error handling`_ below.
|
||||||
|
|
||||||
|
@ -72,36 +72,33 @@ Example
|
||||||
|
|
||||||
Here's a sample URLconf::
|
Here's a sample URLconf::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^articles/2003/$', views.special_case_2003),
|
path('articles/2003/', views.special_case_2003),
|
||||||
url(r'^articles/([0-9]{4})/$', views.year_archive),
|
path('articles/<int:year>/', views.year_archive),
|
||||||
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
|
path('articles/<int:year>/<int:month>/', views.month_archive),
|
||||||
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
|
path('articles/<int:year>/<int:month>/<slug>/', views.article_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
* To capture a value from the URL, just put parenthesis around it.
|
* To capture a value from the URL, use angle brackets.
|
||||||
|
|
||||||
|
* Captured values can optionally include a converter type. For example, use
|
||||||
|
``<int:name>`` to capture an integer parameter. If a converter isn't included,
|
||||||
|
any string, excluding a ``/`` character, is matched.
|
||||||
|
|
||||||
* There's no need to add a leading slash, because every URL has that. For
|
* There's no need to add a leading slash, because every URL has that. For
|
||||||
example, it's ``^articles``, not ``^/articles``.
|
example, it's ``articles``, not ``/articles``.
|
||||||
|
|
||||||
* 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. See `Dive Into Python's explanation`_.
|
|
||||||
|
|
||||||
Example requests:
|
Example requests:
|
||||||
|
|
||||||
* A request to ``/articles/2005/03/`` would match the third entry in the
|
* A request to ``/articles/2005/03/`` would match the third entry in the
|
||||||
list. Django would call the function
|
list. Django would call the function
|
||||||
``views.month_archive(request, '2005', '03')``.
|
``views.month_archive(request, year=2005, month=3)``.
|
||||||
|
|
||||||
* ``/articles/2005/3/`` would not match any URL patterns, because the
|
|
||||||
third entry in the list requires two digits for the month.
|
|
||||||
|
|
||||||
* ``/articles/2003/`` would match the first pattern in the list, not the
|
* ``/articles/2003/`` would match the first pattern in the list, not the
|
||||||
second one, because the patterns are tested in order, and the first one
|
second one, because the patterns are tested in order, and the first one
|
||||||
|
@ -112,66 +109,163 @@ Example requests:
|
||||||
* ``/articles/2003`` would not match any of these patterns, because each
|
* ``/articles/2003`` would not match any of these patterns, because each
|
||||||
pattern requires that the URL end with a slash.
|
pattern requires that the URL end with a slash.
|
||||||
|
|
||||||
* ``/articles/2003/03/03/`` would match the final pattern. Django would call
|
* ``/articles/2003/03/building-a-django-site/`` would match the final
|
||||||
the function ``views.article_detail(request, '2003', '03', '03')``.
|
pattern. Django would call the function
|
||||||
|
``views.article_detail(request, year=2003, month=3, slug="building-a-django-site")``.
|
||||||
|
|
||||||
.. _Dive Into Python's explanation: http://www.diveintopython3.net/regular-expressions.html#streetaddresses
|
Path converters
|
||||||
|
===============
|
||||||
|
|
||||||
Named groups
|
The following path converters are available by default:
|
||||||
============
|
|
||||||
|
|
||||||
The above example used simple, *non-named* regular-expression groups (via
|
* ``str`` - Matches any non-empty string, excluding the path separator, ``'/'``.
|
||||||
parenthesis) to capture bits of the URL and pass them as *positional* arguments
|
This is the default if a converter isn't included in the expression.
|
||||||
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
|
* ``int`` - Matches zero or any positive integer. Returns an `int`.
|
||||||
|
|
||||||
|
* ``slug`` - Matches any slug string consisting of ASCII letters or numbers,
|
||||||
|
plus the hyphen and underscore characters. For example,
|
||||||
|
``building-your-1st-django-site``.
|
||||||
|
|
||||||
|
* ``uuid`` - Matches a formatted UUID. For example,
|
||||||
|
``075194d3-6885-417e-a8a8-6c931e272f00``. Returns a :class:`~uuid.UUID`
|
||||||
|
instance.
|
||||||
|
|
||||||
|
* ``path`` - Matches any non-empty string, including the path separator,
|
||||||
|
``'/'``. This allows you to match against a complete URL path rather than
|
||||||
|
just a segment of a URL path as with ``str``.
|
||||||
|
|
||||||
|
.. _registering-custom-path-converters:
|
||||||
|
|
||||||
|
Registering custom path converters
|
||||||
|
==================================
|
||||||
|
|
||||||
|
For more complex matching requirements, you can define your own path converters.
|
||||||
|
|
||||||
|
A converter is a class that includes the following:
|
||||||
|
|
||||||
|
* A ``regex`` class attribute, as a string.
|
||||||
|
|
||||||
|
* A ``to_python(self, value)`` method, which handles converting the matched
|
||||||
|
string into the type that should be passed to the view function. It should
|
||||||
|
raise ``ValueError`` if it can't convert the given value.
|
||||||
|
|
||||||
|
* A ``to_url(self, value)`` method, which handles converting the Python type
|
||||||
|
into a string to be used in the URL.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
class FourDigitYearConverter:
|
||||||
|
regex = '[0-9]{4}'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return '%04d' % value
|
||||||
|
|
||||||
|
Register custom converter classes in your URLconf using
|
||||||
|
:func:`~django.urls.register_converter`::
|
||||||
|
|
||||||
|
from django.urls import register_converter, path
|
||||||
|
|
||||||
|
from . import converters, views
|
||||||
|
|
||||||
|
register_converter(converters.FourDigitYearConverter, 'yyyy')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('articles/2003/', views.special_case_2003),
|
||||||
|
path('articles/<yyyy:year>/', views.year_archive),
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
Using regular expressions
|
||||||
|
=========================
|
||||||
|
|
||||||
|
If the paths and converters syntax isn't sufficient for defining your URL
|
||||||
|
patterns, you can also use regular expressions. To do so, use
|
||||||
|
:func:`~django.urls.re_path` instead of :func:`~django.urls.path`.
|
||||||
|
|
||||||
|
In Python regular expressions, the syntax for named regular expression groups
|
||||||
is ``(?P<name>pattern)``, where ``name`` is the name of the group and
|
is ``(?P<name>pattern)``, where ``name`` is the name of the group and
|
||||||
``pattern`` is some pattern to match.
|
``pattern`` is some pattern to match.
|
||||||
|
|
||||||
Here's the above example URLconf, rewritten to use named groups::
|
Here's the example URLconf from earlier, rewritten using regular expressions::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^articles/2003/$', views.special_case_2003),
|
path('articles/2003/', views.special_case_2003),
|
||||||
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
|
re_path('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),
|
re_path('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),
|
re_path('articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[^/]+)/', views.article_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
This accomplishes exactly the same thing as the previous example, with one
|
This accomplishes roughly the same thing as the previous example, except:
|
||||||
subtle difference: The captured values are passed to view functions as keyword
|
|
||||||
arguments rather than positional arguments. For example:
|
|
||||||
|
|
||||||
* A request to ``/articles/2005/03/`` would call the function
|
* The exact URLs that will match are slightly more constrained. For example,
|
||||||
``views.month_archive(request, year='2005', month='03')``, instead
|
the year 10000 will no longer match since the year integers are constrained
|
||||||
of ``views.month_archive(request, '2005', '03')``.
|
to be exactly four digits long.
|
||||||
|
|
||||||
* A request to ``/articles/2003/03/03/`` would call the function
|
* Each captured argument is sent to the view as a string, regardless of what
|
||||||
``views.article_detail(request, year='2003', month='03', day='03')``.
|
sort of match the regular expression makes.
|
||||||
|
|
||||||
In practice, this means your URLconfs are slightly more explicit and less prone
|
When switching from using :func:`~django.urls.path` to
|
||||||
to argument-order bugs -- and you can reorder the arguments in your views'
|
:func:`~django.urls.re_path` or vice versa, it's particularly important to be
|
||||||
function definitions. Of course, these benefits come at the cost of brevity;
|
aware that the type of the view arguments may change, and so you may need to
|
||||||
some developers find the named-group syntax ugly and too verbose.
|
adapt your views.
|
||||||
|
|
||||||
The matching/grouping algorithm
|
Using unnamed regular expression groups
|
||||||
-------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Here's the algorithm the URLconf parser follows, with respect to named groups
|
As well as the named group syntax, e.g. ``(?P<year>[0-9]{4})``, you can
|
||||||
vs. non-named groups in a regular expression:
|
also use the shorter unnamed group, e.g. ``([0-9]{4})``.
|
||||||
|
|
||||||
1. If there are any named arguments, it will use those, ignoring non-named
|
This usage isn't particularly recommended as it makes it easier to accidentally
|
||||||
arguments.
|
introduce errors between the intended meaning of a match and the arguments
|
||||||
|
of the view.
|
||||||
|
|
||||||
2. Otherwise, it will pass all non-named arguments as positional arguments.
|
In either case, using only one style within an given regex is recommended. When
|
||||||
|
both styles are mixed, any unnamed groups are ignored and only named groups are
|
||||||
|
passed to the view function.
|
||||||
|
|
||||||
In both cases, any extra keyword arguments that have been given as per `Passing
|
Nested arguments
|
||||||
extra options to view functions`_ (below) will also be passed to the view.
|
----------------
|
||||||
|
|
||||||
|
Regular expressions allow nested arguments, and Django will resolve them and
|
||||||
|
pass them to the view. When reversing, Django will try to fill in all outer
|
||||||
|
captured arguments, ignoring any nested captured arguments. Consider the
|
||||||
|
following URL patterns which optionally take a page argument::
|
||||||
|
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'blog/(page-(\d+)/)?$', blog_articles), # bad
|
||||||
|
re_path(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
|
||||||
|
]
|
||||||
|
|
||||||
|
Both patterns use nested arguments and will resolve: for example,
|
||||||
|
``blog/page-2/`` will result in a match to ``blog_articles`` with two
|
||||||
|
positional arguments: ``page-2/`` and ``2``. The second pattern for
|
||||||
|
``comments`` will match ``comments/page-2/`` with keyword argument
|
||||||
|
``page_number`` set to 2. The outer argument in this case is a non-capturing
|
||||||
|
argument ``(?:...)``.
|
||||||
|
|
||||||
|
The ``blog_articles`` view needs the outermost captured argument to be reversed,
|
||||||
|
``page-2/`` or no arguments in this case, while ``comments`` can be reversed
|
||||||
|
with either no arguments or a value for ``page_number``.
|
||||||
|
|
||||||
|
Nested captured arguments create a strong coupling between the view arguments
|
||||||
|
and the URL as illustrated by ``blog_articles``: the view receives part of the
|
||||||
|
URL (``page-2/``) instead of only the value the view is interested in. This
|
||||||
|
coupling is even more pronounced when reversing, since to reverse the view we
|
||||||
|
need to pass the piece of URL instead of the page number.
|
||||||
|
|
||||||
|
As a rule of thumb, only capture the values the view needs to work with and
|
||||||
|
use non-capturing arguments when the regular expression needs an argument but
|
||||||
|
the view ignores it.
|
||||||
|
|
||||||
What the URLconf searches against
|
What the URLconf searches against
|
||||||
=================================
|
=================================
|
||||||
|
@ -189,18 +283,6 @@ 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
|
methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same
|
||||||
function for the same URL.
|
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'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
|
|
||||||
|
|
||||||
...the ``year`` argument passed 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
|
Specifying defaults for view arguments
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
@ -208,25 +290,25 @@ A convenient trick is to specify default parameters for your views' arguments.
|
||||||
Here's an example URLconf and view::
|
Here's an example URLconf and view::
|
||||||
|
|
||||||
# URLconf
|
# URLconf
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^blog/$', views.page),
|
path('blog/', views.page),
|
||||||
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
|
path('blog/page<int:num>/', views.page),
|
||||||
]
|
]
|
||||||
|
|
||||||
# View (in blog/views.py)
|
# View (in blog/views.py)
|
||||||
def page(request, num="1"):
|
def page(request, num=1):
|
||||||
# Output the appropriate page of blog entries, according to num.
|
# Output the appropriate page of blog entries, according to num.
|
||||||
...
|
...
|
||||||
|
|
||||||
In the above example, both URL patterns point to the same view --
|
In the above example, both URL patterns point to the same view --
|
||||||
``views.page`` -- but the first pattern doesn't capture anything from the
|
``views.page`` -- but the first pattern doesn't capture anything from the
|
||||||
URL. If the first pattern matches, the ``page()`` function will use its
|
URL. If the first pattern matches, the ``page()`` function will use its
|
||||||
default argument for ``num``, ``"1"``. If the second pattern matches,
|
default argument for ``num``, ``1``. If the second pattern matches,
|
||||||
``page()`` will use whatever ``num`` value was captured by the regex.
|
``page()`` will use whatever ``num`` value was captured.
|
||||||
|
|
||||||
Performance
|
Performance
|
||||||
===========
|
===========
|
||||||
|
@ -237,14 +319,14 @@ accessed. This makes the system blazingly fast.
|
||||||
Syntax of the ``urlpatterns`` variable
|
Syntax of the ``urlpatterns`` variable
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
``urlpatterns`` should be a Python list of :func:`~django.conf.urls.url`
|
``urlpatterns`` should be a Python list of :func:`~django.urls.path` and/or
|
||||||
instances.
|
:func:`~django.urls.re_path` instances.
|
||||||
|
|
||||||
Error handling
|
Error handling
|
||||||
==============
|
==============
|
||||||
|
|
||||||
When Django can't find a regex matching the requested URL, or when an
|
When Django can't find a match for the requested URL, or when an exception is
|
||||||
exception is raised, Django will invoke an error-handling view.
|
raised, Django invokes an error-handling view.
|
||||||
|
|
||||||
The views to use for these cases are specified by four variables. Their
|
The views to use for these cases are specified by four variables. Their
|
||||||
default values should suffice for most projects, but further customization is
|
default values should suffice for most projects, but further customization is
|
||||||
|
@ -277,39 +359,37 @@ essentially "roots" a set of URLs below other ones.
|
||||||
For example, here's an excerpt of the URLconf for the `Django website`_
|
For example, here's an excerpt of the URLconf for the `Django website`_
|
||||||
itself. It includes a number of other URLconfs::
|
itself. It includes a number of other URLconfs::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# ... snip ...
|
# ... snip ...
|
||||||
url(r'^community/', include('django_website.aggregator.urls')),
|
path('community/', include('aggregator.urls')),
|
||||||
url(r'^contact/', include('django_website.contact.urls')),
|
path('contact/', include('contact.urls')),
|
||||||
# ... snip ...
|
# ... snip ...
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that the regular expressions in this example don't have a ``$``
|
Whenever Django encounters :func:`~django.urls.include()`, it chops off
|
||||||
(end-of-string match character) but do include a trailing slash. Whenever
|
whatever part of the URL matched up to that point and sends the remaining
|
||||||
Django encounters ``include()`` (:func:`django.conf.urls.include()`), it chops
|
|
||||||
off whatever part of the URL matched up to that point and sends the remaining
|
|
||||||
string to the included URLconf for further processing.
|
string to the included URLconf for further processing.
|
||||||
|
|
||||||
Another possibility is to include additional URL patterns by using a list of
|
Another possibility is to include additional URL patterns by using a list of
|
||||||
:func:`~django.conf.urls.url` instances. For example, consider this URLconf::
|
:func:`~django.urls.path` instances. For example, consider this URLconf::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
from apps.main import views as main_views
|
from apps.main import views as main_views
|
||||||
from credit import views as credit_views
|
from credit import views as credit_views
|
||||||
|
|
||||||
extra_patterns = [
|
extra_patterns = [
|
||||||
url(r'^reports/$', credit_views.report),
|
path('reports/', credit_views.report),
|
||||||
url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report),
|
path('reports/<int:id>/', credit_views.report),
|
||||||
url(r'^charge/$', credit_views.charge),
|
path('charge/', credit_views.charge),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', main_views.homepage),
|
path('', main_views.homepage),
|
||||||
url(r'^help/', include('apps.help.urls')),
|
path('help/', include('apps.help.urls')),
|
||||||
url(r'^credit/', include(extra_patterns)),
|
path('credit/', include(extra_patterns)),
|
||||||
]
|
]
|
||||||
|
|
||||||
In this example, the ``/credit/reports/`` URL will be handled by the
|
In this example, the ``/credit/reports/`` URL will be handled by the
|
||||||
|
@ -318,28 +398,28 @@ In this example, the ``/credit/reports/`` URL will be handled by the
|
||||||
This can be used to remove redundancy from URLconfs where a single pattern
|
This can be used to remove redundancy from URLconfs where a single pattern
|
||||||
prefix is used repeatedly. For example, consider this URLconf::
|
prefix is used repeatedly. For example, consider this URLconf::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history),
|
path('<page_slug>-<page_id>/history/', views.history),
|
||||||
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit),
|
path('<page_slug>-<page_id>/edit/', views.edit),
|
||||||
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss),
|
path('<page_slug>-<page_id>/discuss/', views.discuss),
|
||||||
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/permissions/$', views.permissions),
|
path('<page_slug>-<page_id>/permissions/', views.permissions),
|
||||||
]
|
]
|
||||||
|
|
||||||
We can improve this by stating the common path prefix only once and grouping
|
We can improve this by stating the common path prefix only once and grouping
|
||||||
the suffixes that differ::
|
the suffixes that differ::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/', include([
|
path('<page_slug>-<page_id>/', include([
|
||||||
url(r'^history/$', views.history),
|
path('history/', views.history),
|
||||||
url(r'^edit/$', views.edit),
|
path('edit/', views.edit),
|
||||||
url(r'^discuss/$', views.discuss),
|
path('discuss/', views.discuss),
|
||||||
url(r'^permissions/$', views.permissions),
|
path('permissions/', views.permissions),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -352,60 +432,24 @@ An included URLconf receives any captured parameters from parent URLconfs, so
|
||||||
the following example is valid::
|
the following example is valid::
|
||||||
|
|
||||||
# In settings/urls/main.py
|
# In settings/urls/main.py
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
|
path('<username>/blog/', include('foo.urls.blog')),
|
||||||
]
|
]
|
||||||
|
|
||||||
# In foo/urls/blog.py
|
# In foo/urls/blog.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.blog.index),
|
path('', views.blog.index),
|
||||||
url(r'^archive/$', views.blog.archive),
|
path('archive/', views.blog.archive),
|
||||||
]
|
]
|
||||||
|
|
||||||
In the above example, the captured ``"username"`` variable is passed to the
|
In the above example, the captured ``"username"`` variable is passed to the
|
||||||
included URLconf, as expected.
|
included URLconf, as expected.
|
||||||
|
|
||||||
Nested arguments
|
|
||||||
================
|
|
||||||
|
|
||||||
Regular expressions allow nested arguments, and Django will resolve them and
|
|
||||||
pass them to the view. When reversing, Django will try to fill in all outer
|
|
||||||
captured arguments, ignoring any nested captured arguments. Consider the
|
|
||||||
following URL patterns which optionally take a page argument::
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
|
|
||||||
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
|
|
||||||
]
|
|
||||||
|
|
||||||
Both patterns use nested arguments and will resolve: for example,
|
|
||||||
``blog/page-2/`` will result in a match to ``blog_articles`` with two
|
|
||||||
positional arguments: ``page-2/`` and ``2``. The second pattern for
|
|
||||||
``comments`` will match ``comments/page-2/`` with keyword argument
|
|
||||||
``page_number`` set to 2. The outer argument in this case is a non-capturing
|
|
||||||
argument ``(?:...)``.
|
|
||||||
|
|
||||||
The ``blog_articles`` view needs the outermost captured argument to be reversed,
|
|
||||||
``page-2/`` or no arguments in this case, while ``comments`` can be reversed
|
|
||||||
with either no arguments or a value for ``page_number``.
|
|
||||||
|
|
||||||
Nested captured arguments create a strong coupling between the view arguments
|
|
||||||
and the URL as illustrated by ``blog_articles``: the view receives part of the
|
|
||||||
URL (``page-2/``) instead of only the value the view is interested in. This
|
|
||||||
coupling is even more pronounced when reversing, since to reverse the view we
|
|
||||||
need to pass the piece of URL instead of the page number.
|
|
||||||
|
|
||||||
As a rule of thumb, only capture the values the view needs to work with and
|
|
||||||
use non-capturing arguments when the regular expression needs an argument but
|
|
||||||
the view ignores it.
|
|
||||||
|
|
||||||
.. _views-extra-options:
|
.. _views-extra-options:
|
||||||
|
|
||||||
Passing extra options to view functions
|
Passing extra options to view functions
|
||||||
|
@ -414,21 +458,21 @@ Passing extra options to view functions
|
||||||
URLconfs have a hook that lets you pass extra arguments to your view functions,
|
URLconfs have a hook that lets you pass extra arguments to your view functions,
|
||||||
as a Python dictionary.
|
as a Python dictionary.
|
||||||
|
|
||||||
The :func:`django.conf.urls.url` function can take an optional third argument
|
The :func:`~django.urls.path` function can take an optional third argument
|
||||||
which should be a dictionary of extra keyword arguments to pass to the view
|
which should be a dictionary of extra keyword arguments to pass to the view
|
||||||
function.
|
function.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
|
path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
|
||||||
]
|
]
|
||||||
|
|
||||||
In this example, for a request to ``/blog/2005/``, Django will call
|
In this example, for a request to ``/blog/2005/``, Django will call
|
||||||
``views.year_archive(request, year='2005', foo='bar')``.
|
``views.year_archive(request, year=2005, foo='bar')``.
|
||||||
|
|
||||||
This technique is used in the
|
This technique is used in the
|
||||||
:doc:`syndication framework </ref/contrib/syndication>` to pass metadata and
|
:doc:`syndication framework </ref/contrib/syndication>` to pass metadata and
|
||||||
|
@ -444,46 +488,45 @@ options to views.
|
||||||
Passing extra options to ``include()``
|
Passing extra options to ``include()``
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
Similarly, you can pass extra options to :func:`~django.conf.urls.include`.
|
Similarly, you can pass extra options to :func:`~django.urls.include` and
|
||||||
When you pass extra options to ``include()``, *each* line in the included
|
each line in the included URLconf will be passed the extra options.
|
||||||
URLconf will be passed the extra options.
|
|
||||||
|
|
||||||
For example, these two URLconf sets are functionally identical:
|
For example, these two URLconf sets are functionally identical:
|
||||||
|
|
||||||
Set one::
|
Set one::
|
||||||
|
|
||||||
# main.py
|
# main.py
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^blog/', include('inner'), {'blogid': 3}),
|
path('blog/', include('inner'), {'blog_id': 3}),
|
||||||
]
|
]
|
||||||
|
|
||||||
# inner.py
|
# inner.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
from mysite import views
|
from mysite import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^archive/$', views.archive),
|
path('archive/', views.archive),
|
||||||
url(r'^about/$', views.about),
|
path('about/', views.about),
|
||||||
]
|
]
|
||||||
|
|
||||||
Set two::
|
Set two::
|
||||||
|
|
||||||
# main.py
|
# main.py
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
from mysite import views
|
from mysite import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^blog/', include('inner')),
|
path('blog/', include('inner')),
|
||||||
]
|
]
|
||||||
|
|
||||||
# inner.py
|
# inner.py
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^archive/$', views.archive, {'blogid': 3}),
|
path('archive/', views.archive, {'blog_id': 3}),
|
||||||
url(r'^about/$', views.about, {'blogid': 3}),
|
path('about/', views.about, {'blog_id': 3}),
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that extra options will *always* be passed to *every* line in the included
|
Note that extra options will *always* be passed to *every* line in the included
|
||||||
|
@ -543,18 +586,18 @@ Examples
|
||||||
|
|
||||||
Consider again this URLconf entry::
|
Consider again this URLconf entry::
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#...
|
#...
|
||||||
url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
|
path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
|
||||||
#...
|
#...
|
||||||
]
|
]
|
||||||
|
|
||||||
According to this design, the URL for the archive corresponding to year *nnnn*
|
According to this design, the URL for the archive corresponding to year *nnnn*
|
||||||
is ``/articles/nnnn/``.
|
is ``/articles/<nnnn>/``.
|
||||||
|
|
||||||
You can obtain these in template code by using:
|
You can obtain these in template code by using:
|
||||||
|
|
||||||
|
@ -720,24 +763,24 @@ displaying polls.
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: urls.py
|
:filename: urls.py
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^author-polls/', include('polls.urls', namespace='author-polls')),
|
path('author-polls/', include('polls.urls', namespace='author-polls')),
|
||||||
url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')),
|
path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'polls'
|
app_name = 'polls'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
|
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -783,60 +826,61 @@ Application namespaces of included URLconfs can be specified in two ways.
|
||||||
|
|
||||||
Firstly, you can set an ``app_name`` attribute in the included URLconf module,
|
Firstly, you can set an ``app_name`` attribute in the included URLconf module,
|
||||||
at the same level as the ``urlpatterns`` attribute. You have to pass the actual
|
at the same level as the ``urlpatterns`` attribute. You have to pass the actual
|
||||||
module, or a string reference to the module, to
|
module, or a string reference to the module, to :func:`~django.urls.include`,
|
||||||
:func:`~django.conf.urls.include`, not the list of ``urlpatterns`` itself.
|
not the list of ``urlpatterns`` itself.
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/urls.py
|
:filename: polls/urls.py
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = 'polls'
|
app_name = 'polls'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
|
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: urls.py
|
:filename: urls.py
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^polls/', include('polls.urls')),
|
path('polls/', include('polls.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
The URLs defined in ``polls.urls`` will have an application namespace ``polls``.
|
The URLs defined in ``polls.urls`` will have an application namespace ``polls``.
|
||||||
|
|
||||||
Secondly, you can include an object that contains embedded namespace data. If
|
Secondly, you can include an object that contains embedded namespace data. If
|
||||||
you ``include()`` a list of :func:`~django.conf.urls.url` instances,
|
you ``include()`` a list of :func:`~django.urls.path` or
|
||||||
the URLs contained in that object will be added to the global namespace.
|
:func:`~django.urls.re_path` instances, the URLs contained in that object
|
||||||
However, you can also ``include()`` a 2-tuple containing::
|
will be added to the global namespace. However, you can also ``include()`` a
|
||||||
|
2-tuple containing::
|
||||||
|
|
||||||
(<list of url() instances>, <application namespace>)
|
(<list of path()/re_path() instances>, <application namespace>)
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.urls import include, path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
polls_patterns = ([
|
polls_patterns = ([
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
|
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
|
||||||
], 'polls')
|
], 'polls')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^polls/', include(polls_patterns)),
|
path('polls/', include(polls_patterns)),
|
||||||
]
|
]
|
||||||
|
|
||||||
This will include the nominated URL patterns into the given application
|
This will include the nominated URL patterns into the given application
|
||||||
namespace.
|
namespace.
|
||||||
|
|
||||||
The instance namespace can be specified using the ``namespace`` argument to
|
The instance namespace can be specified using the ``namespace`` argument to
|
||||||
:func:`~django.conf.urls.include`. If the instance namespace is not specified,
|
:func:`~django.urls.include`. If the instance namespace is not specified,
|
||||||
it will default to the included URLconf's application namespace. This means
|
it will default to the included URLconf's application namespace. This means
|
||||||
it will also be the default instance for that namespace.
|
it will also be the default instance for that namespace.
|
||||||
|
|
|
@ -992,13 +992,13 @@ The ``JavaScriptCatalog`` view
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
||||||
]
|
]
|
||||||
|
|
||||||
**Example with custom packages**::
|
**Example with custom packages**::
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^jsi18n/myapp/$',
|
path('jsi18n/myapp/',
|
||||||
JavaScriptCatalog.as_view(packages=['your.app.label']),
|
JavaScriptCatalog.as_view(packages=['your.app.label']),
|
||||||
name='javascript-catalog'),
|
name='javascript-catalog'),
|
||||||
]
|
]
|
||||||
|
@ -1012,7 +1012,7 @@ The ``JavaScriptCatalog`` view
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
|
||||||
urlpatterns = i18n_patterns(
|
urlpatterns = i18n_patterns(
|
||||||
url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
||||||
)
|
)
|
||||||
|
|
||||||
The precedence of translations is such that the packages appearing later in the
|
The precedence of translations is such that the packages appearing later in the
|
||||||
|
@ -1235,7 +1235,7 @@ URL::
|
||||||
|
|
||||||
# The value returned by get_version() must change when translations change.
|
# The value returned by get_version() must change when translations change.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^jsi18n/$',
|
path('jsi18n/',
|
||||||
cache_page(86400, key_prefix='js18n-%s' % get_version())(JavaScriptCatalog.as_view()),
|
cache_page(86400, key_prefix='js18n-%s' % get_version())(JavaScriptCatalog.as_view()),
|
||||||
name='javascript-catalog'),
|
name='javascript-catalog'),
|
||||||
]
|
]
|
||||||
|
@ -1253,7 +1253,7 @@ whenever you restart your application server::
|
||||||
last_modified_date = timezone.now()
|
last_modified_date = timezone.now()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^jsi18n/$',
|
path('jsi18n/',
|
||||||
last_modified(lambda req, **kw: last_modified_date)(JavaScriptCatalog.as_view()),
|
last_modified(lambda req, **kw: last_modified_date)(JavaScriptCatalog.as_view()),
|
||||||
name='javascript-catalog'),
|
name='javascript-catalog'),
|
||||||
]
|
]
|
||||||
|
@ -1302,26 +1302,26 @@ translations to existing site so that the current URLs won't change.
|
||||||
|
|
||||||
Example URL patterns::
|
Example URL patterns::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.urls import include, url
|
||||||
|
|
||||||
from about import views as about_views
|
from about import views as about_views
|
||||||
from news import views as news_views
|
from news import views as news_views
|
||||||
from sitemap.views import sitemap
|
from sitemap.views import sitemap
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sitemap\.xml$', sitemap, name='sitemap-xml'),
|
path('sitemap.xml', sitemap, name='sitemap-xml'),
|
||||||
]
|
]
|
||||||
|
|
||||||
news_patterns = ([
|
news_patterns = ([
|
||||||
url(r'^$', news_views.index, name='index'),
|
path('', news_views.index, name='index'),
|
||||||
url(r'^category/(?P<slug>[\w-]+)/$', news_views.category, name='category'),
|
path('category/<slug>/', news_views.category, name='category'),
|
||||||
url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'),
|
path('<slug>/', news_views.details, name='detail'),
|
||||||
], 'news')
|
], 'news')
|
||||||
|
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
url(r'^about/$', about_views.main, name='about'),
|
path('about/', about_views.main, name='about'),
|
||||||
url(r'^news/', include(news_patterns, namespace='news')),
|
path('news/', include(news_patterns, namespace='news')),
|
||||||
)
|
)
|
||||||
|
|
||||||
After defining these URL patterns, Django will automatically add the
|
After defining these URL patterns, Django will automatically add the
|
||||||
|
@ -1371,8 +1371,8 @@ Translating URL patterns
|
||||||
URL patterns can also be marked translatable using the
|
URL patterns can also be marked translatable using the
|
||||||
:func:`~django.utils.translation.gettext_lazy` function. Example::
|
:func:`~django.utils.translation.gettext_lazy` function. Example::
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.urls import include, path
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from about import views as about_views
|
from about import views as about_views
|
||||||
|
@ -1380,18 +1380,18 @@ URL patterns can also be marked translatable using the
|
||||||
from sitemaps.views import sitemap
|
from sitemaps.views import sitemap
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sitemap\.xml$', sitemap, name='sitemap-xml'),
|
path('sitemap.xml', sitemap, name='sitemap-xml'),
|
||||||
]
|
]
|
||||||
|
|
||||||
news_patterns = ([
|
news_patterns = ([
|
||||||
url(r'^$', news_views.index, name='index'),
|
path('', news_views.index, name='index'),
|
||||||
url(_(r'^category/(?P<slug>[\w-]+)/$'), news_views.category, name='category'),
|
path(_('category/<slug>/'), news_views.category, name='category'),
|
||||||
url(r'^(?P<slug>[\w-]+)/$', news_views.details, name='detail'),
|
path('<slug>/', news_views.details, name='detail'),
|
||||||
], 'news')
|
], 'news')
|
||||||
|
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
url(_(r'^about/$'), about_views.main, name='about'),
|
path(_('about/'), about_views.main, name='about'),
|
||||||
url(_(r'^news/'), include(news_patterns, namespace='news')),
|
path(_('news/'), include(news_patterns, namespace='news')),
|
||||||
)
|
)
|
||||||
|
|
||||||
After you've created the translations, the :func:`~django.urls.reverse`
|
After you've created the translations, the :func:`~django.urls.reverse`
|
||||||
|
@ -1750,7 +1750,7 @@ back to the previous page.
|
||||||
|
|
||||||
Activate this view by adding the following line to your URLconf::
|
Activate this view by adding the following line to your URLconf::
|
||||||
|
|
||||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
path('i18n/', include('django.conf.urls.i18n')),
|
||||||
|
|
||||||
(Note that this example makes the view available at ``/i18n/setlang/``.)
|
(Note that this example makes the view available at ``/i18n/setlang/``.)
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,11 @@ class CheckUrlConfigTests(SimpleTestCase):
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
warning = result[0]
|
warning = result[0]
|
||||||
self.assertEqual(warning.id, 'urls.W001')
|
self.assertEqual(warning.id, 'urls.W001')
|
||||||
expected_msg = "Your URL pattern '^include-with-dollar$' uses include with a regex ending with a '$'."
|
self.assertEqual(warning.msg, (
|
||||||
self.assertIn(expected_msg, warning.msg)
|
"Your URL pattern '^include-with-dollar$' uses include with a "
|
||||||
|
"route ending with a '$'. Remove the dollar from the route to "
|
||||||
|
"avoid problems including URLs."
|
||||||
|
))
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='check_framework.urls.contains_tuple')
|
@override_settings(ROOT_URLCONF='check_framework.urls.contains_tuple')
|
||||||
def test_contains_tuple_not_url_instance(self):
|
def test_contains_tuple_not_url_instance(self):
|
||||||
|
@ -42,23 +45,33 @@ class CheckUrlConfigTests(SimpleTestCase):
|
||||||
self.assertEqual(warning.id, 'urls.E004')
|
self.assertEqual(warning.id, 'urls.E004')
|
||||||
self.assertRegex(warning.msg, (
|
self.assertRegex(warning.msg, (
|
||||||
r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) is "
|
r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) is "
|
||||||
r"invalid. Ensure that urlpatterns is a list of url\(\) instances.$"
|
r"invalid. Ensure that urlpatterns is a list of path\(\) and/or re_path\(\) "
|
||||||
|
r"instances\.$"
|
||||||
|
))
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='check_framework.urls.include_contains_tuple')
|
||||||
|
def test_contains_included_tuple(self):
|
||||||
|
result = check_url_config(None)
|
||||||
|
warning = result[0]
|
||||||
|
self.assertEqual(warning.id, 'urls.E004')
|
||||||
|
self.assertRegex(warning.msg, (
|
||||||
|
r"^Your URL pattern \('\^tuple/\$', <function <lambda> at 0x(\w+)>\) is "
|
||||||
|
r"invalid. Ensure that urlpatterns is a list of path\(\) and/or re_path\(\) "
|
||||||
|
r"instances\.$"
|
||||||
))
|
))
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='check_framework.urls.beginning_with_slash')
|
@override_settings(ROOT_URLCONF='check_framework.urls.beginning_with_slash')
|
||||||
def test_beginning_with_slash(self):
|
def test_beginning_with_slash(self):
|
||||||
result = check_url_config(None)
|
msg = (
|
||||||
self.assertEqual(len(result), 1)
|
"Your URL pattern '%s' has a route beginning with a '/'. Remove "
|
||||||
warning = result[0]
|
"this slash as it is unnecessary. If this pattern is targeted in "
|
||||||
self.assertEqual(warning.id, 'urls.W002')
|
"an include(), ensure the include() pattern has a trailing '/'."
|
||||||
expected_msg = (
|
|
||||||
"Your URL pattern '/starting-with-slash/$' has a regex beginning "
|
|
||||||
"with a '/'. Remove this slash as it is unnecessary. If this "
|
|
||||||
"pattern is targeted in an include(), ensure the include() pattern "
|
|
||||||
"has a trailing '/'."
|
|
||||||
)
|
)
|
||||||
|
warning1, warning2 = check_url_config(None)
|
||||||
self.assertIn(expected_msg, warning.msg)
|
self.assertEqual(warning1.id, 'urls.W002')
|
||||||
|
self.assertEqual(warning1.msg, msg % '/path-starting-with-slash/')
|
||||||
|
self.assertEqual(warning2.id, 'urls.W002')
|
||||||
|
self.assertEqual(warning2.msg, msg % '/url-starting-with-slash/$')
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
ROOT_URLCONF='check_framework.urls.beginning_with_slash',
|
ROOT_URLCONF='check_framework.urls.beginning_with_slash',
|
||||||
|
@ -95,7 +108,7 @@ class CheckUrlConfigTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_get_warning_for_invalid_pattern_tuple(self):
|
def test_get_warning_for_invalid_pattern_tuple(self):
|
||||||
warning = get_warning_for_invalid_pattern((r'^$', lambda x: x))[0]
|
warning = get_warning_for_invalid_pattern((r'^$', lambda x: x))[0]
|
||||||
self.assertEqual(warning.hint, "Try using url() instead of a tuple.")
|
self.assertEqual(warning.hint, "Try using path() instead of a tuple.")
|
||||||
|
|
||||||
def test_get_warning_for_invalid_pattern_other(self):
|
def test_get_warning_for_invalid_pattern_other(self):
|
||||||
warning = get_warning_for_invalid_pattern(object())[0]
|
warning = get_warning_for_invalid_pattern(object())[0]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'/starting-with-slash/$', lambda x: x),
|
path('/path-starting-with-slash/', lambda x: x),
|
||||||
|
url(r'/url-starting-with-slash/$', lambda x: x),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include([(r'^tuple/$', lambda x: x)])),
|
||||||
|
]
|
|
@ -1,6 +1,6 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.urls import path, re_path
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
@ -9,288 +9,212 @@ from .models import Book
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# TemplateView
|
# TemplateView
|
||||||
url(r'^template/no_template/$',
|
path('template/no_template/', TemplateView.as_view()),
|
||||||
TemplateView.as_view()),
|
path('template/login_required/', login_required(TemplateView.as_view())),
|
||||||
url(r'^template/login_required/$',
|
path('template/simple/<foo>/', TemplateView.as_view(template_name='generic_views/about.html')),
|
||||||
login_required(TemplateView.as_view())),
|
path('template/custom/<foo>/', views.CustomTemplateView.as_view(template_name='generic_views/about.html')),
|
||||||
url(r'^template/simple/(?P<foo>\w+)/$',
|
path(
|
||||||
TemplateView.as_view(template_name='generic_views/about.html')),
|
'template/content_type/',
|
||||||
url(r'^template/custom/(?P<foo>\w+)/$',
|
TemplateView.as_view(template_name='generic_views/robots.txt', content_type='text/plain'),
|
||||||
views.CustomTemplateView.as_view(template_name='generic_views/about.html')),
|
),
|
||||||
url(r'^template/content_type/$',
|
path(
|
||||||
TemplateView.as_view(template_name='generic_views/robots.txt', content_type='text/plain')),
|
'template/cached/<foo>/',
|
||||||
|
cache_page(2.0)(TemplateView.as_view(template_name='generic_views/about.html')),
|
||||||
url(r'^template/cached/(?P<foo>\w+)/$',
|
),
|
||||||
cache_page(2.0)(TemplateView.as_view(template_name='generic_views/about.html'))),
|
path(
|
||||||
url(r'^template/extra_context/$',
|
'template/extra_context/',
|
||||||
TemplateView.as_view(template_name='generic_views/about.html', extra_context={'title': 'Title'})),
|
TemplateView.as_view(template_name='generic_views/about.html', extra_context={'title': 'Title'}),
|
||||||
|
),
|
||||||
|
|
||||||
# DetailView
|
# DetailView
|
||||||
url(r'^detail/obj/$',
|
path('detail/obj/', views.ObjectDetail.as_view()),
|
||||||
views.ObjectDetail.as_view()),
|
path('detail/artist/<int:pk>/', views.ArtistDetail.as_view(), name='artist_detail'),
|
||||||
url(r'^detail/artist/(?P<pk>[0-9]+)/$',
|
path('detail/author/<int:pk>/', views.AuthorDetail.as_view(), name='author_detail'),
|
||||||
views.ArtistDetail.as_view(),
|
path('detail/author/bycustompk/<foo>/', views.AuthorDetail.as_view(pk_url_kwarg='foo')),
|
||||||
name="artist_detail"),
|
path('detail/author/byslug/<slug>/', views.AuthorDetail.as_view()),
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/$',
|
path('detail/author/bycustomslug/<foo>/', views.AuthorDetail.as_view(slug_url_kwarg='foo')),
|
||||||
views.AuthorDetail.as_view(),
|
path('detail/author/bypkignoreslug/<int:pk>-<slug>/', views.AuthorDetail.as_view()),
|
||||||
name="author_detail"),
|
path('detail/author/bypkandslug/<int:pk>-<slug>/', views.AuthorDetail.as_view(query_pk_and_slug=True)),
|
||||||
url(r'^detail/author/bycustompk/(?P<foo>[0-9]+)/$',
|
path('detail/author/<int:pk>/template_name_suffix/', views.AuthorDetail.as_view(template_name_suffix='_view')),
|
||||||
views.AuthorDetail.as_view(pk_url_kwarg='foo')),
|
path(
|
||||||
url(r'^detail/author/byslug/(?P<slug>[\w-]+)/$',
|
'detail/author/<int:pk>/template_name/',
|
||||||
views.AuthorDetail.as_view()),
|
views.AuthorDetail.as_view(template_name='generic_views/about.html'),
|
||||||
url(r'^detail/author/bycustomslug/(?P<foo>[\w-]+)/$',
|
),
|
||||||
views.AuthorDetail.as_view(slug_url_kwarg='foo')),
|
path('detail/author/<int:pk>/context_object_name/', views.AuthorDetail.as_view(context_object_name='thingy')),
|
||||||
url(r'^detail/author/bypkignoreslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$',
|
path('detail/author/<int:pk>/custom_detail/', views.AuthorCustomDetail.as_view()),
|
||||||
views.AuthorDetail.as_view()),
|
path('detail/author/<int:pk>/dupe_context_object_name/', views.AuthorDetail.as_view(context_object_name='object')),
|
||||||
url(r'^detail/author/bypkandslug/(?P<pk>[0-9]+)-(?P<slug>[\w-]+)/$',
|
path('detail/page/<int:pk>/field/', views.PageDetail.as_view()),
|
||||||
views.AuthorDetail.as_view(query_pk_and_slug=True)),
|
path(r'detail/author/invalid/url/', views.AuthorDetail.as_view()),
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/template_name_suffix/$',
|
path('detail/author/invalid/qs/', views.AuthorDetail.as_view(queryset=None)),
|
||||||
views.AuthorDetail.as_view(template_name_suffix='_view')),
|
path('detail/nonmodel/1/', views.NonModelDetail.as_view()),
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/template_name/$',
|
path('detail/doesnotexist/<pk>/', views.ObjectDoesNotExistDetail.as_view()),
|
||||||
views.AuthorDetail.as_view(template_name='generic_views/about.html')),
|
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/context_object_name/$',
|
|
||||||
views.AuthorDetail.as_view(context_object_name='thingy')),
|
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/custom_detail/$',
|
|
||||||
views.AuthorCustomDetail.as_view()),
|
|
||||||
url(r'^detail/author/(?P<pk>[0-9]+)/dupe_context_object_name/$',
|
|
||||||
views.AuthorDetail.as_view(context_object_name='object')),
|
|
||||||
url(r'^detail/page/(?P<pk>[0-9]+)/field/$',
|
|
||||||
views.PageDetail.as_view()),
|
|
||||||
url(r'^detail/author/invalid/url/$',
|
|
||||||
views.AuthorDetail.as_view()),
|
|
||||||
url(r'^detail/author/invalid/qs/$',
|
|
||||||
views.AuthorDetail.as_view(queryset=None)),
|
|
||||||
url(r'^detail/nonmodel/1/$',
|
|
||||||
views.NonModelDetail.as_view()),
|
|
||||||
url(r'^detail/doesnotexist/(?P<pk>[0-9]+)/$',
|
|
||||||
views.ObjectDoesNotExistDetail.as_view()),
|
|
||||||
# FormView
|
# FormView
|
||||||
url(r'^contact/$',
|
path('contact/', views.ContactView.as_view()),
|
||||||
views.ContactView.as_view()),
|
path('late-validation/', views.LateValidationView.as_view()),
|
||||||
url(r'^late-validation/$',
|
|
||||||
views.LateValidationView.as_view()),
|
|
||||||
|
|
||||||
# Create/UpdateView
|
# Create/UpdateView
|
||||||
url(r'^edit/artists/create/$',
|
path('edit/artists/create/', views.ArtistCreate.as_view()),
|
||||||
views.ArtistCreate.as_view()),
|
path('edit/artists/<int:pk>/update/', views.ArtistUpdate.as_view()),
|
||||||
url(r'^edit/artists/(?P<pk>[0-9]+)/update/$',
|
|
||||||
views.ArtistUpdate.as_view()),
|
|
||||||
|
|
||||||
url(r'^edit/authors/create/naive/$',
|
path('edit/authors/create/naive/', views.NaiveAuthorCreate.as_view()),
|
||||||
views.NaiveAuthorCreate.as_view()),
|
path('edit/authors/create/redirect/', views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')),
|
||||||
url(r'^edit/authors/create/redirect/$',
|
path(
|
||||||
views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')),
|
'edit/authors/create/interpolate_redirect/',
|
||||||
url(r'^edit/authors/create/interpolate_redirect/$',
|
views.NaiveAuthorCreate.as_view(success_url='/edit/author/{id}/update/'),
|
||||||
views.NaiveAuthorCreate.as_view(success_url='/edit/author/{id}/update/')),
|
),
|
||||||
url(r'^edit/authors/create/interpolate_redirect_nonascii/$',
|
path(
|
||||||
views.NaiveAuthorCreate.as_view(success_url='/%C3%A9dit/author/{id}/update/')),
|
'edit/authors/create/interpolate_redirect_nonascii/',
|
||||||
url(r'^edit/authors/create/restricted/$',
|
views.NaiveAuthorCreate.as_view(success_url='/%C3%A9dit/author/{id}/update/'),
|
||||||
views.AuthorCreateRestricted.as_view()),
|
),
|
||||||
url(r'^[eé]dit/authors/create/$',
|
path('edit/authors/create/restricted/', views.AuthorCreateRestricted.as_view()),
|
||||||
views.AuthorCreate.as_view()),
|
re_path('^[eé]dit/authors/create/$', views.AuthorCreate.as_view()),
|
||||||
url(r'^edit/authors/create/special/$',
|
path('edit/authors/create/special/', views.SpecializedAuthorCreate.as_view()),
|
||||||
views.SpecializedAuthorCreate.as_view()),
|
|
||||||
|
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/naive/$',
|
path('edit/author/<int:pk>/update/naive/', views.NaiveAuthorUpdate.as_view()),
|
||||||
views.NaiveAuthorUpdate.as_view()),
|
path(
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/redirect/$',
|
'edit/author/<int:pk>/update/redirect/',
|
||||||
views.NaiveAuthorUpdate.as_view(success_url='/edit/authors/create/')),
|
views.NaiveAuthorUpdate.as_view(success_url='/edit/authors/create/')
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/interpolate_redirect/$',
|
),
|
||||||
views.NaiveAuthorUpdate.as_view(success_url='/edit/author/{id}/update/')),
|
path(
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/interpolate_redirect_nonascii/$',
|
'edit/author/<int:pk>/update/interpolate_redirect/',
|
||||||
views.NaiveAuthorUpdate.as_view(success_url='/%C3%A9dit/author/{id}/update/')),
|
views.NaiveAuthorUpdate.as_view(success_url='/edit/author/{id}/update/')
|
||||||
url(r'^[eé]dit/author/(?P<pk>[0-9]+)/update/$',
|
),
|
||||||
views.AuthorUpdate.as_view()),
|
path(
|
||||||
url(r'^edit/author/update/$',
|
'edit/author/<int:pk>/update/interpolate_redirect_nonascii/',
|
||||||
views.OneAuthorUpdate.as_view()),
|
views.NaiveAuthorUpdate.as_view(success_url='/%C3%A9dit/author/{id}/update/'),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/update/special/$',
|
),
|
||||||
views.SpecializedAuthorUpdate.as_view()),
|
re_path('^[eé]dit/author/(?P<pk>[0-9]+)/update/$', views.AuthorUpdate.as_view()),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/naive/$',
|
path('edit/author/update/', views.OneAuthorUpdate.as_view()),
|
||||||
views.NaiveAuthorDelete.as_view()),
|
path('edit/author/<int:pk>/update/special/', views.SpecializedAuthorUpdate.as_view()),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/redirect/$',
|
path('edit/author/<int:pk>/delete/naive/', views.NaiveAuthorDelete.as_view()),
|
||||||
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/')),
|
path(
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/interpolate_redirect/$',
|
'edit/author/<int:pk>/delete/redirect/',
|
||||||
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/?deleted={id}')),
|
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/'),
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/interpolate_redirect_nonascii/$',
|
),
|
||||||
views.NaiveAuthorDelete.as_view(success_url='/%C3%A9dit/authors/create/?deleted={id}')),
|
path(
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/$',
|
'edit/author/<int:pk>/delete/interpolate_redirect/',
|
||||||
views.AuthorDelete.as_view()),
|
views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/?deleted={id}')
|
||||||
url(r'^edit/author/(?P<pk>[0-9]+)/delete/special/$',
|
),
|
||||||
views.SpecializedAuthorDelete.as_view()),
|
path(
|
||||||
|
'edit/author/<int:pk>/delete/interpolate_redirect_nonascii/',
|
||||||
|
views.NaiveAuthorDelete.as_view(success_url='/%C3%A9dit/authors/create/?deleted={id}')
|
||||||
|
),
|
||||||
|
path('edit/author/<int:pk>/delete/', views.AuthorDelete.as_view()),
|
||||||
|
path('edit/author/<int:pk>/delete/special/', views.SpecializedAuthorDelete.as_view()),
|
||||||
|
|
||||||
# ArchiveIndexView
|
# ArchiveIndexView
|
||||||
url(r'^dates/books/$',
|
path('dates/books/', views.BookArchive.as_view()),
|
||||||
views.BookArchive.as_view()),
|
path('dates/books/context_object_name/', views.BookArchive.as_view(context_object_name='thingies')),
|
||||||
url(r'^dates/books/context_object_name/$',
|
path('dates/books/allow_empty/', views.BookArchive.as_view(allow_empty=True)),
|
||||||
views.BookArchive.as_view(context_object_name='thingies')),
|
path('dates/books/template_name/', views.BookArchive.as_view(template_name='generic_views/list.html')),
|
||||||
url(r'^dates/books/allow_empty/$',
|
path('dates/books/template_name_suffix/', views.BookArchive.as_view(template_name_suffix='_detail')),
|
||||||
views.BookArchive.as_view(allow_empty=True)),
|
path('dates/books/invalid/', views.BookArchive.as_view(queryset=None)),
|
||||||
url(r'^dates/books/template_name/$',
|
path('dates/books/paginated/', views.BookArchive.as_view(paginate_by=10)),
|
||||||
views.BookArchive.as_view(template_name='generic_views/list.html')),
|
path('dates/books/reverse/', views.BookArchive.as_view(queryset=Book.objects.order_by('pubdate'))),
|
||||||
url(r'^dates/books/template_name_suffix/$',
|
path('dates/books/by_month/', views.BookArchive.as_view(date_list_period='month')),
|
||||||
views.BookArchive.as_view(template_name_suffix='_detail')),
|
path('dates/booksignings/', views.BookSigningArchive.as_view()),
|
||||||
url(r'^dates/books/invalid/$',
|
path('dates/books/sortedbyname/', views.BookArchive.as_view(ordering='name')),
|
||||||
views.BookArchive.as_view(queryset=None)),
|
path('dates/books/sortedbynamedec/', views.BookArchive.as_view(ordering='-name')),
|
||||||
url(r'^dates/books/paginated/$',
|
|
||||||
views.BookArchive.as_view(paginate_by=10)),
|
|
||||||
url(r'^dates/books/reverse/$',
|
|
||||||
views.BookArchive.as_view(queryset=Book.objects.order_by('pubdate'))),
|
|
||||||
url(r'^dates/books/by_month/$',
|
|
||||||
views.BookArchive.as_view(date_list_period='month')),
|
|
||||||
url(r'^dates/booksignings/$',
|
|
||||||
views.BookSigningArchive.as_view()),
|
|
||||||
url(r'^dates/books/sortedbyname/$',
|
|
||||||
views.BookArchive.as_view(ordering='name')),
|
|
||||||
url(r'^dates/books/sortedbynamedec/$',
|
|
||||||
views.BookArchive.as_view(ordering='-name')),
|
|
||||||
|
|
||||||
|
|
||||||
# ListView
|
# ListView
|
||||||
url(r'^list/dict/$',
|
path('list/dict/', views.DictList.as_view()),
|
||||||
views.DictList.as_view()),
|
path('list/dict/paginated/', views.DictList.as_view(paginate_by=1)),
|
||||||
url(r'^list/dict/paginated/$',
|
path('list/artists/', views.ArtistList.as_view(), name='artists_list'),
|
||||||
views.DictList.as_view(paginate_by=1)),
|
path('list/authors/', views.AuthorList.as_view(), name='authors_list'),
|
||||||
url(r'^list/artists/$',
|
path('list/authors/paginated/', views.AuthorList.as_view(paginate_by=30)),
|
||||||
views.ArtistList.as_view(),
|
path('list/authors/paginated/<int:page>/', views.AuthorList.as_view(paginate_by=30)),
|
||||||
name="artists_list"),
|
path('list/authors/paginated-orphaned/', views.AuthorList.as_view(paginate_by=30, paginate_orphans=2)),
|
||||||
url(r'^list/authors/$',
|
path('list/authors/notempty/', views.AuthorList.as_view(allow_empty=False)),
|
||||||
views.AuthorList.as_view(),
|
path('list/authors/notempty/paginated/', views.AuthorList.as_view(allow_empty=False, paginate_by=2)),
|
||||||
name="authors_list"),
|
path('list/authors/template_name/', views.AuthorList.as_view(template_name='generic_views/list.html')),
|
||||||
url(r'^list/authors/paginated/$',
|
path('list/authors/template_name_suffix/', views.AuthorList.as_view(template_name_suffix='_objects')),
|
||||||
views.AuthorList.as_view(paginate_by=30)),
|
path('list/authors/context_object_name/', views.AuthorList.as_view(context_object_name='author_list')),
|
||||||
url(r'^list/authors/paginated/(?P<page>[0-9]+)/$',
|
path('list/authors/dupe_context_object_name/', views.AuthorList.as_view(context_object_name='object_list')),
|
||||||
views.AuthorList.as_view(paginate_by=30)),
|
path('list/authors/invalid/', views.AuthorList.as_view(queryset=None)),
|
||||||
url(r'^list/authors/paginated-orphaned/$',
|
path(
|
||||||
views.AuthorList.as_view(paginate_by=30, paginate_orphans=2)),
|
'list/authors/paginated/custom_class/',
|
||||||
url(r'^list/authors/notempty/$',
|
views.AuthorList.as_view(paginate_by=5, paginator_class=views.CustomPaginator),
|
||||||
views.AuthorList.as_view(allow_empty=False)),
|
),
|
||||||
url(r'^list/authors/notempty/paginated/$',
|
path('list/authors/paginated/custom_page_kwarg/', views.AuthorList.as_view(paginate_by=30, page_kwarg='pagina')),
|
||||||
views.AuthorList.as_view(allow_empty=False, paginate_by=2)),
|
path('list/authors/paginated/custom_constructor/', views.AuthorListCustomPaginator.as_view()),
|
||||||
url(r'^list/authors/template_name/$',
|
path('list/books/sorted/', views.BookList.as_view(ordering='name')),
|
||||||
views.AuthorList.as_view(template_name='generic_views/list.html')),
|
path('list/books/sortedbypagesandnamedec/', views.BookList.as_view(ordering=('pages', '-name'))),
|
||||||
url(r'^list/authors/template_name_suffix/$',
|
|
||||||
views.AuthorList.as_view(template_name_suffix='_objects')),
|
|
||||||
url(r'^list/authors/context_object_name/$',
|
|
||||||
views.AuthorList.as_view(context_object_name='author_list')),
|
|
||||||
url(r'^list/authors/dupe_context_object_name/$',
|
|
||||||
views.AuthorList.as_view(context_object_name='object_list')),
|
|
||||||
url(r'^list/authors/invalid/$',
|
|
||||||
views.AuthorList.as_view(queryset=None)),
|
|
||||||
url(r'^list/authors/paginated/custom_class/$',
|
|
||||||
views.AuthorList.as_view(paginate_by=5, paginator_class=views.CustomPaginator)),
|
|
||||||
url(r'^list/authors/paginated/custom_page_kwarg/$',
|
|
||||||
views.AuthorList.as_view(paginate_by=30, page_kwarg='pagina')),
|
|
||||||
url(r'^list/authors/paginated/custom_constructor/$',
|
|
||||||
views.AuthorListCustomPaginator.as_view()),
|
|
||||||
url(r'^list/books/sorted/$',
|
|
||||||
views.BookList.as_view(ordering='name')),
|
|
||||||
url(r'^list/books/sortedbypagesandnamedec/$',
|
|
||||||
views.BookList.as_view(ordering=('pages', '-name'))),
|
|
||||||
|
|
||||||
# YearArchiveView
|
# YearArchiveView
|
||||||
# Mixing keyword and positional captures below is intentional; the views
|
# Mixing keyword and positional captures below is intentional; the views
|
||||||
# ought to be able to accept either.
|
# ought to be able to accept either.
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/$',
|
path('dates/books/<int:year>/', views.BookYearArchive.as_view()),
|
||||||
views.BookYearArchive.as_view()),
|
path('dates/books/<int:year>/make_object_list/', views.BookYearArchive.as_view(make_object_list=True)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/make_object_list/$',
|
path('dates/books/<int:year>/allow_empty/', views.BookYearArchive.as_view(allow_empty=True)),
|
||||||
views.BookYearArchive.as_view(make_object_list=True)),
|
path('dates/books/<int:year>/allow_future/', views.BookYearArchive.as_view(allow_future=True)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/allow_empty/$',
|
path('dates/books/<int:year>/paginated/', views.BookYearArchive.as_view(make_object_list=True, paginate_by=30)),
|
||||||
views.BookYearArchive.as_view(allow_empty=True)),
|
path(
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/allow_future/$',
|
'dates/books/<int:year>/sortedbyname/',
|
||||||
views.BookYearArchive.as_view(allow_future=True)),
|
views.BookYearArchive.as_view(make_object_list=True, ordering='name'),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/paginated/$',
|
),
|
||||||
views.BookYearArchive.as_view(make_object_list=True, paginate_by=30)),
|
path(
|
||||||
url(r'^dates/books/(?P<year>\d{4})/sortedbyname/$',
|
'dates/books/<int:year>/sortedbypageandnamedec/',
|
||||||
views.BookYearArchive.as_view(make_object_list=True, ordering='name')),
|
views.BookYearArchive.as_view(make_object_list=True, ordering=('pages', '-name')),
|
||||||
url(r'^dates/books/(?P<year>\d{4})/sortedbypageandnamedec/$',
|
),
|
||||||
views.BookYearArchive.as_view(make_object_list=True, ordering=('pages', '-name'))),
|
path('dates/books/no_year/', views.BookYearArchive.as_view()),
|
||||||
url(r'^dates/books/no_year/$',
|
path('dates/books/<int:year>/reverse/', views.BookYearArchive.as_view(queryset=Book.objects.order_by('pubdate'))),
|
||||||
views.BookYearArchive.as_view()),
|
path('dates/booksignings/<int:year>/', views.BookSigningYearArchive.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/reverse/$',
|
|
||||||
views.BookYearArchive.as_view(queryset=Book.objects.order_by('pubdate'))),
|
|
||||||
url(r'^dates/booksignings/(?P<year>[0-9]{4})/$',
|
|
||||||
views.BookSigningYearArchive.as_view()),
|
|
||||||
|
|
||||||
# MonthArchiveView
|
# MonthArchiveView
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/$',
|
path('dates/books/<int:year>/<int:month>/', views.BookMonthArchive.as_view(month_format='%m')),
|
||||||
views.BookMonthArchive.as_view()),
|
path('dates/books/<int:year>/<month>/', views.BookMonthArchive.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$',
|
path('dates/books/<int:year>/<month>/allow_empty/', views.BookMonthArchive.as_view(allow_empty=True)),
|
||||||
views.BookMonthArchive.as_view(month_format='%m')),
|
path('dates/books/<int:year>/<month>/allow_future/', views.BookMonthArchive.as_view(allow_future=True)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/allow_empty/$',
|
path('dates/books/<int:year>/<month>/paginated/', views.BookMonthArchive.as_view(paginate_by=30)),
|
||||||
views.BookMonthArchive.as_view(allow_empty=True)),
|
path('dates/books/<int:year>/no_month/', views.BookMonthArchive.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/allow_future/$',
|
path('dates/booksignings/<int:year>/<month>/', views.BookSigningMonthArchive.as_view()),
|
||||||
views.BookMonthArchive.as_view(allow_future=True)),
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/paginated/$',
|
|
||||||
views.BookMonthArchive.as_view(paginate_by=30)),
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/no_month/$',
|
|
||||||
views.BookMonthArchive.as_view()),
|
|
||||||
url(r'^dates/booksignings/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/$',
|
|
||||||
views.BookSigningMonthArchive.as_view()),
|
|
||||||
|
|
||||||
# WeekArchiveView
|
# WeekArchiveView
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/$',
|
path('dates/books/<int:year>/week/<int:week>/', views.BookWeekArchive.as_view()),
|
||||||
views.BookWeekArchive.as_view()),
|
path('dates/books/<int:year>/week/<int:week>/allow_empty/', views.BookWeekArchive.as_view(allow_empty=True)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/allow_empty/$',
|
path('dates/books/<int:year>/week/<int:week>/allow_future/', views.BookWeekArchive.as_view(allow_future=True)),
|
||||||
views.BookWeekArchive.as_view(allow_empty=True)),
|
path('dates/books/<int:year>/week/<int:week>/paginated/', views.BookWeekArchive.as_view(paginate_by=30)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/allow_future/$',
|
path('dates/books/<int:year>/week/no_week/', views.BookWeekArchive.as_view()),
|
||||||
views.BookWeekArchive.as_view(allow_future=True)),
|
path('dates/books/<int:year>/week/<int:week>/monday/', views.BookWeekArchive.as_view(week_format='%W')),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/paginated/$',
|
path('dates/booksignings/<int:year>/week/<int:week>/', views.BookSigningWeekArchive.as_view()),
|
||||||
views.BookWeekArchive.as_view(paginate_by=30)),
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/no_week/$',
|
|
||||||
views.BookWeekArchive.as_view()),
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/monday/$',
|
|
||||||
views.BookWeekArchive.as_view(week_format='%W')),
|
|
||||||
url(r'^dates/booksignings/(?P<year>[0-9]{4})/week/(?P<week>[0-9]{1,2})/$',
|
|
||||||
views.BookSigningWeekArchive.as_view()),
|
|
||||||
|
|
||||||
# DayArchiveView
|
# DayArchiveView
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/$',
|
path('dates/books/<int:year>/<int:month>/<int:day>/', views.BookDayArchive.as_view(month_format='%m')),
|
||||||
views.BookDayArchive.as_view()),
|
path('dates/books/<int:year>/<month>/<int:day>/', views.BookDayArchive.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/(?P<day>[0-9]{1,2})/$',
|
path('dates/books/<int:year>/<month>/<int:day>/allow_empty/', views.BookDayArchive.as_view(allow_empty=True)),
|
||||||
views.BookDayArchive.as_view(month_format='%m')),
|
path('dates/books/<int:year>/<month>/<int:day>/allow_future/', views.BookDayArchive.as_view(allow_future=True)),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/allow_empty/$',
|
path(
|
||||||
views.BookDayArchive.as_view(allow_empty=True)),
|
'dates/books/<int:year>/<month>/<int:day>/allow_empty_and_future/',
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/allow_future/$',
|
views.BookDayArchive.as_view(allow_empty=True, allow_future=True),
|
||||||
views.BookDayArchive.as_view(allow_future=True)),
|
),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/allow_empty_and_future/$',
|
path('dates/books/<int:year>/<month>/<int:day>/paginated/', views.BookDayArchive.as_view(paginate_by=True)),
|
||||||
views.BookDayArchive.as_view(allow_empty=True, allow_future=True)),
|
path('dates/books/<int:year>/<month>/no_day/', views.BookDayArchive.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/paginated/$',
|
path('dates/booksignings/<int:year>/<month>/<int:day>/', views.BookSigningDayArchive.as_view()),
|
||||||
views.BookDayArchive.as_view(paginate_by=True)),
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/no_day/$',
|
|
||||||
views.BookDayArchive.as_view()),
|
|
||||||
url(r'^dates/booksignings/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/$',
|
|
||||||
views.BookSigningDayArchive.as_view()),
|
|
||||||
|
|
||||||
# TodayArchiveView
|
# TodayArchiveView
|
||||||
url(r'^dates/books/today/$',
|
path('dates/books/today/', views.BookTodayArchive.as_view()),
|
||||||
views.BookTodayArchive.as_view()),
|
path('dates/books/today/allow_empty/', views.BookTodayArchive.as_view(allow_empty=True)),
|
||||||
url(r'^dates/books/today/allow_empty/$',
|
path('dates/booksignings/today/', views.BookSigningTodayArchive.as_view()),
|
||||||
views.BookTodayArchive.as_view(allow_empty=True)),
|
|
||||||
url(r'^dates/booksignings/today/$',
|
|
||||||
views.BookSigningTodayArchive.as_view()),
|
|
||||||
|
|
||||||
# DateDetailView
|
# DateDetailView
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/(?P<pk>[0-9]+)/$',
|
path('dates/books/<int:year>/<int:month>/<day>/<int:pk>/', views.BookDetail.as_view(month_format='%m')),
|
||||||
views.BookDetail.as_view()),
|
path('dates/books/<int:year>/<month>/<day>/<int:pk>/', views.BookDetail.as_view()),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/(?P<day>[0-9]{1,2})/(?P<pk>[0-9]+)/$',
|
path(
|
||||||
views.BookDetail.as_view(month_format='%m')),
|
'dates/books/<int:year>/<month>/<int:day>/<int:pk>/allow_future/',
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/(?P<pk>[0-9]+)/allow_future/$',
|
views.BookDetail.as_view(allow_future=True),
|
||||||
views.BookDetail.as_view(allow_future=True)),
|
),
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/nopk/$',
|
path('dates/books/<int:year>/<month>/<int:day>/nopk/', views.BookDetail.as_view()),
|
||||||
views.BookDetail.as_view()),
|
|
||||||
|
|
||||||
url(r'^dates/books/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/byslug/(?P<slug>[\w-]+)/$',
|
path('dates/books/<int:year>/<month>/<int:day>/byslug/<slug:slug>/', views.BookDetail.as_view()),
|
||||||
views.BookDetail.as_view()),
|
|
||||||
|
|
||||||
url(
|
path(
|
||||||
r'^dates/books/get_object_custom_queryset/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/'
|
'dates/books/get_object_custom_queryset/<int:year>/<month>/<int:day>/<int:pk>/',
|
||||||
r'(?P<pk>[0-9]+)/$',
|
|
||||||
views.BookDetailGetObjectCustomQueryset.as_view(),
|
views.BookDetailGetObjectCustomQueryset.as_view(),
|
||||||
),
|
),
|
||||||
|
|
||||||
url(r'^dates/booksignings/(?P<year>[0-9]{4})/(?P<month>[a-z]{3})/(?P<day>[0-9]{1,2})/(?P<pk>[0-9]+)/$',
|
path('dates/booksignings/<int:year>/<month>/<int:day>/<int:pk>/', views.BookSigningDetail.as_view()),
|
||||||
views.BookSigningDetail.as_view()),
|
|
||||||
|
|
||||||
# Useful for testing redirects
|
# Useful for testing redirects
|
||||||
url(r'^accounts/login/$', auth_views.LoginView.as_view())
|
path('accounts/login/', auth_views.LoginView.as_view())
|
||||||
]
|
]
|
||||||
|
|
Binary file not shown.
|
@ -36,3 +36,7 @@ msgstr "^profiel/"
|
||||||
#: urls/namespace.py:9 urls/wrong_namespace.py:10
|
#: urls/namespace.py:9 urls/wrong_namespace.py:10
|
||||||
msgid "^register/$"
|
msgid "^register/$"
|
||||||
msgstr "^registreren/$"
|
msgstr "^registreren/$"
|
||||||
|
|
||||||
|
#: urls/namespace.py:12
|
||||||
|
msgid "register-as-path/"
|
||||||
|
msgstr "registreren-als-pad/"
|
||||||
|
|
|
@ -155,6 +155,8 @@ class URLTranslationTests(URLTestCaseBase):
|
||||||
self.assertEqual(translate_url('/en/users/', 'nl'), '/nl/gebruikers/')
|
self.assertEqual(translate_url('/en/users/', 'nl'), '/nl/gebruikers/')
|
||||||
# Namespaced URL
|
# Namespaced URL
|
||||||
self.assertEqual(translate_url('/en/account/register/', 'nl'), '/nl/profiel/registreren/')
|
self.assertEqual(translate_url('/en/account/register/', 'nl'), '/nl/profiel/registreren/')
|
||||||
|
# path() URL pattern
|
||||||
|
self.assertEqual(translate_url('/en/account/register-as-path/', 'nl'), '/nl/profiel/registreren-als-pad/')
|
||||||
self.assertEqual(translation.get_language(), 'en')
|
self.assertEqual(translation.get_language(), 'en')
|
||||||
|
|
||||||
with translation.override('nl'):
|
with translation.override('nl'):
|
||||||
|
@ -169,9 +171,11 @@ class URLNamespaceTests(URLTestCaseBase):
|
||||||
def test_account_register(self):
|
def test_account_register(self):
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
self.assertEqual(reverse('account:register'), '/en/account/register/')
|
self.assertEqual(reverse('account:register'), '/en/account/register/')
|
||||||
|
self.assertEqual(reverse('account:register-as-path'), '/en/account/register-as-path/')
|
||||||
|
|
||||||
with translation.override('nl'):
|
with translation.override('nl'):
|
||||||
self.assertEqual(reverse('account:register'), '/nl/profiel/registreren/')
|
self.assertEqual(reverse('account:register'), '/nl/profiel/registreren/')
|
||||||
|
self.assertEqual(reverse('account:register-as-path'), '/nl/profiel/registreren-als-pad/')
|
||||||
|
|
||||||
|
|
||||||
class URLRedirectTests(URLTestCaseBase):
|
class URLRedirectTests(URLTestCaseBase):
|
||||||
|
@ -322,6 +326,18 @@ class URLResponseTests(URLTestCaseBase):
|
||||||
self.assertEqual(response['content-language'], 'pt-br')
|
self.assertEqual(response['content-language'], 'pt-br')
|
||||||
self.assertEqual(response.context['LANGUAGE_CODE'], 'pt-br')
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'pt-br')
|
||||||
|
|
||||||
|
def test_en_path(self):
|
||||||
|
response = self.client.get('/en/account/register-as-path/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-language'], 'en')
|
||||||
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'en')
|
||||||
|
|
||||||
|
def test_nl_path(self):
|
||||||
|
response = self.client.get('/nl/profiel/registreren-als-pad/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-language'], 'nl')
|
||||||
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'nl')
|
||||||
|
|
||||||
|
|
||||||
class URLRedirectWithScriptAliasTests(URLTestCaseBase):
|
class URLRedirectWithScriptAliasTests(URLTestCaseBase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
from django.urls import path
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
@ -8,4 +9,5 @@ app_name = 'account'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(_(r'^register/$'), view, name='register'),
|
url(_(r'^register/$'), view, name='register'),
|
||||||
url(_(r'^register-without-slash$'), view, name='register-without-slash'),
|
url(_(r'^register-without-slash$'), view, name='register-without-slash'),
|
||||||
|
path(_('register-as-path/'), view, name='register-as-path'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('{x}/<{x}:{x}>/'.format(x=name), views.empty_view, name=name)
|
||||||
|
for name in ('int', 'path', 'slug', 'str', 'uuid')
|
||||||
|
]
|
|
@ -0,0 +1,38 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
class Base64Converter:
|
||||||
|
regex = r'[a-zA-Z0-9+/]*={0,2}'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return base64.b64decode(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return base64.b64encode(value).decode('ascii')
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicConverter:
|
||||||
|
_dynamic_to_python = None
|
||||||
|
_dynamic_to_url = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def regex(self):
|
||||||
|
return r'[0-9a-zA-Z]+'
|
||||||
|
|
||||||
|
@regex.setter
|
||||||
|
def regex(self):
|
||||||
|
raise Exception("You can't modify the regular expression.")
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return type(self)._dynamic_to_python(value)
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return type(self)._dynamic_to_url(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_to_python(cls, value):
|
||||||
|
cls._dynamic_to_python = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_to_url(cls, value):
|
||||||
|
cls._dynamic_to_url = value
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.urls import path, register_converter
|
||||||
|
|
||||||
|
from . import converters, views
|
||||||
|
|
||||||
|
register_converter(converters.Base64Converter, 'base64')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('base64/<base64:value>/', views.empty_view, name='base64'),
|
||||||
|
]
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.urls import path, register_converter
|
||||||
|
|
||||||
|
from . import converters, views
|
||||||
|
|
||||||
|
register_converter(converters.DynamicConverter, 'dynamic')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('dynamic/<dynamic:value>/', views.empty_view, name='dynamic'),
|
||||||
|
]
|
|
@ -0,0 +1,15 @@
|
||||||
|
from django.conf.urls import include
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('articles/2003/', views.empty_view, name='articles-2003'),
|
||||||
|
path('articles/<int:year>/', views.empty_view, name='articles-year'),
|
||||||
|
path('articles/<int:year>/<int:month>/', views.empty_view, name='articles-year-month'),
|
||||||
|
path('articles/<int:year>/<int:month>/<int:day>/', views.empty_view, name='articles-year-month-day'),
|
||||||
|
path('users/', views.empty_view, name='users'),
|
||||||
|
path('users/<id>/', views.empty_view, name='user-with-id'),
|
||||||
|
path('included_urls/', include('urlpatterns_reverse.included_urls')),
|
||||||
|
path('<lang>/<path:url>/', views.empty_view, name='lang-and-path'),
|
||||||
|
]
|
|
@ -0,0 +1,165 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.urls import Resolver404, path, resolve, reverse
|
||||||
|
|
||||||
|
from .converters import DynamicConverter
|
||||||
|
from .views import empty_view
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns.path_urls')
|
||||||
|
class SimplifiedURLTests(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_path_lookup_without_parameters(self):
|
||||||
|
match = resolve('/articles/2003/')
|
||||||
|
self.assertEqual(match.url_name, 'articles-2003')
|
||||||
|
self.assertEqual(match.args, ())
|
||||||
|
self.assertEqual(match.kwargs, {})
|
||||||
|
|
||||||
|
def test_path_lookup_with_typed_parameters(self):
|
||||||
|
match = resolve('/articles/2015/')
|
||||||
|
self.assertEqual(match.url_name, 'articles-year')
|
||||||
|
self.assertEqual(match.args, ())
|
||||||
|
self.assertEqual(match.kwargs, {'year': 2015})
|
||||||
|
|
||||||
|
def test_path_lookup_with_multiple_paramaters(self):
|
||||||
|
match = resolve('/articles/2015/04/12/')
|
||||||
|
self.assertEqual(match.url_name, 'articles-year-month-day')
|
||||||
|
self.assertEqual(match.args, ())
|
||||||
|
self.assertEqual(match.kwargs, {'year': 2015, 'month': 4, 'day': 12})
|
||||||
|
|
||||||
|
def test_two_variable_at_start_of_path_pattern(self):
|
||||||
|
match = resolve('/en/foo/')
|
||||||
|
self.assertEqual(match.url_name, 'lang-and-path')
|
||||||
|
self.assertEqual(match.kwargs, {'lang': 'en', 'url': 'foo'})
|
||||||
|
|
||||||
|
def test_path_reverse_without_parameter(self):
|
||||||
|
url = reverse('articles-2003')
|
||||||
|
self.assertEqual(url, '/articles/2003/')
|
||||||
|
|
||||||
|
def test_path_reverse_with_parameter(self):
|
||||||
|
url = reverse('articles-year-month-day', kwargs={'year': 2015, 'month': 4, 'day': 12})
|
||||||
|
self.assertEqual(url, '/articles/2015/4/12/')
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
|
||||||
|
def test_non_identical_converter_resolve(self):
|
||||||
|
match = resolve('/base64/aGVsbG8=/') # base64 of 'hello'
|
||||||
|
self.assertEqual(match.url_name, 'base64')
|
||||||
|
self.assertEqual(match.kwargs, {'value': b'hello'})
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
|
||||||
|
def test_non_identical_converter_reverse(self):
|
||||||
|
url = reverse('base64', kwargs={'value': b'hello'})
|
||||||
|
self.assertEqual(url, '/base64/aGVsbG8=/')
|
||||||
|
|
||||||
|
def test_path_inclusion_is_matchable(self):
|
||||||
|
match = resolve('/included_urls/extra/something/')
|
||||||
|
self.assertEqual(match.url_name, 'inner-extra')
|
||||||
|
self.assertEqual(match.kwargs, {'extra': 'something'})
|
||||||
|
|
||||||
|
def test_path_inclusion_is_reversable(self):
|
||||||
|
url = reverse('inner-extra', kwargs={'extra': 'something'})
|
||||||
|
self.assertEqual(url, '/included_urls/extra/something/')
|
||||||
|
|
||||||
|
def test_invalid_converter(self):
|
||||||
|
msg = "URL route 'foo/<nonexistent:var>/' uses invalid converter 'nonexistent'."
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
path('foo/<nonexistent:var>/', empty_view)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns.converter_urls')
|
||||||
|
class ConverterTests(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_matching_urls(self):
|
||||||
|
def no_converter(x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
test_data = (
|
||||||
|
('int', {'0', '1', '01', 1234567890}, int),
|
||||||
|
('str', {'abcxyz'}, no_converter),
|
||||||
|
('path', {'allows.ANY*characters'}, no_converter),
|
||||||
|
('slug', {'abcxyz-ABCXYZ_01234567890'}, no_converter),
|
||||||
|
('uuid', {'39da9369-838e-4750-91a5-f7805cd82839'}, uuid.UUID),
|
||||||
|
)
|
||||||
|
for url_name, url_suffixes, converter in test_data:
|
||||||
|
for url_suffix in url_suffixes:
|
||||||
|
url = '/%s/%s/' % (url_name, url_suffix)
|
||||||
|
with self.subTest(url=url):
|
||||||
|
match = resolve(url)
|
||||||
|
self.assertEqual(match.url_name, url_name)
|
||||||
|
self.assertEqual(match.kwargs, {url_name: converter(url_suffix)})
|
||||||
|
# reverse() works with string parameters.
|
||||||
|
string_kwargs = {url_name: url_suffix}
|
||||||
|
self.assertEqual(reverse(url_name, kwargs=string_kwargs), url)
|
||||||
|
# reverse() also works with native types (int, UUID, etc.).
|
||||||
|
if converter is not no_converter:
|
||||||
|
# The converted value might be different for int (a
|
||||||
|
# leading zero is lost in the conversion).
|
||||||
|
converted_value = match.kwargs[url_name]
|
||||||
|
converted_url = '/%s/%s/' % (url_name, converted_value)
|
||||||
|
self.assertEqual(reverse(url_name, kwargs={url_name: converted_value}), converted_url)
|
||||||
|
|
||||||
|
def test_nonmatching_urls(self):
|
||||||
|
test_data = (
|
||||||
|
('int', {'-1', 'letters'}),
|
||||||
|
('str', {'', '/'}),
|
||||||
|
('path', {''}),
|
||||||
|
('slug', {'', 'stars*notallowed'}),
|
||||||
|
('uuid', {
|
||||||
|
'',
|
||||||
|
'9da9369-838e-4750-91a5-f7805cd82839',
|
||||||
|
'39da9369-838-4750-91a5-f7805cd82839',
|
||||||
|
'39da9369-838e-475-91a5-f7805cd82839',
|
||||||
|
'39da9369-838e-4750-91a-f7805cd82839',
|
||||||
|
'39da9369-838e-4750-91a5-f7805cd8283',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
for url_name, url_suffixes in test_data:
|
||||||
|
for url_suffix in url_suffixes:
|
||||||
|
url = '/%s/%s/' % (url_name, url_suffix)
|
||||||
|
with self.subTest(url=url), self.assertRaises(Resolver404):
|
||||||
|
resolve(url)
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterRestrictionTests(SimpleTestCase):
|
||||||
|
def test_non_identifier_parameter_name_causes_exception(self):
|
||||||
|
msg = (
|
||||||
|
"URL route 'hello/<int:1>/' uses parameter name '1' which isn't "
|
||||||
|
"a valid Python identifier."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
|
path(r'hello/<int:1>/', lambda r: None)
|
||||||
|
|
||||||
|
def test_allows_non_ascii_but_valid_identifiers(self):
|
||||||
|
# \u0394 is "GREEK CAPITAL LETTER DELTA", a valid identifier.
|
||||||
|
p = path('hello/<str:\u0394>/', lambda r: None)
|
||||||
|
match = p.resolve('hello/1/')
|
||||||
|
self.assertEqual(match.kwargs, {'\u0394': '1'})
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='urlpatterns.path_dynamic_urls')
|
||||||
|
class ConversionExceptionTests(SimpleTestCase):
|
||||||
|
"""How are errors in Converter.to_python() and to_url() handled?"""
|
||||||
|
|
||||||
|
def test_resolve_value_error_means_no_match(self):
|
||||||
|
@DynamicConverter.register_to_python
|
||||||
|
def raises_value_error(value):
|
||||||
|
raise ValueError()
|
||||||
|
with self.assertRaises(Resolver404):
|
||||||
|
resolve('/dynamic/abc/')
|
||||||
|
|
||||||
|
def test_resolve_type_error_propogates(self):
|
||||||
|
@DynamicConverter.register_to_python
|
||||||
|
def raises_type_error(value):
|
||||||
|
raise TypeError('This type error propagates.')
|
||||||
|
with self.assertRaisesMessage(TypeError, 'This type error propagates.'):
|
||||||
|
resolve('/dynamic/abc/')
|
||||||
|
|
||||||
|
def test_reverse_value_error_propagates(self):
|
||||||
|
@DynamicConverter.register_to_url
|
||||||
|
def raises_value_error(value):
|
||||||
|
raise ValueError('This value error propagates.')
|
||||||
|
with self.assertRaisesMessage(ValueError, 'This value error propagates.'):
|
||||||
|
reverse('dynamic', kwargs={'value': object()})
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
def empty_view(request, *args, **kwargs):
|
||||||
|
return HttpResponse('')
|
|
@ -3,15 +3,14 @@ from unittest import mock
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
from django.urls import LocaleRegexProvider
|
from django.urls.resolvers import LocaleRegexDescriptor, RegexPattern
|
||||||
from django.urls.resolvers import LocaleRegexDescriptor
|
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
here = os.path.dirname(os.path.abspath(__file__))
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
@override_settings(LOCALE_PATHS=[os.path.join(here, 'translations', 'locale')])
|
@override_settings(LOCALE_PATHS=[os.path.join(here, 'translations', 'locale')])
|
||||||
class LocaleRegexProviderTests(SimpleTestCase):
|
class LocaleRegexDescriptorTests(SimpleTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
translation.trans_real._translations = {}
|
translation.trans_real._translations = {}
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ class LocaleRegexProviderTests(SimpleTestCase):
|
||||||
translation.trans_real._translations = {}
|
translation.trans_real._translations = {}
|
||||||
|
|
||||||
def test_translated_regex_compiled_per_language(self):
|
def test_translated_regex_compiled_per_language(self):
|
||||||
provider = LocaleRegexProvider(translation.gettext_lazy('^foo/$'))
|
provider = RegexPattern(translation.gettext_lazy('^foo/$'))
|
||||||
with translation.override('de'):
|
with translation.override('de'):
|
||||||
de_compiled = provider.regex
|
de_compiled = provider.regex
|
||||||
# compiled only once per language
|
# compiled only once per language
|
||||||
|
@ -33,7 +32,7 @@ class LocaleRegexProviderTests(SimpleTestCase):
|
||||||
self.assertEqual(de_compiled, de_compiled_2)
|
self.assertEqual(de_compiled, de_compiled_2)
|
||||||
|
|
||||||
def test_nontranslated_regex_compiled_once(self):
|
def test_nontranslated_regex_compiled_once(self):
|
||||||
provider = LocaleRegexProvider('^foo/$')
|
provider = RegexPattern('^foo/$')
|
||||||
with translation.override('de'):
|
with translation.override('de'):
|
||||||
de_compiled = provider.regex
|
de_compiled = provider.regex
|
||||||
with translation.override('fr'):
|
with translation.override('fr'):
|
||||||
|
@ -46,10 +45,10 @@ class LocaleRegexProviderTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_regex_compile_error(self):
|
def test_regex_compile_error(self):
|
||||||
"""Regex errors are re-raised as ImproperlyConfigured."""
|
"""Regex errors are re-raised as ImproperlyConfigured."""
|
||||||
provider = LocaleRegexProvider('*')
|
provider = RegexPattern('*')
|
||||||
msg = '"*" is not a valid regular expression: nothing to repeat'
|
msg = '"*" is not a valid regular expression: nothing to repeat'
|
||||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
provider.regex
|
provider.regex
|
||||||
|
|
||||||
def test_access_locale_regex_descriptor(self):
|
def test_access_locale_regex_descriptor(self):
|
||||||
self.assertIsInstance(LocaleRegexProvider.regex, LocaleRegexDescriptor)
|
self.assertIsInstance(RegexPattern.regex, LocaleRegexDescriptor)
|
|
@ -17,9 +17,10 @@ from django.shortcuts import redirect
|
||||||
from django.test import SimpleTestCase, TestCase, override_settings
|
from django.test import SimpleTestCase, TestCase, override_settings
|
||||||
from django.test.utils import override_script_prefix
|
from django.test.utils import override_script_prefix
|
||||||
from django.urls import (
|
from django.urls import (
|
||||||
NoReverseMatch, RegexURLPattern, RegexURLResolver, Resolver404,
|
NoReverseMatch, Resolver404, ResolverMatch, URLPattern, URLResolver,
|
||||||
ResolverMatch, get_callable, get_resolver, resolve, reverse, reverse_lazy,
|
get_callable, get_resolver, resolve, reverse, reverse_lazy,
|
||||||
)
|
)
|
||||||
|
from django.urls.resolvers import RegexPattern
|
||||||
|
|
||||||
from . import middleware, urlconf_outer, views
|
from . import middleware, urlconf_outer, views
|
||||||
from .utils import URLObject
|
from .utils import URLObject
|
||||||
|
@ -259,9 +260,9 @@ class NoURLPatternsTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_no_urls_exception(self):
|
def test_no_urls_exception(self):
|
||||||
"""
|
"""
|
||||||
RegexURLResolver should raise an exception when no urlpatterns exist.
|
URLResolver should raise an exception when no urlpatterns exist.
|
||||||
"""
|
"""
|
||||||
resolver = RegexURLResolver(r'^$', settings.ROOT_URLCONF)
|
resolver = URLResolver(RegexPattern(r'^$'), settings.ROOT_URLCONF)
|
||||||
|
|
||||||
with self.assertRaisesMessage(
|
with self.assertRaisesMessage(
|
||||||
ImproperlyConfigured,
|
ImproperlyConfigured,
|
||||||
|
@ -368,13 +369,13 @@ class URLPatternReverse(SimpleTestCase):
|
||||||
class ResolverTests(SimpleTestCase):
|
class ResolverTests(SimpleTestCase):
|
||||||
def test_resolver_repr(self):
|
def test_resolver_repr(self):
|
||||||
"""
|
"""
|
||||||
Test repr of RegexURLResolver, especially when urlconf_name is a list
|
Test repr of URLResolver, especially when urlconf_name is a list
|
||||||
(#17892).
|
(#17892).
|
||||||
"""
|
"""
|
||||||
# Pick a resolver from a namespaced URLconf
|
# Pick a resolver from a namespaced URLconf
|
||||||
resolver = get_resolver('urlpatterns_reverse.namespace_urls')
|
resolver = get_resolver('urlpatterns_reverse.namespace_urls')
|
||||||
sub_resolver = resolver.namespace_dict['test-ns1'][1]
|
sub_resolver = resolver.namespace_dict['test-ns1'][1]
|
||||||
self.assertIn('<RegexURLPattern list>', repr(sub_resolver))
|
self.assertIn('<URLPattern list>', repr(sub_resolver))
|
||||||
|
|
||||||
def test_reverse_lazy_object_coercion_by_resolve(self):
|
def test_reverse_lazy_object_coercion_by_resolve(self):
|
||||||
"""
|
"""
|
||||||
|
@ -445,13 +446,13 @@ class ResolverTests(SimpleTestCase):
|
||||||
# you try to resolve a nonexistent URL in the first level of included
|
# you try to resolve a nonexistent URL in the first level of included
|
||||||
# URLs in named_urls.py (e.g., '/included/nonexistent-url')
|
# URLs in named_urls.py (e.g., '/included/nonexistent-url')
|
||||||
url_types_names = [
|
url_types_names = [
|
||||||
[{'type': RegexURLPattern, 'name': 'named-url1'}],
|
[{'type': URLPattern, 'name': 'named-url1'}],
|
||||||
[{'type': RegexURLPattern, 'name': 'named-url2'}],
|
[{'type': URLPattern, 'name': 'named-url2'}],
|
||||||
[{'type': RegexURLPattern, 'name': None}],
|
[{'type': URLPattern, 'name': None}],
|
||||||
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url3'}],
|
[{'type': URLResolver}, {'type': URLPattern, 'name': 'named-url3'}],
|
||||||
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url4'}],
|
[{'type': URLResolver}, {'type': URLPattern, 'name': 'named-url4'}],
|
||||||
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': None}],
|
[{'type': URLResolver}, {'type': URLPattern, 'name': None}],
|
||||||
[{'type': RegexURLResolver}, {'type': RegexURLResolver}],
|
[{'type': URLResolver}, {'type': URLResolver}],
|
||||||
]
|
]
|
||||||
with self.assertRaisesMessage(Resolver404, 'tried') as cm:
|
with self.assertRaisesMessage(Resolver404, 'tried') as cm:
|
||||||
resolve('/included/nonexistent-url', urlconf=urls)
|
resolve('/included/nonexistent-url', urlconf=urls)
|
||||||
|
@ -494,10 +495,10 @@ class ResolverTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_populate_concurrency(self):
|
def test_populate_concurrency(self):
|
||||||
"""
|
"""
|
||||||
RegexURLResolver._populate() can be called concurrently, but not more
|
URLResolver._populate() can be called concurrently, but not more
|
||||||
than once per thread (#26888).
|
than once per thread (#26888).
|
||||||
"""
|
"""
|
||||||
resolver = RegexURLResolver(r'^/', 'urlpatterns_reverse.urls')
|
resolver = URLResolver(RegexPattern(r'^/'), 'urlpatterns_reverse.urls')
|
||||||
resolver._local.populating = True
|
resolver._local.populating = True
|
||||||
thread = threading.Thread(target=resolver._populate)
|
thread = threading.Thread(target=resolver._populate)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1039,8 +1040,8 @@ class ErrorHandlerResolutionTests(SimpleTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
urlconf = 'urlpatterns_reverse.urls_error_handlers'
|
urlconf = 'urlpatterns_reverse.urls_error_handlers'
|
||||||
urlconf_callables = 'urlpatterns_reverse.urls_error_handlers_callables'
|
urlconf_callables = 'urlpatterns_reverse.urls_error_handlers_callables'
|
||||||
self.resolver = RegexURLResolver(r'^$', urlconf)
|
self.resolver = URLResolver(RegexPattern(r'^$'), urlconf)
|
||||||
self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables)
|
self.callable_resolver = URLResolver(RegexPattern(r'^$'), urlconf_callables)
|
||||||
|
|
||||||
def test_named_handlers(self):
|
def test_named_handlers(self):
|
||||||
handler = (empty_view, {})
|
handler = (empty_view, {})
|
||||||
|
|
|
@ -112,7 +112,14 @@ class DebugViewTests(LoggingCaptureMixin, SimpleTestCase):
|
||||||
def test_404_not_in_urls(self):
|
def test_404_not_in_urls(self):
|
||||||
response = self.client.get('/not-in-urls')
|
response = self.client.get('/not-in-urls')
|
||||||
self.assertNotContains(response, "Raised by:", status_code=404)
|
self.assertNotContains(response, "Raised by:", status_code=404)
|
||||||
|
self.assertContains(response, "Django tried these URL patterns", status_code=404)
|
||||||
self.assertContains(response, "<code>not-in-urls</code>, didn't match", status_code=404)
|
self.assertContains(response, "<code>not-in-urls</code>, didn't match", status_code=404)
|
||||||
|
# Pattern and view name of a RegexURLPattern appear.
|
||||||
|
self.assertContains(response, r"^regex-post/(?P<pk>[0-9]+)/$", status_code=404)
|
||||||
|
self.assertContains(response, "[name='regex-post']", status_code=404)
|
||||||
|
# Pattern and view name of a RoutePattern appear.
|
||||||
|
self.assertContains(response, r"path-post/<int:pk>/", status_code=404)
|
||||||
|
self.assertContains(response, "[name='path-post']", status_code=404)
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF=WithoutEmptyPathUrls)
|
@override_settings(ROOT_URLCONF=WithoutEmptyPathUrls)
|
||||||
def test_404_empty_path_not_in_urls(self):
|
def test_404_empty_path_not_in_urls(self):
|
||||||
|
|
|
@ -141,7 +141,7 @@ class StaticHelperTest(StaticTests):
|
||||||
urls.urlpatterns = self._old_views_urlpatterns
|
urls.urlpatterns = self._old_views_urlpatterns
|
||||||
|
|
||||||
def test_prefix(self):
|
def test_prefix(self):
|
||||||
self.assertEqual(static('test')[0].regex.pattern, '^test(?P<path>.*)$')
|
self.assertEqual(static('test')[0].pattern.regex.pattern, '^test(?P<path>.*)$')
|
||||||
|
|
||||||
@override_settings(DEBUG=False)
|
@override_settings(DEBUG=False)
|
||||||
def test_debug_off(self):
|
def test_debug_off(self):
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
import os
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from os import path
|
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.urls import path, re_path
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import defaults, i18n, static
|
from django.views import defaults, i18n, static
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
base_dir = path.dirname(path.abspath(__file__))
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
media_dir = path.join(base_dir, 'media')
|
media_dir = os.path.join(base_dir, 'media')
|
||||||
locale_dir = path.join(base_dir, 'locale')
|
locale_dir = os.path.join(base_dir, 'locale')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index_page),
|
url(r'^$', views.index_page),
|
||||||
|
@ -64,4 +65,7 @@ urlpatterns += [
|
||||||
),
|
),
|
||||||
url(r'^render_no_template/$', views.render_no_template, name='render_no_template'),
|
url(r'^render_no_template/$', views.render_no_template, name='render_no_template'),
|
||||||
url(r'^test-setlang/(?P<parameter>[^/]+)/$', views.with_parameter, name='with_parameter'),
|
url(r'^test-setlang/(?P<parameter>[^/]+)/$', views.with_parameter, name='with_parameter'),
|
||||||
|
# Patterns to test the technical 404.
|
||||||
|
re_path(r'^regex-post/(?P<pk>[0-9]+)/$', views.index_page, name='regex-post'),
|
||||||
|
path('path-post/<int:pk>/', views.index_page, name='path-post'),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue