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

1""" 

2This module converts requested URLs to callback view functions. 

3 

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 

15 

16from asgiref.local import Local 

17 

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 

27 

28from .converters import get_converter 

29from .exceptions import NoReverseMatch, Resolver404 

30from .utils import get_callable 

31 

32 

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 

51 

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) 

58 

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__ 

67 

68 view_path = url_name or self._func_path 

69 self.view_name = ":".join(self.namespaces + [view_path]) 

70 

71 def __getitem__(self, index): 

72 return (self.func, self.args, self.kwargs)[index] 

73 

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 ) 

92 

93 def __reduce_ex__(self, protocol): 

94 raise PicklingError(f"Cannot pickle {self.__class__.__qualname__}.") 

95 

96 

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) 

101 

102 

103@functools.lru_cache(maxsize=None) 

104def _get_cached_resolver(urlconf=None): 

105 return URLResolver(RegexPattern(r"^/"), urlconf) 

106 

107 

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]) 

117 

118 

119class LocaleRegexDescriptor: 

120 def __init__(self, attr): 

121 self.attr = attr 

122 

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] 

140 

141 

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 

151 

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 [] 

175 

176 

177class RegexPattern(CheckURLMixin): 

178 regex = LocaleRegexDescriptor("_regex") 

179 

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 = {} 

186 

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 

202 

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 

209 

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 [] 

223 

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 

232 

233 def __str__(self): 

234 return str(self._regex) 

235 

236 

237_PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile( 

238 r"<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>" 

239) 

240 

241 

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 

286 

287 

288class RoutePattern(CheckURLMixin): 

289 regex = LocaleRegexDescriptor("_route") 

290 

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] 

297 

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 

311 

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 

325 

326 def _compile(self, route): 

327 return re.compile(_route_to_regex(route, self._is_endpoint)[0]) 

328 

329 def __str__(self): 

330 return str(self._route) 

331 

332 

333class LocalePrefixPattern: 

334 def __init__(self, prefix_default_language=True): 

335 self.prefix_default_language = prefix_default_language 

336 self.converters = {} 

337 

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) 

342 

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 

350 

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 

356 

357 def check(self): 

358 return [] 

359 

360 def describe(self): 

361 return "'{}'".format(self) 

362 

363 def __str__(self): 

364 return self.language_prefix 

365 

366 

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 

373 

374 def __repr__(self): 

375 return "<%s %s>" % (self.__class__.__name__, self.pattern.describe()) 

376 

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 

382 

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 [] 

396 

397 def _check_callback(self): 

398 from django.views import View 

399 

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 [] 

415 

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 ) 

425 

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__ 

440 

441 

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() 

463 

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 ) 

477 

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() 

484 

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 

515 

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 

600 

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] 

607 

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] 

614 

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] 

621 

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) 

628 

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 

637 

638 def _is_callback(self, name): 

639 if not self._populated: 

640 self._populate() 

641 return name in self._callback_strs 

642 

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}) 

685 

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 

692 

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 

708 

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 

715 

716 callback = getattr(urls, "handler%s" % view_type) 

717 return get_callable(callback) 

718 

719 def reverse(self, lookup_view, *args, **kwargs): 

720 return self._reverse_with_prefix(lookup_view, "", *args, **kwargs) 

721 

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()!") 

725 

726 if not self._populated: 

727 self._populate() 

728 

729 possibilities = self.reverse_dict.getlist(lookup_view) 

730 

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 

782 

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)