123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828 |
- """
- This module converts requested URLs to callback view functions.
-
- URLResolver 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.
- """
- import functools
- import inspect
- import re
- import string
- from importlib import import_module
- from pickle import PicklingError
- from urllib.parse import quote
-
- from asgiref.local import Local
-
- from django.conf import settings
- from django.core.checks import Error, Warning
- from django.core.checks.urls import check_resolver
- from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
- from django.utils.datastructures import MultiValueDict
- from django.utils.functional import cached_property
- from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
- from django.utils.regex_helper import _lazy_re_compile, normalize
- from django.utils.translation import get_language
-
- from .converters import get_converter
- from .exceptions import NoReverseMatch, Resolver404
- from .utils import get_callable
-
-
- class ResolverMatch:
- def __init__(
- self,
- func,
- args,
- kwargs,
- url_name=None,
- app_names=None,
- namespaces=None,
- route=None,
- tried=None,
- captured_kwargs=None,
- extra_kwargs=None,
- ):
- self.func = func
- self.args = args
- self.kwargs = kwargs
- self.url_name = url_name
- self.route = route
- self.tried = tried
- self.captured_kwargs = captured_kwargs
- self.extra_kwargs = extra_kwargs
-
- # 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 hasattr(func, "view_class"):
- func = func.view_class
- if not hasattr(func, "__name__"):
- # A class-based view
- self._func_path = func.__class__.__module__ + "." + func.__class__.__name__
- else:
- # A function-based view
- self._func_path = 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):
- if isinstance(self.func, functools.partial):
- func = repr(self.func)
- else:
- func = self._func_path
- return (
- "ResolverMatch(func=%s, args=%r, kwargs=%r, url_name=%r, "
- "app_names=%r, namespaces=%r, route=%r%s%s)"
- % (
- func,
- self.args,
- self.kwargs,
- self.url_name,
- self.app_names,
- self.namespaces,
- self.route,
- f", captured_kwargs={self.captured_kwargs!r}"
- if self.captured_kwargs
- else "",
- f", extra_kwargs={self.extra_kwargs!r}" if self.extra_kwargs else "",
- )
- )
-
- def __reduce_ex__(self, protocol):
- raise PicklingError(f"Cannot pickle {self.__class__.__qualname__}.")
-
-
- def get_resolver(urlconf=None):
- if urlconf is None:
- urlconf = settings.ROOT_URLCONF
- return _get_cached_resolver(urlconf)
-
-
- @functools.lru_cache(maxsize=None)
- def _get_cached_resolver(urlconf=None):
- return URLResolver(RegexPattern(r"^/"), urlconf)
-
-
- @functools.lru_cache(maxsize=None)
- def get_ns_resolver(ns_pattern, resolver, converters):
- # Build a namespaced resolver for the given parent URLconf pattern.
- # This makes it possible to have captured parameters in the parent
- # URLconf pattern.
- pattern = RegexPattern(ns_pattern)
- pattern.converters = dict(converters)
- ns_resolver = URLResolver(pattern, resolver.url_patterns)
- return URLResolver(RegexPattern(r"^/"), [ns_resolver])
-
-
- class LocaleRegexDescriptor:
- def __init__(self, attr):
- self.attr = attr
-
- def __get__(self, instance, cls=None):
- """
- Return a compiled regular expression based on the active language.
- """
- if instance is None:
- return self
- # As a performance optimization, if the given regex string is a regular
- # string (not a lazily-translated string proxy), compile it once and
- # avoid per-language compilation.
- pattern = getattr(instance, self.attr)
- if isinstance(pattern, str):
- instance.__dict__["regex"] = instance._compile(pattern)
- return instance.__dict__["regex"]
- language_code = get_language()
- if language_code not in instance._regex_dict:
- instance._regex_dict[language_code] = instance._compile(str(pattern))
- return instance._regex_dict[language_code]
-
-
- class CheckURLMixin:
- def describe(self):
- """
- Format the URL pattern for display in warning messages.
- """
- description = "'{}'".format(self)
- if self.name:
- description += " [name='{}']".format(self.name)
- return description
-
- def _check_pattern_startswith_slash(self):
- """
- Check that the pattern does not begin with a forward slash.
- """
- regex_pattern = self.regex.pattern
- if not settings.APPEND_SLASH:
- # Skip check as it can be useful to start a URL pattern with a slash
- # when APPEND_SLASH=False.
- return []
- if regex_pattern.startswith(("/", "^/", "^\\/")) and not regex_pattern.endswith(
- "/"
- ):
- warning = Warning(
- "Your URL pattern {} has a route 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 '/'.".format(
- self.describe()
- ),
- id="urls.W002",
- )
- return [warning]
- else:
- return []
-
-
- class RegexPattern(CheckURLMixin):
- regex = LocaleRegexDescriptor("_regex")
-
- def __init__(self, regex, name=None, is_endpoint=False):
- self._regex = regex
- self._regex_dict = {}
- self._is_endpoint = is_endpoint
- self.name = name
- self.converters = {}
-
- def match(self, path):
- match = (
- self.regex.fullmatch(path)
- if self._is_endpoint and self.regex.pattern.endswith("$")
- else 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()
- kwargs = {k: v for k, v in kwargs.items() if v is not None}
- 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)
- ) from e
-
- def __str__(self):
- return str(self._regex)
-
-
- _PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile(
- r"<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>"
- )
-
-
- 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
- elif not set(match.group()).isdisjoint(string.whitespace):
- raise ImproperlyConfigured(
- "URL route '%s' cannot contain whitespace in angle brackets "
- "<…>." % original_route
- )
- parts.append(re.escape(route[: match.start()]))
- route = route[match.end() :]
- parameter = match["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["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 %r uses invalid converter %r."
- % (original_route, raw_converter)
- ) from e
- converters[parameter] = converter
- parts.append("(?P<" + parameter + ">" + converter.regex + ")")
- if is_endpoint:
- parts.append(r"\Z")
- 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):
- warnings = self._check_pattern_startswith_slash()
- route = self._route
- if "(?P<" in route or route.startswith("^") or route.endswith("$"):
- warnings.append(
- Warning(
- "Your URL pattern {} has a route that contains '(?P<', begins "
- "with a '^', or ends with a '$'. This was likely an oversight "
- "when migrating to django.urls.path().".format(self.describe()),
- id="2_0.W001",
- )
- )
- return warnings
-
- def _compile(self, route):
- return re.compile(_route_to_regex(route, self._is_endpoint)[0])
-
- def __str__(self):
- return str(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(re.escape(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())
- warnings.extend(self._check_callback())
- 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 _check_callback(self):
- from django.views import View
-
- view = self.callback
- if inspect.isclass(view) and issubclass(view, View):
- return [
- Error(
- "Your URL pattern %s has an invalid view, pass %s.as_view() "
- "instead of %s."
- % (
- self.pattern.describe(),
- view.__name__,
- view.__name__,
- ),
- id="urls.E009",
- )
- ]
- return []
-
- def resolve(self, path):
- match = self.pattern.match(path)
- if match:
- new_path, args, captured_kwargs = match
- # Pass any default args as **kwargs.
- kwargs = {**captured_kwargs, **self.default_args}
- return ResolverMatch(
- self.callback,
- args,
- kwargs,
- self.pattern.name,
- route=str(self.pattern),
- captured_kwargs=captured_kwargs,
- extra_kwargs=self.default_args,
- )
-
- @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 hasattr(callback, "view_class"):
- callback = callback.view_class
- elif not hasattr(callback, "__name__"):
- return callback.__module__ + "." + callback.__class__.__name__
- return callback.__module__ + "." + callback.__qualname__
-
-
- class URLResolver:
- def __init__(
- self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None
- ):
- self.pattern = pattern
- # 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
- self._local = Local()
-
- def __repr__(self):
- if isinstance(self.urlconf_name, list) and 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 "<%s %s (%s:%s) %s>" % (
- self.__class__.__name__,
- urlconf_repr,
- self.app_name,
- self.namespace,
- self.pattern.describe(),
- )
-
- def check(self):
- messages = []
- for pattern in self.url_patterns:
- messages.extend(check_resolver(pattern))
- messages.extend(self._check_custom_error_handlers())
- return messages or self.pattern.check()
-
- def _check_custom_error_handlers(self):
- messages = []
- # All handlers take (request, exception) arguments except handler500
- # which takes (request).
- for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]:
- try:
- handler = self.resolve_error_handler(status_code)
- except (ImportError, ViewDoesNotExist) as e:
- path = getattr(self.urlconf_module, "handler%s" % status_code)
- msg = (
- "The custom handler{status_code} view '{path}' could not be "
- "imported."
- ).format(status_code=status_code, path=path)
- messages.append(Error(msg, hint=str(e), id="urls.E008"))
- continue
- signature = inspect.signature(handler)
- args = [None] * num_parameters
- try:
- signature.bind(*args)
- except TypeError:
- msg = (
- "The custom handler{status_code} view '{path}' does not "
- "take the correct number of arguments ({args})."
- ).format(
- status_code=status_code,
- path=handler.__module__ + "." + handler.__qualname__,
- args="request, exception" if num_parameters == 2 else "request",
- )
- messages.append(Error(msg, id="urls.E007"))
- return messages
-
- def _populate(self):
- # Short-circuit if called recursively in this thread to prevent
- # infinite recursion. Concurrent threads may call this at the same
- # time and will need to continue, so set 'populating' on a
- # thread-local variable.
- if getattr(self._local, "populating", False):
- return
- try:
- self._local.populating = True
- lookups = MultiValueDict()
- namespaces = {}
- apps = {}
- language_code = get_language()
- for url_pattern in reversed(self.url_patterns):
- p_pattern = url_pattern.pattern.regex.pattern
- if p_pattern.startswith("^"):
- p_pattern = p_pattern[1:]
- if isinstance(url_pattern, URLPattern):
- self._callback_strs.add(url_pattern.lookup_str)
- bits = normalize(url_pattern.pattern.regex.pattern)
- lookups.appendlist(
- 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:
- for name in url_pattern.reverse_dict:
- for (
- matches,
- pat,
- defaults,
- converters,
- ) in url_pattern.reverse_dict.getlist(name):
- new_matches = normalize(p_pattern + pat)
- lookups.appendlist(
- name,
- (
- new_matches,
- p_pattern + pat,
- {**defaults, **url_pattern.default_kwargs},
- {
- **self.pattern.converters,
- **url_pattern.pattern.converters,
- **converters,
- },
- ),
- )
- for namespace, (
- prefix,
- sub_pattern,
- ) in url_pattern.namespace_dict.items():
- current_converters = url_pattern.pattern.converters
- sub_pattern.pattern.converters.update(current_converters)
- namespaces[namespace] = (p_pattern + prefix, sub_pattern)
- for app_name, namespace_list in url_pattern.app_dict.items():
- apps.setdefault(app_name, []).extend(namespace_list)
- self._callback_strs.update(url_pattern._callback_strs)
- self._namespace_dict[language_code] = namespaces
- self._app_dict[language_code] = apps
- self._reverse_dict[language_code] = lookups
- self._populated = True
- finally:
- self._local.populating = False
-
- @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]
-
- @staticmethod
- def _extend_tried(tried, pattern, sub_tried=None):
- if sub_tried is None:
- tried.append([pattern])
- else:
- tried.extend([pattern, *t] for t in sub_tried)
-
- @staticmethod
- def _join_route(route1, route2):
- """Join two routes, without the starting ^ in the second route."""
- if not route1:
- return route2
- if route2.startswith("^"):
- route2 = route2[1:]
- return route1 + route2
-
- def _is_callback(self, name):
- if not self._populated:
- self._populate()
- return name in self._callback_strs
-
- def resolve(self, path):
- path = str(path) # path may be a reverse_lazy object
- tried = []
- match = self.pattern.match(path)
- if match:
- new_path, args, kwargs = match
- for pattern in self.url_patterns:
- try:
- sub_match = pattern.resolve(new_path)
- except Resolver404 as e:
- self._extend_tried(tried, pattern, e.args[0].get("tried"))
- else:
- if sub_match:
- # Merge captured arguments in match with submatch
- sub_match_dict = {**kwargs, **self.default_kwargs}
- # Update the sub_match_dict with the kwargs from the sub_match.
- 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 = args + sub_match.args
- current_route = (
- ""
- if isinstance(pattern, URLPattern)
- else str(pattern.pattern)
- )
- self._extend_tried(tried, pattern, sub_match.tried)
- 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,
- self._join_route(current_route, sub_match.route),
- tried,
- captured_kwargs=sub_match.captured_kwargs,
- extra_kwargs={
- **self.default_kwargs,
- **sub_match.extra_kwargs,
- },
- )
- 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, str):
- 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 as e:
- msg = (
- "The included URLconf '{name}' does not appear to have "
- "any patterns in it. If you see the 'urlpatterns' variable "
- "with valid patterns in the file then the issue is probably "
- "caused by a circular import."
- )
- raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e
- 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(self, lookup_view, *args, **kwargs):
- return self._reverse_with_prefix(lookup_view, "", *args, **kwargs)
-
- 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()!")
-
- if not self._populated:
- self._populate()
-
- possibilities = self.reverse_dict.getlist(lookup_view)
-
- for possibility, pattern, defaults, converters in possibilities:
- for result, params in possibility:
- if args:
- if len(args) != len(params):
- continue
- candidate_subs = dict(zip(params, args))
- else:
- if set(kwargs).symmetric_difference(params).difference(defaults):
- continue
- matches = True
- for k, v in defaults.items():
- if k in params:
- continue
- if kwargs.get(k, v) != v:
- matches = False
- break
- if not matches:
- continue
- candidate_subs = kwargs
- # Convert the candidate subs to text using Converter.to_url().
- text_candidate_subs = {}
- match = True
- for k, v in candidate_subs.items():
- if k in converters:
- try:
- text_candidate_subs[k] = converters[k].to_url(v)
- except ValueError:
- match = False
- break
- else:
- text_candidate_subs[k] = str(v)
- if not match:
- continue
- # 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 % text_candidate_subs,
- ):
- # safe characters from `pchar` definition of RFC 3986
- url = quote(
- candidate_pat % text_candidate_subs,
- safe=RFC3986_SUBDELIMS + "/~:@",
- )
- # Don't allow construction of scheme relative urls.
- return escape_leading_slashes(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 (_, pattern, _, _) in possibilities]
- if patterns:
- if args:
- arg_msg = "arguments '%s'" % (args,)
- elif kwargs:
- arg_msg = "keyword arguments '%s'" % kwargs
- else:
- arg_msg = "no arguments"
- msg = "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % (
- lookup_view_s,
- arg_msg,
- len(patterns),
- patterns,
- )
- else:
- msg = (
- "Reverse for '%(view)s' not found. '%(view)s' is not "
- "a valid view function or pattern name." % {"view": lookup_view_s}
- )
- raise NoReverseMatch(msg)
|