Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/urls/resolvers.py: 76%
448 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2This module converts requested URLs to callback view functions.
4URLResolver is the main class here. Its resolve() method takes a URL (as
5a string) and returns a ResolverMatch object which provides access to all
6attributes of the resolved URL match.
7"""
8import functools
9import inspect
10import re
11import string
12from importlib import import_module
13from pickle import PicklingError
14from urllib.parse import quote
16from asgiref.local import Local
18from django.conf import settings
19from django.core.checks import Error, Warning
20from django.core.checks.urls import check_resolver
21from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
22from django.utils.datastructures import MultiValueDict
23from django.utils.functional import cached_property
24from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
25from django.utils.regex_helper import _lazy_re_compile, normalize
26from django.utils.translation import get_language
28from .converters import get_converter
29from .exceptions import NoReverseMatch, Resolver404
30from .utils import get_callable
33class ResolverMatch:
34 def __init__(
35 self,
36 func,
37 args,
38 kwargs,
39 url_name=None,
40 app_names=None,
41 namespaces=None,
42 route=None,
43 tried=None,
44 ):
45 self.func = func
46 self.args = args
47 self.kwargs = kwargs
48 self.url_name = url_name
49 self.route = route
50 self.tried = tried
52 # If a URLRegexResolver doesn't have a namespace or app_name, it passes
53 # in an empty value.
54 self.app_names = [x for x in app_names if x] if app_names else []
55 self.app_name = ":".join(self.app_names)
56 self.namespaces = [x for x in namespaces if x] if namespaces else []
57 self.namespace = ":".join(self.namespaces)
59 if hasattr(func, "view_class"):
60 func = func.view_class
61 if not hasattr(func, "__name__"): 61 ↛ 63line 61 didn't jump to line 63, because the condition on line 61 was never true
62 # A class-based view
63 self._func_path = func.__class__.__module__ + "." + func.__class__.__name__
64 else:
65 # A function-based view
66 self._func_path = func.__module__ + "." + func.__name__
68 view_path = url_name or self._func_path
69 self.view_name = ":".join(self.namespaces + [view_path])
71 def __getitem__(self, index):
72 return (self.func, self.args, self.kwargs)[index]
74 def __repr__(self):
75 if isinstance(self.func, functools.partial):
76 func = repr(self.func)
77 else:
78 func = self._func_path
79 return (
80 "ResolverMatch(func=%s, args=%r, kwargs=%r, url_name=%r, "
81 "app_names=%r, namespaces=%r, route=%r)"
82 % (
83 func,
84 self.args,
85 self.kwargs,
86 self.url_name,
87 self.app_names,
88 self.namespaces,
89 self.route,
90 )
91 )
93 def __reduce_ex__(self, protocol):
94 raise PicklingError(f"Cannot pickle {self.__class__.__qualname__}.")
97def get_resolver(urlconf=None):
98 if urlconf is None: 98 ↛ 100line 98 didn't jump to line 100, because the condition on line 98 was never false
99 urlconf = settings.ROOT_URLCONF
100 return _get_cached_resolver(urlconf)
103@functools.lru_cache(maxsize=None)
104def _get_cached_resolver(urlconf=None):
105 return URLResolver(RegexPattern(r"^/"), urlconf)
108@functools.lru_cache(maxsize=None)
109def get_ns_resolver(ns_pattern, resolver, converters):
110 # Build a namespaced resolver for the given parent URLconf pattern.
111 # This makes it possible to have captured parameters in the parent
112 # URLconf pattern.
113 pattern = RegexPattern(ns_pattern)
114 pattern.converters = dict(converters)
115 ns_resolver = URLResolver(pattern, resolver.url_patterns)
116 return URLResolver(RegexPattern(r"^/"), [ns_resolver])
119class LocaleRegexDescriptor:
120 def __init__(self, attr):
121 self.attr = attr
123 def __get__(self, instance, cls=None):
124 """
125 Return a compiled regular expression based on the active language.
126 """
127 if instance is None: 127 ↛ 128line 127 didn't jump to line 128, because the condition on line 127 was never true
128 return self
129 # As a performance optimization, if the given regex string is a regular
130 # string (not a lazily-translated string proxy), compile it once and
131 # avoid per-language compilation.
132 pattern = getattr(instance, self.attr)
133 if isinstance(pattern, str): 133 ↛ 136line 133 didn't jump to line 136, because the condition on line 133 was never false
134 instance.__dict__["regex"] = instance._compile(pattern)
135 return instance.__dict__["regex"]
136 language_code = get_language()
137 if language_code not in instance._regex_dict:
138 instance._regex_dict[language_code] = instance._compile(str(pattern))
139 return instance._regex_dict[language_code]
142class CheckURLMixin:
143 def describe(self):
144 """
145 Format the URL pattern for display in warning messages.
146 """
147 description = "'{}'".format(self)
148 if self.name:
149 description += " [name='{}']".format(self.name)
150 return description
152 def _check_pattern_startswith_slash(self):
153 """
154 Check that the pattern does not begin with a forward slash.
155 """
156 regex_pattern = self.regex.pattern
157 if not settings.APPEND_SLASH: 157 ↛ 160line 157 didn't jump to line 160, because the condition on line 157 was never true
158 # Skip check as it can be useful to start a URL pattern with a slash
159 # when APPEND_SLASH=False.
160 return []
161 if regex_pattern.startswith(("/", "^/", "^\\/")) and not regex_pattern.endswith( 161 ↛ 164line 161 didn't jump to line 164, because the condition on line 161 was never true
162 "/"
163 ):
164 warning = Warning(
165 "Your URL pattern {} has a route beginning with a '/'. Remove this "
166 "slash as it is unnecessary. If this pattern is targeted in an "
167 "include(), ensure the include() pattern has a trailing '/'.".format(
168 self.describe()
169 ),
170 id="urls.W002",
171 )
172 return [warning]
173 else:
174 return []
177class RegexPattern(CheckURLMixin):
178 regex = LocaleRegexDescriptor("_regex")
180 def __init__(self, regex, name=None, is_endpoint=False):
181 self._regex = regex
182 self._regex_dict = {}
183 self._is_endpoint = is_endpoint
184 self.name = name
185 self.converters = {}
187 def match(self, path):
188 match = (
189 self.regex.fullmatch(path)
190 if self._is_endpoint and self.regex.pattern.endswith("$")
191 else self.regex.search(path)
192 )
193 if match:
194 # If there are any named groups, use those as kwargs, ignoring
195 # non-named groups. Otherwise, pass all non-named arguments as
196 # positional arguments.
197 kwargs = match.groupdict()
198 args = () if kwargs else match.groups()
199 kwargs = {k: v for k, v in kwargs.items() if v is not None}
200 return path[match.end() :], args, kwargs
201 return None
203 def check(self):
204 warnings = []
205 warnings.extend(self._check_pattern_startswith_slash())
206 if not self._is_endpoint:
207 warnings.extend(self._check_include_trailing_dollar())
208 return warnings
210 def _check_include_trailing_dollar(self):
211 regex_pattern = self.regex.pattern
212 if regex_pattern.endswith("$") and not regex_pattern.endswith(r"\$"): 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true
213 return [
214 Warning(
215 "Your URL pattern {} uses include with a route ending with a '$'. "
216 "Remove the dollar from the route to avoid problems including "
217 "URLs.".format(self.describe()),
218 id="urls.W001",
219 )
220 ]
221 else:
222 return []
224 def _compile(self, regex):
225 """Compile and return the given regular expression."""
226 try:
227 return re.compile(regex)
228 except re.error as e:
229 raise ImproperlyConfigured(
230 '"%s" is not a valid regular expression: %s' % (regex, e)
231 ) from e
233 def __str__(self):
234 return str(self._regex)
237_PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile(
238 r"<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>"
239)
242def _route_to_regex(route, is_endpoint=False):
243 """
244 Convert a path pattern into a regular expression. Return the regular
245 expression and a dictionary mapping the capture names to the converters.
246 For example, 'foo/<int:pk>' returns '^foo\\/(?P<pk>[0-9]+)'
247 and {'pk': <django.urls.converters.IntConverter>}.
248 """
249 original_route = route
250 parts = ["^"]
251 converters = {}
252 while True:
253 match = _PATH_PARAMETER_COMPONENT_RE.search(route)
254 if not match:
255 parts.append(re.escape(route))
256 break
257 elif not set(match.group()).isdisjoint(string.whitespace): 257 ↛ 258line 257 didn't jump to line 258, because the condition on line 257 was never true
258 raise ImproperlyConfigured(
259 "URL route '%s' cannot contain whitespace in angle brackets "
260 "<…>." % original_route
261 )
262 parts.append(re.escape(route[: match.start()]))
263 route = route[match.end() :]
264 parameter = match["parameter"]
265 if not parameter.isidentifier(): 265 ↛ 266line 265 didn't jump to line 266, because the condition on line 265 was never true
266 raise ImproperlyConfigured(
267 "URL route '%s' uses parameter name %r which isn't a valid "
268 "Python identifier." % (original_route, parameter)
269 )
270 raw_converter = match["converter"]
271 if raw_converter is None:
272 # If a converter isn't specified, the default is `str`.
273 raw_converter = "str"
274 try:
275 converter = get_converter(raw_converter)
276 except KeyError as e:
277 raise ImproperlyConfigured(
278 "URL route %r uses invalid converter %r."
279 % (original_route, raw_converter)
280 ) from e
281 converters[parameter] = converter
282 parts.append("(?P<" + parameter + ">" + converter.regex + ")")
283 if is_endpoint:
284 parts.append(r"\Z")
285 return "".join(parts), converters
288class RoutePattern(CheckURLMixin):
289 regex = LocaleRegexDescriptor("_route")
291 def __init__(self, route, name=None, is_endpoint=False):
292 self._route = route
293 self._regex_dict = {}
294 self._is_endpoint = is_endpoint
295 self.name = name
296 self.converters = _route_to_regex(str(route), is_endpoint)[1]
298 def match(self, path):
299 match = self.regex.search(path)
300 if match:
301 # RoutePattern doesn't allow non-named groups so args are ignored.
302 kwargs = match.groupdict()
303 for key, value in kwargs.items(): 303 ↛ 304line 303 didn't jump to line 304, because the loop on line 303 never started
304 converter = self.converters[key]
305 try:
306 kwargs[key] = converter.to_python(value)
307 except ValueError:
308 return None
309 return path[match.end() :], (), kwargs
310 return None
312 def check(self):
313 warnings = self._check_pattern_startswith_slash()
314 route = self._route
315 if "(?P<" in route or route.startswith("^") or route.endswith("$"): 315 ↛ 316line 315 didn't jump to line 316, because the condition on line 315 was never true
316 warnings.append(
317 Warning(
318 "Your URL pattern {} has a route that contains '(?P<', begins "
319 "with a '^', or ends with a '$'. This was likely an oversight "
320 "when migrating to django.urls.path().".format(self.describe()),
321 id="2_0.W001",
322 )
323 )
324 return warnings
326 def _compile(self, route):
327 return re.compile(_route_to_regex(route, self._is_endpoint)[0])
329 def __str__(self):
330 return str(self._route)
333class LocalePrefixPattern:
334 def __init__(self, prefix_default_language=True):
335 self.prefix_default_language = prefix_default_language
336 self.converters = {}
338 @property
339 def regex(self):
340 # This is only used by reverse() and cached in _reverse_dict.
341 return re.compile(self.language_prefix)
343 @property
344 def language_prefix(self):
345 language_code = get_language() or settings.LANGUAGE_CODE
346 if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language:
347 return ""
348 else:
349 return "%s/" % language_code
351 def match(self, path):
352 language_prefix = self.language_prefix
353 if path.startswith(language_prefix):
354 return path[len(language_prefix) :], (), {}
355 return None
357 def check(self):
358 return []
360 def describe(self):
361 return "'{}'".format(self)
363 def __str__(self):
364 return self.language_prefix
367class URLPattern:
368 def __init__(self, pattern, callback, default_args=None, name=None):
369 self.pattern = pattern
370 self.callback = callback # the view
371 self.default_args = default_args or {}
372 self.name = name
374 def __repr__(self):
375 return "<%s %s>" % (self.__class__.__name__, self.pattern.describe())
377 def check(self):
378 warnings = self._check_pattern_name()
379 warnings.extend(self.pattern.check())
380 warnings.extend(self._check_callback())
381 return warnings
383 def _check_pattern_name(self):
384 """
385 Check that the pattern name does not contain a colon.
386 """
387 if self.pattern.name is not None and ":" in self.pattern.name: 387 ↛ 388line 387 didn't jump to line 388, because the condition on line 387 was never true
388 warning = Warning(
389 "Your URL pattern {} has a name including a ':'. Remove the colon, to "
390 "avoid ambiguous namespace references.".format(self.pattern.describe()),
391 id="urls.W003",
392 )
393 return [warning]
394 else:
395 return []
397 def _check_callback(self):
398 from django.views import View
400 view = self.callback
401 if inspect.isclass(view) and issubclass(view, View): 401 ↛ 402line 401 didn't jump to line 402, because the condition on line 401 was never true
402 return [
403 Error(
404 "Your URL pattern %s has an invalid view, pass %s.as_view() "
405 "instead of %s."
406 % (
407 self.pattern.describe(),
408 view.__name__,
409 view.__name__,
410 ),
411 id="urls.E009",
412 )
413 ]
414 return []
416 def resolve(self, path):
417 match = self.pattern.match(path)
418 if match:
419 new_path, args, kwargs = match
420 # Pass any extra_kwargs as **kwargs.
421 kwargs.update(self.default_args)
422 return ResolverMatch(
423 self.callback, args, kwargs, self.pattern.name, route=str(self.pattern)
424 )
426 @cached_property
427 def lookup_str(self):
428 """
429 A string that identifies the view (e.g. 'path.to.view_function' or
430 'path.to.ClassBasedView').
431 """
432 callback = self.callback
433 if isinstance(callback, functools.partial): 433 ↛ 434line 433 didn't jump to line 434, because the condition on line 433 was never true
434 callback = callback.func
435 if hasattr(callback, "view_class"):
436 callback = callback.view_class
437 elif not hasattr(callback, "__name__"): 437 ↛ 438line 437 didn't jump to line 438, because the condition on line 437 was never true
438 return callback.__module__ + "." + callback.__class__.__name__
439 return callback.__module__ + "." + callback.__qualname__
442class URLResolver:
443 def __init__(
444 self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None
445 ):
446 self.pattern = pattern
447 # urlconf_name is the dotted Python path to the module defining
448 # urlpatterns. It may also be an object with an urlpatterns attribute
449 # or urlpatterns itself.
450 self.urlconf_name = urlconf_name
451 self.callback = None
452 self.default_kwargs = default_kwargs or {}
453 self.namespace = namespace
454 self.app_name = app_name
455 self._reverse_dict = {}
456 self._namespace_dict = {}
457 self._app_dict = {}
458 # set of dotted paths to all functions and classes that are used in
459 # urlpatterns
460 self._callback_strs = set()
461 self._populated = False
462 self._local = Local()
464 def __repr__(self):
465 if isinstance(self.urlconf_name, list) and self.urlconf_name:
466 # Don't bother to output the whole list, it can be huge
467 urlconf_repr = "<%s list>" % self.urlconf_name[0].__class__.__name__
468 else:
469 urlconf_repr = repr(self.urlconf_name)
470 return "<%s %s (%s:%s) %s>" % (
471 self.__class__.__name__,
472 urlconf_repr,
473 self.app_name,
474 self.namespace,
475 self.pattern.describe(),
476 )
478 def check(self):
479 messages = []
480 for pattern in self.url_patterns:
481 messages.extend(check_resolver(pattern))
482 messages.extend(self._check_custom_error_handlers())
483 return messages or self.pattern.check()
485 def _check_custom_error_handlers(self):
486 messages = []
487 # All handlers take (request, exception) arguments except handler500
488 # which takes (request).
489 for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]:
490 try:
491 handler = self.resolve_error_handler(status_code)
492 except (ImportError, ViewDoesNotExist) as e:
493 path = getattr(self.urlconf_module, "handler%s" % status_code)
494 msg = (
495 "The custom handler{status_code} view '{path}' could not be "
496 "imported."
497 ).format(status_code=status_code, path=path)
498 messages.append(Error(msg, hint=str(e), id="urls.E008"))
499 continue
500 signature = inspect.signature(handler)
501 args = [None] * num_parameters
502 try:
503 signature.bind(*args)
504 except TypeError:
505 msg = (
506 "The custom handler{status_code} view '{path}' does not "
507 "take the correct number of arguments ({args})."
508 ).format(
509 status_code=status_code,
510 path=handler.__module__ + "." + handler.__qualname__,
511 args="request, exception" if num_parameters == 2 else "request",
512 )
513 messages.append(Error(msg, id="urls.E007"))
514 return messages
516 def _populate(self):
517 # Short-circuit if called recursively in this thread to prevent
518 # infinite recursion. Concurrent threads may call this at the same
519 # time and will need to continue, so set 'populating' on a
520 # thread-local variable.
521 if getattr(self._local, "populating", False): 521 ↛ 522line 521 didn't jump to line 522, because the condition on line 521 was never true
522 return
523 try:
524 self._local.populating = True
525 lookups = MultiValueDict()
526 namespaces = {}
527 apps = {}
528 language_code = get_language()
529 for url_pattern in reversed(self.url_patterns):
530 p_pattern = url_pattern.pattern.regex.pattern
531 if p_pattern.startswith("^"):
532 p_pattern = p_pattern[1:]
533 if isinstance(url_pattern, URLPattern):
534 self._callback_strs.add(url_pattern.lookup_str)
535 bits = normalize(url_pattern.pattern.regex.pattern)
536 lookups.appendlist(
537 url_pattern.callback,
538 (
539 bits,
540 p_pattern,
541 url_pattern.default_args,
542 url_pattern.pattern.converters,
543 ),
544 )
545 if url_pattern.name is not None:
546 lookups.appendlist(
547 url_pattern.name,
548 (
549 bits,
550 p_pattern,
551 url_pattern.default_args,
552 url_pattern.pattern.converters,
553 ),
554 )
555 else: # url_pattern is a URLResolver.
556 url_pattern._populate()
557 if url_pattern.app_name:
558 apps.setdefault(url_pattern.app_name, []).append(
559 url_pattern.namespace
560 )
561 namespaces[url_pattern.namespace] = (p_pattern, url_pattern)
562 else:
563 for name in url_pattern.reverse_dict:
564 for (
565 matches,
566 pat,
567 defaults,
568 converters,
569 ) in url_pattern.reverse_dict.getlist(name):
570 new_matches = normalize(p_pattern + pat)
571 lookups.appendlist(
572 name,
573 (
574 new_matches,
575 p_pattern + pat,
576 {**defaults, **url_pattern.default_kwargs},
577 {
578 **self.pattern.converters,
579 **url_pattern.pattern.converters,
580 **converters,
581 },
582 ),
583 )
584 for namespace, (
585 prefix,
586 sub_pattern,
587 ) in url_pattern.namespace_dict.items():
588 current_converters = url_pattern.pattern.converters
589 sub_pattern.pattern.converters.update(current_converters)
590 namespaces[namespace] = (p_pattern + prefix, sub_pattern)
591 for app_name, namespace_list in url_pattern.app_dict.items():
592 apps.setdefault(app_name, []).extend(namespace_list)
593 self._callback_strs.update(url_pattern._callback_strs)
594 self._namespace_dict[language_code] = namespaces
595 self._app_dict[language_code] = apps
596 self._reverse_dict[language_code] = lookups
597 self._populated = True
598 finally:
599 self._local.populating = False
601 @property
602 def reverse_dict(self):
603 language_code = get_language()
604 if language_code not in self._reverse_dict: 604 ↛ 605line 604 didn't jump to line 605, because the condition on line 604 was never true
605 self._populate()
606 return self._reverse_dict[language_code]
608 @property
609 def namespace_dict(self):
610 language_code = get_language()
611 if language_code not in self._namespace_dict: 611 ↛ 612line 611 didn't jump to line 612, because the condition on line 611 was never true
612 self._populate()
613 return self._namespace_dict[language_code]
615 @property
616 def app_dict(self):
617 language_code = get_language()
618 if language_code not in self._app_dict: 618 ↛ 619line 618 didn't jump to line 619, because the condition on line 618 was never true
619 self._populate()
620 return self._app_dict[language_code]
622 @staticmethod
623 def _extend_tried(tried, pattern, sub_tried=None):
624 if sub_tried is None:
625 tried.append([pattern])
626 else:
627 tried.extend([pattern, *t] for t in sub_tried)
629 @staticmethod
630 def _join_route(route1, route2):
631 """Join two routes, without the starting ^ in the second route."""
632 if not route1:
633 return route2
634 if route2.startswith("^"):
635 route2 = route2[1:]
636 return route1 + route2
638 def _is_callback(self, name):
639 if not self._populated:
640 self._populate()
641 return name in self._callback_strs
643 def resolve(self, path):
644 path = str(path) # path may be a reverse_lazy object
645 tried = []
646 match = self.pattern.match(path)
647 if match:
648 new_path, args, kwargs = match
649 for pattern in self.url_patterns: 649 ↛ 683line 649 didn't jump to line 683, because the loop on line 649 didn't complete
650 try:
651 sub_match = pattern.resolve(new_path)
652 except Resolver404 as e:
653 self._extend_tried(tried, pattern, e.args[0].get("tried"))
654 else:
655 if sub_match:
656 # Merge captured arguments in match with submatch
657 sub_match_dict = {**kwargs, **self.default_kwargs}
658 # Update the sub_match_dict with the kwargs from the sub_match.
659 sub_match_dict.update(sub_match.kwargs)
660 # If there are *any* named groups, ignore all non-named groups.
661 # Otherwise, pass all non-named arguments as positional
662 # arguments.
663 sub_match_args = sub_match.args
664 if not sub_match_dict:
665 sub_match_args = args + sub_match.args
666 current_route = (
667 ""
668 if isinstance(pattern, URLPattern)
669 else str(pattern.pattern)
670 )
671 self._extend_tried(tried, pattern, sub_match.tried)
672 return ResolverMatch(
673 sub_match.func,
674 sub_match_args,
675 sub_match_dict,
676 sub_match.url_name,
677 [self.app_name] + sub_match.app_names,
678 [self.namespace] + sub_match.namespaces,
679 self._join_route(current_route, sub_match.route),
680 tried,
681 )
682 tried.append([pattern])
683 raise Resolver404({"tried": tried, "path": new_path})
684 raise Resolver404({"path": path})
686 @cached_property
687 def urlconf_module(self):
688 if isinstance(self.urlconf_name, str):
689 return import_module(self.urlconf_name)
690 else:
691 return self.urlconf_name
693 @cached_property
694 def url_patterns(self):
695 # urlconf_module might be a valid set of patterns, so we default to it
696 patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
697 try:
698 iter(patterns)
699 except TypeError as e:
700 msg = (
701 "The included URLconf '{name}' does not appear to have "
702 "any patterns in it. If you see the 'urlpatterns' variable "
703 "with valid patterns in the file then the issue is probably "
704 "caused by a circular import."
705 )
706 raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e
707 return patterns
709 def resolve_error_handler(self, view_type):
710 callback = getattr(self.urlconf_module, "handler%s" % view_type, None)
711 if not callback: 711 ↛ 717line 711 didn't jump to line 717, because the condition on line 711 was never false
712 # No handler specified in file; use lazy import, since
713 # django.conf.urls imports this file.
714 from django.conf import urls
716 callback = getattr(urls, "handler%s" % view_type)
717 return get_callable(callback)
719 def reverse(self, lookup_view, *args, **kwargs):
720 return self._reverse_with_prefix(lookup_view, "", *args, **kwargs)
722 def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
723 if args and kwargs: 723 ↛ 724line 723 didn't jump to line 724, because the condition on line 723 was never true
724 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
726 if not self._populated:
727 self._populate()
729 possibilities = self.reverse_dict.getlist(lookup_view)
731 for possibility, pattern, defaults, converters in possibilities: 731 ↛ 776line 731 didn't jump to line 776, because the loop on line 731 didn't complete
732 for result, params in possibility:
733 if args:
734 if len(args) != len(params):
735 continue
736 candidate_subs = dict(zip(params, args))
737 else:
738 if set(kwargs).symmetric_difference(params).difference(defaults):
739 continue
740 if any(kwargs.get(k, v) != v for k, v in defaults.items()): 740 ↛ 741line 740 didn't jump to line 741, because the condition on line 740 was never true
741 continue
742 candidate_subs = kwargs
743 # Convert the candidate subs to text using Converter.to_url().
744 text_candidate_subs = {}
745 match = True
746 for k, v in candidate_subs.items():
747 if k in converters: 747 ↛ 748line 747 didn't jump to line 748, because the condition on line 747 was never true
748 try:
749 text_candidate_subs[k] = converters[k].to_url(v)
750 except ValueError:
751 match = False
752 break
753 else:
754 text_candidate_subs[k] = str(v)
755 if not match: 755 ↛ 756line 755 didn't jump to line 756, because the condition on line 755 was never true
756 continue
757 # WSGI provides decoded URLs, without %xx escapes, and the URL
758 # resolver operates on such URLs. First substitute arguments
759 # without quoting to build a decoded URL and look for a match.
760 # Then, if we have a match, redo the substitution with quoted
761 # arguments in order to return a properly encoded URL.
762 candidate_pat = _prefix.replace("%", "%%") + result
763 if re.search( 763 ↛ 732line 763 didn't jump to line 732, because the condition on line 763 was never false
764 "^%s%s" % (re.escape(_prefix), pattern),
765 candidate_pat % text_candidate_subs,
766 ):
767 # safe characters from `pchar` definition of RFC 3986
768 url = quote(
769 candidate_pat % text_candidate_subs,
770 safe=RFC3986_SUBDELIMS + "/~:@",
771 )
772 # Don't allow construction of scheme relative urls.
773 return escape_leading_slashes(url)
774 # lookup_view can be URL name or callable, but callables are not
775 # friendly in error messages.
776 m = getattr(lookup_view, "__module__", None)
777 n = getattr(lookup_view, "__name__", None)
778 if m is not None and n is not None:
779 lookup_view_s = "%s.%s" % (m, n)
780 else:
781 lookup_view_s = lookup_view
783 patterns = [pattern for (_, pattern, _, _) in possibilities]
784 if patterns:
785 if args:
786 arg_msg = "arguments '%s'" % (args,)
787 elif kwargs:
788 arg_msg = "keyword arguments '%s'" % kwargs
789 else:
790 arg_msg = "no arguments"
791 msg = "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % (
792 lookup_view_s,
793 arg_msg,
794 len(patterns),
795 patterns,
796 )
797 else:
798 msg = (
799 "Reverse for '%(view)s' not found. '%(view)s' is not "
800 "a valid view function or pattern name." % {"view": lookup_view_s}
801 )
802 raise NoReverseMatch(msg)