mirror of
https://github.com/django/django.git
synced 2025-10-26 07:06:08 +00:00
Fixed #26013 -- Moved django.core.urlresolvers to django.urls.
Thanks to Tim Graham for the review.
This commit is contained in:
committed by
Tim Graham
parent
df3d5b1d73
commit
16411b8400
@@ -15,7 +15,7 @@ def setup(set_prefix=True):
|
||||
"""
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import set_script_prefix
|
||||
from django.urls import set_script_prefix
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.log import configure_logging
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import warnings
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import (
|
||||
from django.urls import (
|
||||
LocaleRegexURLResolver, RegexURLPattern, RegexURLResolver,
|
||||
)
|
||||
from django.utils import six
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
from django.core.urlresolvers import LocaleRegexURLResolver
|
||||
from django.urls import LocaleRegexURLResolver
|
||||
from django.views.i18n import set_language
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ from __future__ import unicode_literals
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.utils import quote
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.db import models
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
@@ -24,7 +24,6 @@ from django.core.exceptions import (
|
||||
FieldDoesNotExist, FieldError, PermissionDenied, ValidationError,
|
||||
)
|
||||
from django.core.paginator import Paginator
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models, router, transaction
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||
@@ -37,6 +36,7 @@ from django.forms.widgets import CheckboxSelectMultiple, SelectMultiple
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http.response import HttpResponseBase
|
||||
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils import six
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
|
||||
@@ -5,11 +5,11 @@ from django.conf import settings
|
||||
from django.contrib.admin import ModelAdmin, actions
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.db.models.base import ModelBase
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.template.engine import Engine
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import six
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -11,11 +11,11 @@ from django.contrib.admin.views.main import (
|
||||
ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
|
||||
)
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.db import models
|
||||
from django.template import Library
|
||||
from django.template.loader import get_template
|
||||
from django.templatetags.static import static
|
||||
from django.urls import NoReverseMatch
|
||||
from django.utils import formats
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django import template
|
||||
from django.contrib.admin.utils import quote
|
||||
from django.core.urlresolvers import Resolver404, get_script_prefix, resolve
|
||||
from django.urls import Resolver404, get_script_prefix, resolve
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.six.moves.urllib.parse import parse_qsl, urlparse, urlunparse
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ from collections import defaultdict
|
||||
|
||||
from django.contrib.auth import get_permission_codename
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.db import models
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.deletion import Collector
|
||||
from django.db.models.sql.constants import QUERY_TERMS
|
||||
from django.forms.utils import pretty_name
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import formats, six, timezone
|
||||
from django.utils.encoding import force_str, force_text, smart_text
|
||||
from django.utils.html import format_html
|
||||
|
||||
@@ -15,8 +15,8 @@ from django.core.exceptions import (
|
||||
FieldDoesNotExist, ImproperlyConfigured, SuspiciousOperation,
|
||||
)
|
||||
from django.core.paginator import InvalidPage
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.http import urlencode
|
||||
|
||||
@@ -6,11 +6,11 @@ from __future__ import unicode_literals
|
||||
import copy
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models.deletion import CASCADE
|
||||
from django.forms.utils import flatatt
|
||||
from django.forms.widgets import RadioFieldRenderer
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import (
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
from email.errors import HeaderParseError
|
||||
from email.parser import HeaderParser
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.admindocs import utils
|
||||
from django.core import urlresolvers
|
||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||
from django.db import models
|
||||
from django.http import Http404
|
||||
from django.template.engine import Engine
|
||||
from django.urls import get_mod_func, get_resolver, get_urlconf, reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.inspect import (
|
||||
func_accepts_kwargs, func_accepts_var_args, func_has_no_args,
|
||||
@@ -38,7 +38,7 @@ class BaseAdminDocsView(TemplateView):
|
||||
return super(BaseAdminDocsView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs.update({'root_path': urlresolvers.reverse('admin:index')})
|
||||
kwargs.update({'root_path': reverse('admin:index')})
|
||||
kwargs.update(admin.site.each_context(self.request))
|
||||
return super(BaseAdminDocsView, self).get_context_data(**kwargs)
|
||||
|
||||
@@ -147,9 +147,9 @@ class ViewDetailView(BaseAdminDocsView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
view = self.kwargs['view']
|
||||
urlconf = urlresolvers.get_urlconf()
|
||||
if urlresolvers.get_resolver(urlconf)._is_callback(view):
|
||||
mod, func = urlresolvers.get_mod_func(view)
|
||||
urlconf = get_urlconf()
|
||||
if get_resolver(urlconf)._is_callback(view):
|
||||
mod, func = get_mod_func(view)
|
||||
view_func = getattr(import_module(mod), func)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
@@ -9,10 +9,10 @@ from django.contrib.auth.forms import (
|
||||
)
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import escape
|
||||
|
||||
@@ -13,10 +13,10 @@ from django.contrib.auth.forms import (
|
||||
)
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect, QueryDict
|
||||
from django.shortcuts import resolve_url
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.http import is_safe_url, urlsafe_base64_decode
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import get_script_prefix
|
||||
from django.db import models
|
||||
from django.urls import get_script_prefix
|
||||
from django.utils.encoding import iri_to_uri, python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from django.apps import apps
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.core import urlresolvers
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class KMLSitemap(Sitemap):
|
||||
@@ -56,12 +56,14 @@ class KMLSitemap(Sitemap):
|
||||
return self.locations
|
||||
|
||||
def location(self, obj):
|
||||
return urlresolvers.reverse('django.contrib.gis.sitemaps.views.%s' % self.geo_format,
|
||||
kwargs={'label': obj[0],
|
||||
'model': obj[1],
|
||||
'field_name': obj[2],
|
||||
}
|
||||
)
|
||||
return reverse(
|
||||
'django.contrib.gis.sitemaps.views.%s' % self.geo_format,
|
||||
kwargs={
|
||||
'label': obj[0],
|
||||
'model': obj[1],
|
||||
'field_name': obj[2],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class KMZSitemap(KMLSitemap):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.apps import apps as django_apps
|
||||
from django.conf import settings
|
||||
from django.core import paginator, urlresolvers
|
||||
from django.core import paginator
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import translation
|
||||
from django.utils.six.moves.urllib.parse import urlencode
|
||||
from django.utils.six.moves.urllib.request import urlopen
|
||||
@@ -18,17 +19,17 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
|
||||
Alerts Google that the sitemap for the current site has been updated.
|
||||
If sitemap_url is provided, it should be an absolute path to the sitemap
|
||||
for this site -- e.g., '/sitemap.xml'. If sitemap_url is not provided, this
|
||||
function will attempt to deduce it by using urlresolvers.reverse().
|
||||
function will attempt to deduce it by using urls.reverse().
|
||||
"""
|
||||
if sitemap_url is None:
|
||||
try:
|
||||
# First, try to get the "index" sitemap URL.
|
||||
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.index')
|
||||
except urlresolvers.NoReverseMatch:
|
||||
sitemap_url = reverse('django.contrib.sitemaps.views.index')
|
||||
except NoReverseMatch:
|
||||
try:
|
||||
# Next, try for the "global" sitemap URL.
|
||||
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap')
|
||||
except urlresolvers.NoReverseMatch:
|
||||
sitemap_url = reverse('django.contrib.sitemaps.views.sitemap')
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
|
||||
if sitemap_url is None:
|
||||
|
||||
@@ -3,10 +3,10 @@ from calendar import timegm
|
||||
from functools import wraps
|
||||
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core import urlresolvers
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||
from django.http import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.http import http_date
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ def index(request, sitemaps,
|
||||
if callable(site):
|
||||
site = site()
|
||||
protocol = req_protocol if site.protocol is None else site.protocol
|
||||
sitemap_url = urlresolvers.reverse(
|
||||
sitemap_url_name, kwargs={'section': section})
|
||||
sitemap_url = reverse(sitemap_url_name, kwargs={'section': section})
|
||||
absolute_url = '%s://%s%s' % (protocol, req_site.domain, sitemap_url)
|
||||
sites.append(absolute_url)
|
||||
for page in range(2, site.paginator.num_pages + 1):
|
||||
|
||||
@@ -5,7 +5,7 @@ from . import Tags, Warning, register
|
||||
|
||||
@register(Tags.urls)
|
||||
def check_url_config(app_configs, **kwargs):
|
||||
from django.core.urlresolvers import get_resolver
|
||||
from django.urls import get_resolver
|
||||
resolver = get_resolver()
|
||||
return check_resolver(resolver)
|
||||
|
||||
@@ -14,7 +14,7 @@ def check_resolver(resolver):
|
||||
"""
|
||||
Recursively check the resolver.
|
||||
"""
|
||||
from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
|
||||
from django.urls import RegexURLPattern, RegexURLResolver
|
||||
warnings = []
|
||||
for pattern in resolver.url_patterns:
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
|
||||
@@ -7,12 +7,13 @@ import warnings
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.core import signals, urlresolvers
|
||||
from django.core import signals
|
||||
from django.core.exceptions import (
|
||||
MiddlewareNotUsed, PermissionDenied, SuspiciousOperation,
|
||||
)
|
||||
from django.db import connections, transaction
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.urls import get_resolver, set_urlconf
|
||||
from django.utils import six
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
@@ -111,8 +112,8 @@ class BaseHandler(object):
|
||||
# variable" exception in the event an exception is raised before
|
||||
# resolver is set
|
||||
urlconf = settings.ROOT_URLCONF
|
||||
urlresolvers.set_urlconf(urlconf)
|
||||
resolver = urlresolvers.get_resolver(urlconf)
|
||||
set_urlconf(urlconf)
|
||||
resolver = get_resolver(urlconf)
|
||||
# Use a flag to check if the response was rendered to prevent
|
||||
# multiple renderings or to force rendering if necessary.
|
||||
response_is_rendered = False
|
||||
@@ -128,8 +129,8 @@ class BaseHandler(object):
|
||||
if hasattr(request, 'urlconf'):
|
||||
# Reset url resolver with a custom URLconf.
|
||||
urlconf = request.urlconf
|
||||
urlresolvers.set_urlconf(urlconf)
|
||||
resolver = urlresolvers.get_resolver(urlconf)
|
||||
set_urlconf(urlconf)
|
||||
resolver = get_resolver(urlconf)
|
||||
|
||||
resolver_match = resolver.resolve(request.path_info)
|
||||
callback, callback_args, callback_kwargs = resolver_match
|
||||
|
||||
@@ -12,7 +12,7 @@ from django import http
|
||||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.handlers import base
|
||||
from django.core.urlresolvers import set_script_prefix
|
||||
from django.urls import set_script_prefix
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_str, force_text
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
@@ -1,640 +1,9 @@
|
||||
"""
|
||||
This module converts requested URLs to callback view functions.
|
||||
import warnings
|
||||
|
||||
RegexURLResolver is the main class here. Its resolve() method takes a URL (as
|
||||
a string) and returns a ResolverMatch object which provides access to all
|
||||
attributes of the resolved URL match.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from django.urls import * # NOQA
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
|
||||
import functools
|
||||
import re
|
||||
from importlib import import_module
|
||||
from threading import local
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||
from django.http import Http404
|
||||
from django.utils import lru_cache, six
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.encoding import force_str, force_text, iri_to_uri
|
||||
from django.utils.functional import cached_property, lazy
|
||||
from django.utils.http import RFC3986_SUBDELIMS, urlquote
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
from django.utils.regex_helper import normalize
|
||||
from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
|
||||
from django.utils.translation import get_language, override
|
||||
|
||||
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
|
||||
# the current thread (which is the only one we ever access), it is assumed to
|
||||
# be empty.
|
||||
_prefixes = local()
|
||||
|
||||
# Overridden URLconfs for each thread are stored here.
|
||||
_urlconfs = local()
|
||||
|
||||
|
||||
class ResolverMatch(object):
|
||||
def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None):
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.url_name = url_name
|
||||
|
||||
# If a URLRegexResolver doesn't have a namespace or app_name, it passes
|
||||
# in an empty value.
|
||||
self.app_names = [x for x in app_names if x] if app_names else []
|
||||
self.app_name = ':'.join(self.app_names)
|
||||
|
||||
if namespaces:
|
||||
self.namespaces = [x for x in namespaces if x]
|
||||
else:
|
||||
self.namespaces = []
|
||||
self.namespace = ':'.join(self.namespaces)
|
||||
|
||||
if not hasattr(func, '__name__'):
|
||||
# A class-based view
|
||||
self._func_path = '.'.join([func.__class__.__module__, func.__class__.__name__])
|
||||
else:
|
||||
# A function-based view
|
||||
self._func_path = '.'.join([func.__module__, func.__name__])
|
||||
|
||||
view_path = url_name or self._func_path
|
||||
self.view_name = ':'.join(self.namespaces + [view_path])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.func, self.args, self.kwargs)[index]
|
||||
|
||||
def __repr__(self):
|
||||
return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_names=%s, namespaces=%s)" % (
|
||||
self._func_path, self.args, self.kwargs, self.url_name, self.app_names, self.namespaces)
|
||||
|
||||
|
||||
class Resolver404(Http404):
|
||||
pass
|
||||
|
||||
|
||||
class NoReverseMatch(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_callable(lookup_view):
|
||||
"""
|
||||
Return a callable corresponding to lookup_view.
|
||||
|
||||
* If lookup_view is already a callable, return it.
|
||||
* If lookup_view is a string import path that can be resolved to a callable,
|
||||
import that callable and return it, otherwise raise an exception
|
||||
(ImportError or ViewDoesNotExist).
|
||||
"""
|
||||
if callable(lookup_view):
|
||||
return lookup_view
|
||||
|
||||
if not isinstance(lookup_view, six.string_types):
|
||||
raise ViewDoesNotExist("'%s' is not a callable or a dot-notation path" % lookup_view)
|
||||
|
||||
mod_name, func_name = get_mod_func(lookup_view)
|
||||
if not func_name: # No '.' in lookup_view
|
||||
raise ImportError("Could not import '%s'. The path must be fully qualified." % lookup_view)
|
||||
|
||||
try:
|
||||
mod = import_module(mod_name)
|
||||
except ImportError:
|
||||
parentmod, submod = get_mod_func(mod_name)
|
||||
if submod and not module_has_submodule(import_module(parentmod), submod):
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s'. Parent module %s does not exist." %
|
||||
(lookup_view, mod_name)
|
||||
)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
try:
|
||||
view_func = getattr(mod, func_name)
|
||||
except AttributeError:
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s'. View does not exist in module %s." %
|
||||
(lookup_view, mod_name)
|
||||
)
|
||||
else:
|
||||
if not callable(view_func):
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s.%s'. View is not callable." %
|
||||
(mod_name, func_name)
|
||||
)
|
||||
return view_func
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_resolver(urlconf=None):
|
||||
if urlconf is None:
|
||||
from django.conf import settings
|
||||
urlconf = settings.ROOT_URLCONF
|
||||
return RegexURLResolver(r'^/', urlconf)
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_ns_resolver(ns_pattern, resolver):
|
||||
# Build a namespaced resolver for the given parent URLconf pattern.
|
||||
# This makes it possible to have captured parameters in the parent
|
||||
# URLconf pattern.
|
||||
ns_resolver = RegexURLResolver(ns_pattern, resolver.url_patterns)
|
||||
return RegexURLResolver(r'^/', [ns_resolver])
|
||||
|
||||
|
||||
def get_mod_func(callback):
|
||||
# Converts 'django.views.news.stories.story_detail' to
|
||||
# ['django.views.news.stories', 'story_detail']
|
||||
try:
|
||||
dot = callback.rindex('.')
|
||||
except ValueError:
|
||||
return callback, ''
|
||||
return callback[:dot], callback[dot + 1:]
|
||||
|
||||
|
||||
class LocaleRegexProvider(object):
|
||||
"""
|
||||
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 ugettext_lazy) representing a regular
|
||||
# expression.
|
||||
self._regex = regex
|
||||
self._regex_dict = {}
|
||||
|
||||
@property
|
||||
def regex(self):
|
||||
"""
|
||||
Returns a compiled regular expression, depending upon the activated
|
||||
language-code.
|
||||
"""
|
||||
language_code = get_language()
|
||||
if language_code not in self._regex_dict:
|
||||
if isinstance(self._regex, six.string_types):
|
||||
regex = self._regex
|
||||
else:
|
||||
regex = force_text(self._regex)
|
||||
try:
|
||||
compiled_regex = re.compile(regex, re.UNICODE)
|
||||
except re.error as e:
|
||||
raise ImproperlyConfigured(
|
||||
'"%s" is not a valid regular expression: %s' %
|
||||
(regex, six.text_type(e)))
|
||||
|
||||
self._regex_dict[language_code] = compiled_regex
|
||||
return self._regex_dict[language_code]
|
||||
|
||||
|
||||
class RegexURLPattern(LocaleRegexProvider):
|
||||
def __init__(self, regex, callback, default_args=None, name=None):
|
||||
LocaleRegexProvider.__init__(self, regex)
|
||||
self.callback = callback # the view
|
||||
self.default_args = default_args or {}
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
|
||||
|
||||
def resolve(self, path):
|
||||
match = self.regex.search(path)
|
||||
if match:
|
||||
# If there are any named groups, use those as kwargs, ignoring
|
||||
# non-named groups. Otherwise, pass all non-named arguments as
|
||||
# positional arguments.
|
||||
kwargs = match.groupdict()
|
||||
if kwargs:
|
||||
args = ()
|
||||
else:
|
||||
args = match.groups()
|
||||
# In both cases, pass any extra_kwargs as **kwargs.
|
||||
kwargs.update(self.default_args)
|
||||
|
||||
return ResolverMatch(self.callback, args, kwargs, self.name)
|
||||
|
||||
@cached_property
|
||||
def lookup_str(self):
|
||||
"""
|
||||
A string that identifies the view (e.g. 'path.to.view_function' or
|
||||
'path.to.ClassBasedView').
|
||||
"""
|
||||
callback = self.callback
|
||||
if isinstance(callback, functools.partial):
|
||||
callback = callback.func
|
||||
if not hasattr(callback, '__name__'):
|
||||
return callback.__module__ + "." + callback.__class__.__name__
|
||||
else:
|
||||
return callback.__module__ + "." + callback.__name__
|
||||
|
||||
|
||||
class RegexURLResolver(LocaleRegexProvider):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||
LocaleRegexProvider.__init__(self, regex)
|
||||
# urlconf_name is the dotted Python path to the module defining
|
||||
# urlpatterns. It may also be an object with an urlpatterns attribute
|
||||
# or urlpatterns itself.
|
||||
self.urlconf_name = urlconf_name
|
||||
self.callback = None
|
||||
self.default_kwargs = default_kwargs or {}
|
||||
self.namespace = namespace
|
||||
self.app_name = app_name
|
||||
self._reverse_dict = {}
|
||||
self._namespace_dict = {}
|
||||
self._app_dict = {}
|
||||
# set of dotted paths to all functions and classes that are used in
|
||||
# urlpatterns
|
||||
self._callback_strs = set()
|
||||
self._populated = False
|
||||
|
||||
def __repr__(self):
|
||||
if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
|
||||
# Don't bother to output the whole list, it can be huge
|
||||
urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
|
||||
else:
|
||||
urlconf_repr = repr(self.urlconf_name)
|
||||
return str('<%s %s (%s:%s) %s>') % (
|
||||
self.__class__.__name__, urlconf_repr, self.app_name,
|
||||
self.namespace, self.regex.pattern)
|
||||
|
||||
def _populate(self):
|
||||
lookups = MultiValueDict()
|
||||
namespaces = {}
|
||||
apps = {}
|
||||
language_code = get_language()
|
||||
for pattern in reversed(self.url_patterns):
|
||||
if isinstance(pattern, RegexURLPattern):
|
||||
self._callback_strs.add(pattern.lookup_str)
|
||||
p_pattern = pattern.regex.pattern
|
||||
if p_pattern.startswith('^'):
|
||||
p_pattern = p_pattern[1:]
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if pattern.namespace:
|
||||
namespaces[pattern.namespace] = (p_pattern, pattern)
|
||||
if pattern.app_name:
|
||||
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
|
||||
else:
|
||||
parent_pat = pattern.regex.pattern
|
||||
for name in pattern.reverse_dict:
|
||||
for matches, pat, defaults in pattern.reverse_dict.getlist(name):
|
||||
new_matches = normalize(parent_pat + pat)
|
||||
lookups.appendlist(
|
||||
name,
|
||||
(
|
||||
new_matches,
|
||||
p_pattern + pat,
|
||||
dict(defaults, **pattern.default_kwargs),
|
||||
)
|
||||
)
|
||||
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
|
||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||
for app_name, namespace_list in pattern.app_dict.items():
|
||||
apps.setdefault(app_name, []).extend(namespace_list)
|
||||
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._app_dict[language_code] = apps
|
||||
self._populated = True
|
||||
|
||||
@property
|
||||
def reverse_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._reverse_dict:
|
||||
self._populate()
|
||||
return self._reverse_dict[language_code]
|
||||
|
||||
@property
|
||||
def namespace_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._namespace_dict:
|
||||
self._populate()
|
||||
return self._namespace_dict[language_code]
|
||||
|
||||
@property
|
||||
def app_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._app_dict:
|
||||
self._populate()
|
||||
return self._app_dict[language_code]
|
||||
|
||||
def _is_callback(self, name):
|
||||
if not self._populated:
|
||||
self._populate()
|
||||
return name in self._callback_strs
|
||||
|
||||
def resolve(self, path):
|
||||
path = force_text(path) # path may be a reverse_lazy object
|
||||
tried = []
|
||||
match = self.regex.search(path)
|
||||
if match:
|
||||
new_path = path[match.end():]
|
||||
for pattern in self.url_patterns:
|
||||
try:
|
||||
sub_match = pattern.resolve(new_path)
|
||||
except Resolver404 as e:
|
||||
sub_tried = e.args[0].get('tried')
|
||||
if sub_tried is not None:
|
||||
tried.extend([pattern] + t for t in sub_tried)
|
||||
else:
|
||||
tried.append([pattern])
|
||||
else:
|
||||
if sub_match:
|
||||
# Merge captured arguments in match with submatch
|
||||
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
|
||||
sub_match_dict.update(sub_match.kwargs)
|
||||
|
||||
# If there are *any* named groups, ignore all non-named groups.
|
||||
# Otherwise, pass all non-named arguments as positional arguments.
|
||||
sub_match_args = sub_match.args
|
||||
if not sub_match_dict:
|
||||
sub_match_args = match.groups() + sub_match.args
|
||||
|
||||
return ResolverMatch(
|
||||
sub_match.func,
|
||||
sub_match_args,
|
||||
sub_match_dict,
|
||||
sub_match.url_name,
|
||||
[self.app_name] + sub_match.app_names,
|
||||
[self.namespace] + sub_match.namespaces
|
||||
)
|
||||
tried.append([pattern])
|
||||
raise Resolver404({'tried': tried, 'path': new_path})
|
||||
raise Resolver404({'path': path})
|
||||
|
||||
@cached_property
|
||||
def urlconf_module(self):
|
||||
if isinstance(self.urlconf_name, six.string_types):
|
||||
return import_module(self.urlconf_name)
|
||||
else:
|
||||
return self.urlconf_name
|
||||
|
||||
@cached_property
|
||||
def url_patterns(self):
|
||||
# urlconf_module might be a valid set of patterns, so we default to it
|
||||
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
|
||||
try:
|
||||
iter(patterns)
|
||||
except TypeError:
|
||||
msg = (
|
||||
"The included URLconf '{name}' does not appear to have any "
|
||||
"patterns in it. If you see valid patterns in the file then "
|
||||
"the issue is probably caused by a circular import."
|
||||
)
|
||||
raise ImproperlyConfigured(msg.format(name=self.urlconf_name))
|
||||
return patterns
|
||||
|
||||
def resolve_error_handler(self, view_type):
|
||||
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
|
||||
if not callback:
|
||||
# No handler specified in file; use default
|
||||
# Lazy import, since django.urls imports this file
|
||||
from django.conf import urls
|
||||
callback = getattr(urls, 'handler%s' % view_type)
|
||||
return get_callable(callback), {}
|
||||
|
||||
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
||||
if args and kwargs:
|
||||
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
||||
text_args = [force_text(v) for v in args]
|
||||
text_kwargs = {k: force_text(v) for (k, v) in kwargs.items()}
|
||||
|
||||
if not self._populated:
|
||||
self._populate()
|
||||
|
||||
possibilities = self.reverse_dict.getlist(lookup_view)
|
||||
|
||||
for possibility, pattern, defaults in possibilities:
|
||||
for result, params in possibility:
|
||||
if args:
|
||||
if len(args) != len(params):
|
||||
continue
|
||||
candidate_subs = dict(zip(params, text_args))
|
||||
else:
|
||||
if (set(kwargs.keys()) | set(defaults.keys()) != set(params) |
|
||||
set(defaults.keys())):
|
||||
continue
|
||||
matches = True
|
||||
for k, v in defaults.items():
|
||||
if kwargs.get(k, v) != v:
|
||||
matches = False
|
||||
break
|
||||
if not matches:
|
||||
continue
|
||||
candidate_subs = text_kwargs
|
||||
# WSGI provides decoded URLs, without %xx escapes, and the URL
|
||||
# resolver operates on such URLs. First substitute arguments
|
||||
# without quoting to build a decoded URL and look for a match.
|
||||
# Then, if we have a match, redo the substitution with quoted
|
||||
# arguments in order to return a properly encoded URL.
|
||||
candidate_pat = _prefix.replace('%', '%%') + result
|
||||
if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % candidate_subs, re.UNICODE):
|
||||
# safe characters from `pchar` definition of RFC 3986
|
||||
url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@'))
|
||||
# Don't allow construction of scheme relative urls.
|
||||
if url.startswith('//'):
|
||||
url = '/%%2F%s' % url[2:]
|
||||
return url
|
||||
# lookup_view can be URL name or callable, but callables are not
|
||||
# friendly in error messages.
|
||||
m = getattr(lookup_view, '__module__', None)
|
||||
n = getattr(lookup_view, '__name__', None)
|
||||
if m is not None and n is not None:
|
||||
lookup_view_s = "%s.%s" % (m, n)
|
||||
else:
|
||||
lookup_view_s = lookup_view
|
||||
|
||||
patterns = [pattern for (possibility, pattern, defaults) in possibilities]
|
||||
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
|
||||
"arguments '%s' not found. %d pattern(s) tried: %s" %
|
||||
(lookup_view_s, args, kwargs, len(patterns), patterns))
|
||||
|
||||
|
||||
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):
|
||||
super(LocaleRegexURLResolver, self).__init__(
|
||||
None, urlconf_name, default_kwargs, app_name, namespace)
|
||||
|
||||
@property
|
||||
def regex(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._regex_dict:
|
||||
regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
|
||||
self._regex_dict[language_code] = regex_compiled
|
||||
return self._regex_dict[language_code]
|
||||
|
||||
|
||||
def resolve(path, urlconf=None):
|
||||
if urlconf is None:
|
||||
urlconf = get_urlconf()
|
||||
return get_resolver(urlconf).resolve(path)
|
||||
|
||||
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
|
||||
if urlconf is None:
|
||||
urlconf = get_urlconf()
|
||||
resolver = get_resolver(urlconf)
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
|
||||
prefix = get_script_prefix()
|
||||
|
||||
if not isinstance(viewname, six.string_types):
|
||||
view = viewname
|
||||
else:
|
||||
parts = viewname.split(':')
|
||||
parts.reverse()
|
||||
view = parts[0]
|
||||
path = parts[1:]
|
||||
|
||||
if current_app:
|
||||
current_path = current_app.split(':')
|
||||
current_path.reverse()
|
||||
else:
|
||||
current_path = None
|
||||
|
||||
resolved_path = []
|
||||
ns_pattern = ''
|
||||
while path:
|
||||
ns = path.pop()
|
||||
current_ns = current_path.pop() if current_path else None
|
||||
|
||||
# Lookup the name to see if it could be an app identifier
|
||||
try:
|
||||
app_list = resolver.app_dict[ns]
|
||||
# Yes! Path part matches an app in the current Resolver
|
||||
if current_ns and current_ns in app_list:
|
||||
# If we are reversing for a particular app,
|
||||
# use that namespace
|
||||
ns = current_ns
|
||||
elif ns not in app_list:
|
||||
# The name isn't shared by one of the instances
|
||||
# (i.e., the default) so just pick the first instance
|
||||
# as the default.
|
||||
ns = app_list[0]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if ns != current_ns:
|
||||
current_path = None
|
||||
|
||||
try:
|
||||
extra, resolver = resolver.namespace_dict[ns]
|
||||
resolved_path.append(ns)
|
||||
ns_pattern = ns_pattern + extra
|
||||
except KeyError as key:
|
||||
if resolved_path:
|
||||
raise NoReverseMatch(
|
||||
"%s is not a registered namespace inside '%s'" %
|
||||
(key, ':'.join(resolved_path)))
|
||||
else:
|
||||
raise NoReverseMatch("%s is not a registered namespace" %
|
||||
key)
|
||||
if ns_pattern:
|
||||
resolver = get_ns_resolver(ns_pattern, resolver)
|
||||
|
||||
return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))
|
||||
|
||||
reverse_lazy = lazy(reverse, six.text_type)
|
||||
|
||||
|
||||
def clear_url_caches():
|
||||
get_callable.cache_clear()
|
||||
get_resolver.cache_clear()
|
||||
get_ns_resolver.cache_clear()
|
||||
|
||||
|
||||
def set_script_prefix(prefix):
|
||||
"""
|
||||
Sets the script prefix for the current thread.
|
||||
"""
|
||||
if not prefix.endswith('/'):
|
||||
prefix += '/'
|
||||
_prefixes.value = prefix
|
||||
|
||||
|
||||
def get_script_prefix():
|
||||
"""
|
||||
Returns the currently active script prefix. Useful for client code that
|
||||
wishes to construct their own URLs manually (although accessing the request
|
||||
instance is normally going to be a lot cleaner).
|
||||
"""
|
||||
return getattr(_prefixes, "value", '/')
|
||||
|
||||
|
||||
def clear_script_prefix():
|
||||
"""
|
||||
Unsets the script prefix for the current thread.
|
||||
"""
|
||||
try:
|
||||
del _prefixes.value
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def set_urlconf(urlconf_name):
|
||||
"""
|
||||
Sets the URLconf for the current thread (overriding the default one in
|
||||
settings). Set to None to revert back to the default.
|
||||
"""
|
||||
if urlconf_name:
|
||||
_urlconfs.value = urlconf_name
|
||||
else:
|
||||
if hasattr(_urlconfs, "value"):
|
||||
del _urlconfs.value
|
||||
|
||||
|
||||
def get_urlconf(default=None):
|
||||
"""
|
||||
Returns the root URLconf to use for the current thread if it has been
|
||||
changed from the default one.
|
||||
"""
|
||||
return getattr(_urlconfs, "value", default)
|
||||
|
||||
|
||||
def is_valid_path(path, urlconf=None):
|
||||
"""
|
||||
Returns True if the given path resolves against the default URL resolver,
|
||||
False otherwise.
|
||||
|
||||
This is a convenience method to make working with "is this a match?" cases
|
||||
easier, avoiding unnecessarily indented try...except blocks.
|
||||
"""
|
||||
try:
|
||||
resolve(path, urlconf)
|
||||
return True
|
||||
except Resolver404:
|
||||
return False
|
||||
|
||||
|
||||
def translate_url(url, lang_code):
|
||||
"""
|
||||
Given a URL (absolute or relative), try to get its translated version in
|
||||
the `lang_code` language (either by i18n_patterns or by translated regex).
|
||||
Return the original URL if no translated version is found.
|
||||
"""
|
||||
parsed = urlsplit(url)
|
||||
try:
|
||||
match = resolve(parsed.path)
|
||||
except Resolver404:
|
||||
pass
|
||||
else:
|
||||
to_be_reversed = "%s:%s" % (match.namespace, match.url_name) if match.namespace else match.url_name
|
||||
with override(lang_code):
|
||||
try:
|
||||
url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
else:
|
||||
url = urlunsplit((parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment))
|
||||
return url
|
||||
warnings.warn(
|
||||
"Importing from django.core.urlresolvers is deprecated in favor of "
|
||||
"django.urls.", RemovedInDjango20Warning, stacklevel=2
|
||||
)
|
||||
|
||||
@@ -26,15 +26,15 @@ from django.db.models.fields.related import ( # NOQA isort:skip
|
||||
|
||||
def permalink(func):
|
||||
"""
|
||||
Decorator that calls urlresolvers.reverse() to return a URL using
|
||||
parameters returned by the decorated function "func".
|
||||
Decorator that calls urls.reverse() to return a URL using parameters
|
||||
returned by the decorated function "func".
|
||||
|
||||
"func" should be a function that returns a tuple in one of the
|
||||
following formats:
|
||||
(viewname, viewargs)
|
||||
(viewname, viewargs, viewkwargs)
|
||||
"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
|
||||
@@ -3,9 +3,9 @@ import re
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.mail import mail_managers
|
||||
from django.urls import is_valid_path
|
||||
from django.utils.cache import get_conditional_response, set_response_etag
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
@@ -74,8 +74,8 @@ class CommonMiddleware(object):
|
||||
if settings.APPEND_SLASH and not request.get_full_path().endswith('/'):
|
||||
urlconf = getattr(request, 'urlconf', None)
|
||||
return (
|
||||
not urlresolvers.is_valid_path(request.path_info, urlconf)
|
||||
and urlresolvers.is_valid_path('%s/' % request.path_info, urlconf)
|
||||
not is_valid_path(request.path_info, urlconf)
|
||||
and is_valid_path('%s/' % request.path_info, urlconf)
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import logging
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import get_callable
|
||||
from django.urls import get_callable
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.crypto import constant_time_compare, get_random_string
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"This is the locale selecting middleware that will look at accept headers"
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import (
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import (
|
||||
LocaleRegexURLResolver, get_resolver, get_script_prefix, is_valid_path,
|
||||
)
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils import translation
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
@@ -3,7 +3,6 @@ This module collects helper functions and classes that "span" multiple levels
|
||||
of MVC. In other words, these functions/classes introduce controlled coupling
|
||||
for convenience's sake.
|
||||
"""
|
||||
from django.core import urlresolvers
|
||||
from django.db.models.base import ModelBase
|
||||
from django.db.models.manager import Manager
|
||||
from django.db.models.query import QuerySet
|
||||
@@ -11,6 +10,7 @@ from django.http import (
|
||||
Http404, HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect,
|
||||
)
|
||||
from django.template import loader
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.functional import Promise
|
||||
@@ -43,8 +43,8 @@ def redirect(to, *args, **kwargs):
|
||||
|
||||
* A model: the model's `get_absolute_url()` function will be called.
|
||||
|
||||
* A view name, possibly with arguments: `urlresolvers.reverse()` will
|
||||
be used to reverse-resolve the name.
|
||||
* A view name, possibly with arguments: `urls.reverse()` will be used
|
||||
to reverse-resolve the name.
|
||||
|
||||
* A URL, which will be used as-is for the redirect location.
|
||||
|
||||
@@ -123,8 +123,8 @@ def resolve_url(to, *args, **kwargs):
|
||||
|
||||
* A model: the model's `get_absolute_url()` function will be called.
|
||||
|
||||
* A view name, possibly with arguments: `urlresolvers.reverse()` will
|
||||
be used to reverse-resolve the name.
|
||||
* A view name, possibly with arguments: `urls.reverse()` will be used
|
||||
to reverse-resolve the name.
|
||||
|
||||
* A URL, which will be returned as-is.
|
||||
"""
|
||||
@@ -144,8 +144,8 @@ def resolve_url(to, *args, **kwargs):
|
||||
|
||||
# Next try a reverse URL resolution.
|
||||
try:
|
||||
return urlresolvers.reverse(to, args=args, kwargs=kwargs)
|
||||
except urlresolvers.NoReverseMatch:
|
||||
return reverse(to, args=args, kwargs=kwargs)
|
||||
except NoReverseMatch:
|
||||
# If this is a callable, re-raise.
|
||||
if callable(to):
|
||||
raise
|
||||
|
||||
@@ -426,7 +426,7 @@ class URLNode(Node):
|
||||
self.asvar = asvar
|
||||
|
||||
def render(self, context):
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.urls import reverse, NoReverseMatch
|
||||
args = [arg.resolve(context) for arg in self.args]
|
||||
kwargs = {
|
||||
smart_text(k, 'ascii'): v.resolve(context)
|
||||
|
||||
@@ -11,7 +11,6 @@ from io import BytesIO
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.handlers.wsgi import ISO_8859_1, UTF_8, WSGIRequest
|
||||
from django.core.signals import (
|
||||
@@ -22,6 +21,7 @@ from django.http import HttpRequest, QueryDict, SimpleCookie
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.test import signals
|
||||
from django.test.utils import ContextList
|
||||
from django.urls import resolve
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_bytes, force_str, uri_to_iri
|
||||
from django.utils.functional import SimpleLazyObject, curry
|
||||
@@ -477,8 +477,7 @@ class Client(RequestFactory):
|
||||
response.json = curry(self._parse_json, response)
|
||||
|
||||
# Attach the ResolverMatch instance to the response
|
||||
response.resolver_match = SimpleLazyObject(
|
||||
lambda: urlresolvers.resolve(request['PATH_INFO']))
|
||||
response.resolver_match = SimpleLazyObject(lambda: resolve(request['PATH_INFO']))
|
||||
|
||||
# Flatten a single context. Not really necessary anymore thanks to
|
||||
# the __getattr__ flattening in ContextList, but has some edge-case
|
||||
|
||||
@@ -145,7 +145,7 @@ def complex_setting_changed(**kwargs):
|
||||
@receiver(setting_changed)
|
||||
def root_urlconf_changed(**kwargs):
|
||||
if kwargs['setting'] == 'ROOT_URLCONF':
|
||||
from django.core.urlresolvers import clear_url_caches, set_urlconf
|
||||
from django.urls import clear_url_caches, set_urlconf
|
||||
clear_url_caches()
|
||||
set_urlconf(None)
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ from django.apps import apps
|
||||
from django.conf import UserSettingsHolder, settings
|
||||
from django.core import mail
|
||||
from django.core.signals import request_started
|
||||
from django.core.urlresolvers import get_script_prefix, set_script_prefix
|
||||
from django.db import reset_queries
|
||||
from django.http import request
|
||||
from django.template import Template
|
||||
from django.test.signals import setting_changed, template_rendered
|
||||
from django.urls import get_script_prefix, set_script_prefix
|
||||
from django.utils import six
|
||||
from django.utils.decorators import ContextDecorator
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
20
django/urls/__init__.py
Normal file
20
django/urls/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from .base import (
|
||||
clear_script_prefix, clear_url_caches, get_script_prefix, get_urlconf,
|
||||
is_valid_path, resolve, reverse, reverse_lazy, set_script_prefix,
|
||||
set_urlconf, translate_url,
|
||||
)
|
||||
from .exceptions import NoReverseMatch, Resolver404
|
||||
from .resolvers import (
|
||||
LocaleRegexProvider, LocaleRegexURLResolver, RegexURLPattern,
|
||||
RegexURLResolver, ResolverMatch, get_ns_resolver, get_resolver,
|
||||
)
|
||||
from .utils import get_callable, get_mod_func
|
||||
|
||||
__all__ = [
|
||||
'LocaleRegexProvider', 'LocaleRegexURLResolver', 'NoReverseMatch',
|
||||
'RegexURLPattern', 'RegexURLResolver', 'Resolver404', 'ResolverMatch',
|
||||
'clear_script_prefix', 'clear_url_caches', 'get_callable', 'get_mod_func',
|
||||
'get_ns_resolver', 'get_resolver', 'get_script_prefix', 'get_urlconf',
|
||||
'is_valid_path', 'resolve', 'reverse', 'reverse_lazy', 'set_script_prefix',
|
||||
'set_urlconf', 'translate_url',
|
||||
]
|
||||
185
django/urls/base.py
Normal file
185
django/urls/base.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from threading import local
|
||||
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text, iri_to_uri
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
|
||||
from django.utils.translation import override
|
||||
|
||||
from .exceptions import NoReverseMatch, Resolver404
|
||||
from .resolvers import get_ns_resolver, get_resolver
|
||||
from .utils import get_callable
|
||||
|
||||
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
|
||||
# the current thread (which is the only one we ever access), it is assumed to
|
||||
# be empty.
|
||||
_prefixes = local()
|
||||
|
||||
# Overridden URLconfs for each thread are stored here.
|
||||
_urlconfs = local()
|
||||
|
||||
|
||||
def resolve(path, urlconf=None):
|
||||
if urlconf is None:
|
||||
urlconf = get_urlconf()
|
||||
return get_resolver(urlconf).resolve(path)
|
||||
|
||||
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
|
||||
if urlconf is None:
|
||||
urlconf = get_urlconf()
|
||||
resolver = get_resolver(urlconf)
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
|
||||
prefix = get_script_prefix()
|
||||
|
||||
if not isinstance(viewname, six.string_types):
|
||||
view = viewname
|
||||
else:
|
||||
parts = viewname.split(':')
|
||||
parts.reverse()
|
||||
view = parts[0]
|
||||
path = parts[1:]
|
||||
|
||||
if current_app:
|
||||
current_path = current_app.split(':')
|
||||
current_path.reverse()
|
||||
else:
|
||||
current_path = None
|
||||
|
||||
resolved_path = []
|
||||
ns_pattern = ''
|
||||
while path:
|
||||
ns = path.pop()
|
||||
current_ns = current_path.pop() if current_path else None
|
||||
# Lookup the name to see if it could be an app identifier.
|
||||
try:
|
||||
app_list = resolver.app_dict[ns]
|
||||
# Yes! Path part matches an app in the current Resolver.
|
||||
if current_ns and current_ns in app_list:
|
||||
# If we are reversing for a particular app, use that
|
||||
# namespace.
|
||||
ns = current_ns
|
||||
elif ns not in app_list:
|
||||
# The name isn't shared by one of the instances (i.e.,
|
||||
# the default) so pick the first instance as the default.
|
||||
ns = app_list[0]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if ns != current_ns:
|
||||
current_path = None
|
||||
|
||||
try:
|
||||
extra, resolver = resolver.namespace_dict[ns]
|
||||
resolved_path.append(ns)
|
||||
ns_pattern = ns_pattern + extra
|
||||
except KeyError as key:
|
||||
if resolved_path:
|
||||
raise NoReverseMatch(
|
||||
"%s is not a registered namespace inside '%s'" %
|
||||
(key, ':'.join(resolved_path))
|
||||
)
|
||||
else:
|
||||
raise NoReverseMatch("%s is not a registered namespace" % key)
|
||||
if ns_pattern:
|
||||
resolver = get_ns_resolver(ns_pattern, resolver)
|
||||
|
||||
return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))
|
||||
|
||||
reverse_lazy = lazy(reverse, six.text_type)
|
||||
|
||||
|
||||
def clear_url_caches():
|
||||
get_callable.cache_clear()
|
||||
get_resolver.cache_clear()
|
||||
get_ns_resolver.cache_clear()
|
||||
|
||||
|
||||
def set_script_prefix(prefix):
|
||||
"""
|
||||
Set the script prefix for the current thread.
|
||||
"""
|
||||
if not prefix.endswith('/'):
|
||||
prefix += '/'
|
||||
_prefixes.value = prefix
|
||||
|
||||
|
||||
def get_script_prefix():
|
||||
"""
|
||||
Return the currently active script prefix. Useful for client code that
|
||||
wishes to construct their own URLs manually (although accessing the request
|
||||
instance is normally going to be a lot cleaner).
|
||||
"""
|
||||
return getattr(_prefixes, "value", '/')
|
||||
|
||||
|
||||
def clear_script_prefix():
|
||||
"""
|
||||
Unset the script prefix for the current thread.
|
||||
"""
|
||||
try:
|
||||
del _prefixes.value
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def set_urlconf(urlconf_name):
|
||||
"""
|
||||
Set the URLconf for the current thread (overriding the default one in
|
||||
settings). If urlconf_name is None, revert back to the default.
|
||||
"""
|
||||
if urlconf_name:
|
||||
_urlconfs.value = urlconf_name
|
||||
else:
|
||||
if hasattr(_urlconfs, "value"):
|
||||
del _urlconfs.value
|
||||
|
||||
|
||||
def get_urlconf(default=None):
|
||||
"""
|
||||
Return the root URLconf to use for the current thread if it has been
|
||||
changed from the default one.
|
||||
"""
|
||||
return getattr(_urlconfs, "value", default)
|
||||
|
||||
|
||||
def is_valid_path(path, urlconf=None):
|
||||
"""
|
||||
Return True if the given path resolves against the default URL resolver,
|
||||
False otherwise. This is a convenience method to make working with "is
|
||||
this a match?" cases easier, avoiding try...except blocks.
|
||||
"""
|
||||
from django.urls.base import resolve
|
||||
try:
|
||||
resolve(path, urlconf)
|
||||
return True
|
||||
except Resolver404:
|
||||
return False
|
||||
|
||||
|
||||
def translate_url(url, lang_code):
|
||||
"""
|
||||
Given a URL (absolute or relative), try to get its translated version in
|
||||
the `lang_code` language (either by i18n_patterns or by translated regex).
|
||||
Return the original URL if no translated version is found.
|
||||
"""
|
||||
from django.urls import resolve, reverse
|
||||
parsed = urlsplit(url)
|
||||
try:
|
||||
match = resolve(parsed.path)
|
||||
except Resolver404:
|
||||
pass
|
||||
else:
|
||||
to_be_reversed = "%s:%s" % (match.namespace, match.url_name) if match.namespace else match.url_name
|
||||
with override(lang_code):
|
||||
try:
|
||||
url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
else:
|
||||
url = urlunsplit((parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment))
|
||||
return url
|
||||
11
django/urls/exceptions.py
Normal file
11
django/urls/exceptions.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.http import Http404
|
||||
|
||||
|
||||
class Resolver404(Http404):
|
||||
pass
|
||||
|
||||
|
||||
class NoReverseMatch(Exception):
|
||||
pass
|
||||
393
django/urls/resolvers.py
Normal file
393
django/urls/resolvers.py
Normal file
@@ -0,0 +1,393 @@
|
||||
"""
|
||||
This module converts requested URLs to callback view functions.
|
||||
|
||||
RegexURLResolver is the main class here. Its resolve() method takes a URL (as
|
||||
a string) and returns a ResolverMatch object which provides access to all
|
||||
attributes of the resolved URL match.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
import re
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import lru_cache, six
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.encoding import force_str, force_text
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import RFC3986_SUBDELIMS, urlquote
|
||||
from django.utils.regex_helper import normalize
|
||||
from django.utils.translation import get_language
|
||||
|
||||
from .exceptions import NoReverseMatch, Resolver404
|
||||
from .utils import get_callable
|
||||
|
||||
|
||||
class ResolverMatch(object):
|
||||
def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None):
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.url_name = url_name
|
||||
|
||||
# If a URLRegexResolver doesn't have a namespace or app_name, it passes
|
||||
# in an empty value.
|
||||
self.app_names = [x for x in app_names if x] if app_names else []
|
||||
self.app_name = ':'.join(self.app_names)
|
||||
self.namespaces = [x for x in namespaces if x] if namespaces else []
|
||||
self.namespace = ':'.join(self.namespaces)
|
||||
|
||||
if not hasattr(func, '__name__'):
|
||||
# A class-based view
|
||||
self._func_path = '.'.join([func.__class__.__module__, func.__class__.__name__])
|
||||
else:
|
||||
# A function-based view
|
||||
self._func_path = '.'.join([func.__module__, func.__name__])
|
||||
|
||||
view_path = url_name or self._func_path
|
||||
self.view_name = ':'.join(self.namespaces + [view_path])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.func, self.args, self.kwargs)[index]
|
||||
|
||||
def __repr__(self):
|
||||
return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_names=%s, namespaces=%s)" % (
|
||||
self._func_path, self.args, self.kwargs, self.url_name,
|
||||
self.app_names, self.namespaces,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_resolver(urlconf=None):
|
||||
if urlconf is None:
|
||||
from django.conf import settings
|
||||
urlconf = settings.ROOT_URLCONF
|
||||
return RegexURLResolver(r'^/', urlconf)
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_ns_resolver(ns_pattern, resolver):
|
||||
# Build a namespaced resolver for the given parent URLconf pattern.
|
||||
# This makes it possible to have captured parameters in the parent
|
||||
# URLconf pattern.
|
||||
ns_resolver = RegexURLResolver(ns_pattern, resolver.url_patterns)
|
||||
return RegexURLResolver(r'^/', [ns_resolver])
|
||||
|
||||
|
||||
class LocaleRegexProvider(object):
|
||||
"""
|
||||
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 ugettext_lazy) representing a regular
|
||||
# expression.
|
||||
self._regex = regex
|
||||
self._regex_dict = {}
|
||||
|
||||
@property
|
||||
def regex(self):
|
||||
"""
|
||||
Return a compiled regular expression based on the activate language.
|
||||
"""
|
||||
language_code = get_language()
|
||||
if language_code not in self._regex_dict:
|
||||
regex = self._regex if isinstance(self._regex, six.string_types) else force_text(self._regex)
|
||||
try:
|
||||
compiled_regex = re.compile(regex, re.UNICODE)
|
||||
except re.error as e:
|
||||
raise ImproperlyConfigured(
|
||||
'"%s" is not a valid regular expression: %s' %
|
||||
(regex, six.text_type(e))
|
||||
)
|
||||
self._regex_dict[language_code] = compiled_regex
|
||||
return self._regex_dict[language_code]
|
||||
|
||||
|
||||
class RegexURLPattern(LocaleRegexProvider):
|
||||
def __init__(self, regex, callback, default_args=None, name=None):
|
||||
LocaleRegexProvider.__init__(self, regex)
|
||||
self.callback = callback # the view
|
||||
self.default_args = default_args or {}
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
|
||||
|
||||
def resolve(self, path):
|
||||
match = self.regex.search(path)
|
||||
if match:
|
||||
# If there are any named groups, use those as kwargs, ignoring
|
||||
# non-named groups. Otherwise, pass all non-named arguments as
|
||||
# positional arguments.
|
||||
kwargs = match.groupdict()
|
||||
args = () if kwargs else match.groups()
|
||||
# In both cases, pass any extra_kwargs as **kwargs.
|
||||
kwargs.update(self.default_args)
|
||||
return ResolverMatch(self.callback, args, kwargs, self.name)
|
||||
|
||||
@cached_property
|
||||
def lookup_str(self):
|
||||
"""
|
||||
A string that identifies the view (e.g. 'path.to.view_function' or
|
||||
'path.to.ClassBasedView').
|
||||
"""
|
||||
callback = self.callback
|
||||
if isinstance(callback, functools.partial):
|
||||
callback = callback.func
|
||||
if not hasattr(callback, '__name__'):
|
||||
return callback.__module__ + "." + callback.__class__.__name__
|
||||
else:
|
||||
return callback.__module__ + "." + callback.__name__
|
||||
|
||||
|
||||
class RegexURLResolver(LocaleRegexProvider):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||
LocaleRegexProvider.__init__(self, regex)
|
||||
# urlconf_name is the dotted Python path to the module defining
|
||||
# urlpatterns. It may also be an object with an urlpatterns attribute
|
||||
# or urlpatterns itself.
|
||||
self.urlconf_name = urlconf_name
|
||||
self.callback = None
|
||||
self.default_kwargs = default_kwargs or {}
|
||||
self.namespace = namespace
|
||||
self.app_name = app_name
|
||||
self._reverse_dict = {}
|
||||
self._namespace_dict = {}
|
||||
self._app_dict = {}
|
||||
# set of dotted paths to all functions and classes that are used in
|
||||
# urlpatterns
|
||||
self._callback_strs = set()
|
||||
self._populated = False
|
||||
|
||||
def __repr__(self):
|
||||
if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
|
||||
# Don't bother to output the whole list, it can be huge
|
||||
urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
|
||||
else:
|
||||
urlconf_repr = repr(self.urlconf_name)
|
||||
return str('<%s %s (%s:%s) %s>') % (
|
||||
self.__class__.__name__, urlconf_repr, self.app_name,
|
||||
self.namespace, self.regex.pattern,
|
||||
)
|
||||
|
||||
def _populate(self):
|
||||
lookups = MultiValueDict()
|
||||
namespaces = {}
|
||||
apps = {}
|
||||
language_code = get_language()
|
||||
for pattern in reversed(self.url_patterns):
|
||||
if isinstance(pattern, RegexURLPattern):
|
||||
self._callback_strs.add(pattern.lookup_str)
|
||||
p_pattern = pattern.regex.pattern
|
||||
if p_pattern.startswith('^'):
|
||||
p_pattern = p_pattern[1:]
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if pattern.namespace:
|
||||
namespaces[pattern.namespace] = (p_pattern, pattern)
|
||||
if pattern.app_name:
|
||||
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
|
||||
else:
|
||||
parent_pat = pattern.regex.pattern
|
||||
for name in pattern.reverse_dict:
|
||||
for matches, pat, defaults in pattern.reverse_dict.getlist(name):
|
||||
new_matches = normalize(parent_pat + pat)
|
||||
lookups.appendlist(
|
||||
name,
|
||||
(
|
||||
new_matches,
|
||||
p_pattern + pat,
|
||||
dict(defaults, **pattern.default_kwargs),
|
||||
)
|
||||
)
|
||||
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
|
||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||
for app_name, namespace_list in pattern.app_dict.items():
|
||||
apps.setdefault(app_name, []).extend(namespace_list)
|
||||
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._app_dict[language_code] = apps
|
||||
self._populated = True
|
||||
|
||||
@property
|
||||
def reverse_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._reverse_dict:
|
||||
self._populate()
|
||||
return self._reverse_dict[language_code]
|
||||
|
||||
@property
|
||||
def namespace_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._namespace_dict:
|
||||
self._populate()
|
||||
return self._namespace_dict[language_code]
|
||||
|
||||
@property
|
||||
def app_dict(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._app_dict:
|
||||
self._populate()
|
||||
return self._app_dict[language_code]
|
||||
|
||||
def _is_callback(self, name):
|
||||
if not self._populated:
|
||||
self._populate()
|
||||
return name in self._callback_strs
|
||||
|
||||
def resolve(self, path):
|
||||
path = force_text(path) # path may be a reverse_lazy object
|
||||
tried = []
|
||||
match = self.regex.search(path)
|
||||
if match:
|
||||
new_path = path[match.end():]
|
||||
for pattern in self.url_patterns:
|
||||
try:
|
||||
sub_match = pattern.resolve(new_path)
|
||||
except Resolver404 as e:
|
||||
sub_tried = e.args[0].get('tried')
|
||||
if sub_tried is not None:
|
||||
tried.extend([pattern] + t for t in sub_tried)
|
||||
else:
|
||||
tried.append([pattern])
|
||||
else:
|
||||
if sub_match:
|
||||
# Merge captured arguments in match with submatch
|
||||
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
|
||||
sub_match_dict.update(sub_match.kwargs)
|
||||
|
||||
# If there are *any* named groups, ignore all non-named groups.
|
||||
# Otherwise, pass all non-named arguments as positional arguments.
|
||||
sub_match_args = sub_match.args
|
||||
if not sub_match_dict:
|
||||
sub_match_args = match.groups() + sub_match.args
|
||||
|
||||
return ResolverMatch(
|
||||
sub_match.func,
|
||||
sub_match_args,
|
||||
sub_match_dict,
|
||||
sub_match.url_name,
|
||||
[self.app_name] + sub_match.app_names,
|
||||
[self.namespace] + sub_match.namespaces,
|
||||
)
|
||||
tried.append([pattern])
|
||||
raise Resolver404({'tried': tried, 'path': new_path})
|
||||
raise Resolver404({'path': path})
|
||||
|
||||
@cached_property
|
||||
def urlconf_module(self):
|
||||
if isinstance(self.urlconf_name, six.string_types):
|
||||
return import_module(self.urlconf_name)
|
||||
else:
|
||||
return self.urlconf_name
|
||||
|
||||
@cached_property
|
||||
def url_patterns(self):
|
||||
# urlconf_module might be a valid set of patterns, so we default to it
|
||||
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
|
||||
try:
|
||||
iter(patterns)
|
||||
except TypeError:
|
||||
msg = (
|
||||
"The included URLconf '{name}' does not appear to have any "
|
||||
"patterns in it. If you see valid patterns in the file then "
|
||||
"the issue is probably caused by a circular import."
|
||||
)
|
||||
raise ImproperlyConfigured(msg.format(name=self.urlconf_name))
|
||||
return patterns
|
||||
|
||||
def resolve_error_handler(self, view_type):
|
||||
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
|
||||
if not callback:
|
||||
# No handler specified in file; use lazy import, since
|
||||
# django.conf.urls imports this file.
|
||||
from django.conf import urls
|
||||
callback = getattr(urls, 'handler%s' % view_type)
|
||||
return get_callable(callback), {}
|
||||
|
||||
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
||||
if args and kwargs:
|
||||
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
||||
text_args = [force_text(v) for v in args]
|
||||
text_kwargs = {k: force_text(v) for (k, v) in kwargs.items()}
|
||||
|
||||
if not self._populated:
|
||||
self._populate()
|
||||
|
||||
possibilities = self.reverse_dict.getlist(lookup_view)
|
||||
|
||||
for possibility, pattern, defaults in possibilities:
|
||||
for result, params in possibility:
|
||||
if args:
|
||||
if len(args) != len(params):
|
||||
continue
|
||||
candidate_subs = dict(zip(params, text_args))
|
||||
else:
|
||||
if (set(kwargs.keys()) | set(defaults.keys()) != set(params) |
|
||||
set(defaults.keys())):
|
||||
continue
|
||||
matches = True
|
||||
for k, v in defaults.items():
|
||||
if kwargs.get(k, v) != v:
|
||||
matches = False
|
||||
break
|
||||
if not matches:
|
||||
continue
|
||||
candidate_subs = text_kwargs
|
||||
# WSGI provides decoded URLs, without %xx escapes, and the URL
|
||||
# resolver operates on such URLs. First substitute arguments
|
||||
# without quoting to build a decoded URL and look for a match.
|
||||
# Then, if we have a match, redo the substitution with quoted
|
||||
# arguments in order to return a properly encoded URL.
|
||||
candidate_pat = _prefix.replace('%', '%%') + result
|
||||
if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % candidate_subs, re.UNICODE):
|
||||
# safe characters from `pchar` definition of RFC 3986
|
||||
url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@'))
|
||||
# Don't allow construction of scheme relative urls.
|
||||
if url.startswith('//'):
|
||||
url = '/%%2F%s' % url[2:]
|
||||
return url
|
||||
# lookup_view can be URL name or callable, but callables are not
|
||||
# friendly in error messages.
|
||||
m = getattr(lookup_view, '__module__', None)
|
||||
n = getattr(lookup_view, '__name__', None)
|
||||
if m is not None and n is not None:
|
||||
lookup_view_s = "%s.%s" % (m, n)
|
||||
else:
|
||||
lookup_view_s = lookup_view
|
||||
|
||||
patterns = [pattern for (possibility, pattern, defaults) in possibilities]
|
||||
raise NoReverseMatch(
|
||||
"Reverse for '%s' with arguments '%s' and keyword "
|
||||
"arguments '%s' not found. %d pattern(s) tried: %s" %
|
||||
(lookup_view_s, args, kwargs, len(patterns), patterns)
|
||||
)
|
||||
|
||||
|
||||
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):
|
||||
super(LocaleRegexURLResolver, self).__init__(
|
||||
None, urlconf_name, default_kwargs, app_name, namespace,
|
||||
)
|
||||
|
||||
@property
|
||||
def regex(self):
|
||||
language_code = get_language()
|
||||
if language_code not in self._regex_dict:
|
||||
regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
|
||||
self._regex_dict[language_code] = regex_compiled
|
||||
return self._regex_dict[language_code]
|
||||
64
django/urls/utils.py
Normal file
64
django/urls/utils.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
from django.core.exceptions import ViewDoesNotExist
|
||||
from django.utils import lru_cache, six
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_callable(lookup_view):
|
||||
"""
|
||||
Return a callable corresponding to lookup_view.
|
||||
* If lookup_view is already a callable, return it.
|
||||
* If lookup_view is a string import path that can be resolved to a callable,
|
||||
import that callable and return it, otherwise raise an exception
|
||||
(ImportError or ViewDoesNotExist).
|
||||
"""
|
||||
if callable(lookup_view):
|
||||
return lookup_view
|
||||
|
||||
if not isinstance(lookup_view, six.string_types):
|
||||
raise ViewDoesNotExist("'%s' is not a callable or a dot-notation path" % lookup_view)
|
||||
|
||||
mod_name, func_name = get_mod_func(lookup_view)
|
||||
if not func_name: # No '.' in lookup_view
|
||||
raise ImportError("Could not import '%s'. The path must be fully qualified." % lookup_view)
|
||||
|
||||
try:
|
||||
mod = import_module(mod_name)
|
||||
except ImportError:
|
||||
parentmod, submod = get_mod_func(mod_name)
|
||||
if submod and not module_has_submodule(import_module(parentmod), submod):
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s'. Parent module %s does not exist." %
|
||||
(lookup_view, mod_name)
|
||||
)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
try:
|
||||
view_func = getattr(mod, func_name)
|
||||
except AttributeError:
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s'. View does not exist in module %s." %
|
||||
(lookup_view, mod_name)
|
||||
)
|
||||
else:
|
||||
if not callable(view_func):
|
||||
raise ViewDoesNotExist(
|
||||
"Could not import '%s.%s'. View is not callable." %
|
||||
(mod_name, func_name)
|
||||
)
|
||||
return view_func
|
||||
|
||||
|
||||
def get_mod_func(callback):
|
||||
# Convert 'django.views.news.stories.story_detail' to
|
||||
# ['django.views.news.stories', 'story_detail']
|
||||
try:
|
||||
dot = callback.rindex('.')
|
||||
except ValueError:
|
||||
return callback, ''
|
||||
return callback[:dot], callback[dot + 1:]
|
||||
@@ -5,10 +5,10 @@ import sys
|
||||
import types
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import Resolver404, resolve
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.template import Context, Engine, TemplateDoesNotExist
|
||||
from django.template.defaultfilters import force_escape, pprint
|
||||
from django.urls import Resolver404, resolve
|
||||
from django.utils import lru_cache, six, timezone
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.encoding import force_bytes, smart_text
|
||||
|
||||
@@ -5,8 +5,8 @@ from functools import update_wrapper
|
||||
|
||||
from django import http
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils import six
|
||||
from django.utils.decorators import classonlymethod
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import os
|
||||
from django import http
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import translate_url
|
||||
from django.template import Context, Engine
|
||||
from django.urls import translate_url
|
||||
from django.utils import six
|
||||
from django.utils._os import upath
|
||||
from django.utils.encoding import smart_text
|
||||
|
||||
Reference in New Issue
Block a user