Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/urls/resolvers.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

462 statements  

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""" 

8 

9import functools 

10import inspect 

11import re 

12import string 

13from importlib import import_module 

14from pickle import PicklingError 

15from urllib.parse import quote 

16 

17from asgiref.local import Local 

18 

19from django.conf import settings 

20from django.core.checks import Error, Warning 

21from django.core.checks.urls import check_resolver 

22from django.core.exceptions import ImproperlyConfigured 

23from django.utils.datastructures import MultiValueDict 

24from django.utils.functional import cached_property 

25from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes 

26from django.utils.regex_helper import _lazy_re_compile, normalize 

27from django.utils.translation import get_language 

28 

29from .converters import get_converters 

30from .exceptions import NoReverseMatch, Resolver404 

31from .utils import get_callable 

32 

33 

34class ResolverMatch: 

35 def __init__( 

36 self, 

37 func, 

38 args, 

39 kwargs, 

40 url_name=None, 

41 app_names=None, 

42 namespaces=None, 

43 route=None, 

44 tried=None, 

45 captured_kwargs=None, 

46 extra_kwargs=None, 

47 ): 

48 self.func = func 

49 self.args = args 

50 self.kwargs = kwargs 

51 self.url_name = url_name 

52 self.route = route 

53 self.tried = tried 

54 self.captured_kwargs = captured_kwargs 

55 self.extra_kwargs = extra_kwargs 

56 

57 # If a URLRegexResolver doesn't have a namespace or app_name, it passes 

58 # in an empty value. 

59 self.app_names = [x for x in app_names if x] if app_names else [] 

60 self.app_name = ":".join(self.app_names) 

61 self.namespaces = [x for x in namespaces if x] if namespaces else [] 

62 self.namespace = ":".join(self.namespaces) 

63 

64 if hasattr(func, "view_class"): 

65 func = func.view_class 

66 if not hasattr(func, "__name__"): 

67 # A class-based view 

68 self._func_path = func.__class__.__module__ + "." + func.__class__.__name__ 

69 else: 

70 # A function-based view 

71 self._func_path = func.__module__ + "." + func.__name__ 

72 

73 view_path = url_name or self._func_path 

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

75 

76 def __getitem__(self, index): 

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

78 

79 def __repr__(self): 

80 if isinstance(self.func, functools.partial): 

81 func = repr(self.func) 

82 else: 

83 func = self._func_path 

84 return ( 

85 "ResolverMatch(func=%s, args=%r, kwargs=%r, url_name=%r, " 

86 "app_names=%r, namespaces=%r, route=%r%s%s)" 

87 % ( 

88 func, 

89 self.args, 

90 self.kwargs, 

91 self.url_name, 

92 self.app_names, 

93 self.namespaces, 

94 self.route, 

95 ( 

96 f", captured_kwargs={self.captured_kwargs!r}" 

97 if self.captured_kwargs 

98 else "" 

99 ), 

100 f", extra_kwargs={self.extra_kwargs!r}" if self.extra_kwargs else "", 

101 ) 

102 ) 

103 

104 def __reduce_ex__(self, protocol): 

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

106 

107 

108def get_resolver(urlconf=None): 

109 if urlconf is None: 

110 urlconf = settings.ROOT_URLCONF 

111 return _get_cached_resolver(urlconf) 

112 

113 

114@functools.cache 

115def _get_cached_resolver(urlconf=None): 

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

117 

118 

119@functools.cache 

120def get_ns_resolver(ns_pattern, resolver, converters): 

121 # Build a namespaced resolver for the given parent URLconf pattern. 

122 # This makes it possible to have captured parameters in the parent 

123 # URLconf pattern. 

124 pattern = RegexPattern(ns_pattern) 

125 pattern.converters = dict(converters) 

126 ns_resolver = URLResolver(pattern, resolver.url_patterns) 

127 return URLResolver(RegexPattern(r"^/"), [ns_resolver]) 

128 

129 

130class LocaleRegexDescriptor: 

131 def __get__(self, instance, cls=None): 

132 """ 

133 Return a compiled regular expression based on the active language. 

134 """ 

135 if instance is None: 

136 return self 

137 # As a performance optimization, if the given regex string is a regular 

138 # string (not a lazily-translated string proxy), compile it once and 

139 # avoid per-language compilation. 

140 pattern = instance._regex 

141 if isinstance(pattern, str): 

142 instance.__dict__["regex"] = self._compile(pattern) 

143 return instance.__dict__["regex"] 

144 language_code = get_language() 

145 if language_code not in instance._regex_dict: 

146 instance._regex_dict[language_code] = self._compile(str(pattern)) 

147 return instance._regex_dict[language_code] 

148 

149 def _compile(self, regex): 

150 try: 

151 return re.compile(regex) 

152 except re.error as e: 

153 raise ImproperlyConfigured( 

154 f'"{regex}" is not a valid regular expression: {e}' 

155 ) from e 

156 

157 

158class CheckURLMixin: 

159 def describe(self): 

160 """ 

161 Format the URL pattern for display in warning messages. 

162 """ 

163 description = "'{}'".format(self) 

164 if self.name: 

165 description += " [name='{}']".format(self.name) 

166 return description 

167 

168 def _check_pattern_startswith_slash(self): 

169 """ 

170 Check that the pattern does not begin with a forward slash. 

171 """ 

172 if not settings.APPEND_SLASH: 

173 # Skip check as it can be useful to start a URL pattern with a slash 

174 # when APPEND_SLASH=False. 

175 return [] 

176 if self._regex.startswith(("/", "^/", "^\\/")) and not self._regex.endswith( 

177 "/" 

178 ): 

179 warning = Warning( 

180 "Your URL pattern {} has a route beginning with a '/'. Remove this " 

181 "slash as it is unnecessary. If this pattern is targeted in an " 

182 "include(), ensure the include() pattern has a trailing '/'.".format( 

183 self.describe() 

184 ), 

185 id="urls.W002", 

186 ) 

187 return [warning] 

188 else: 

189 return [] 

190 

191 

192class RegexPattern(CheckURLMixin): 

193 regex = LocaleRegexDescriptor() 

194 

195 def __init__(self, regex, name=None, is_endpoint=False): 

196 self._regex = regex 

197 self._regex_dict = {} 

198 self._is_endpoint = is_endpoint 

199 self.name = name 

200 self.converters = {} 

201 

202 def match(self, path): 

203 match = ( 

204 self.regex.fullmatch(path) 

205 if self._is_endpoint and self.regex.pattern.endswith("$") 

206 else self.regex.search(path) 

207 ) 

208 if match: 

209 # If there are any named groups, use those as kwargs, ignoring 

210 # non-named groups. Otherwise, pass all non-named arguments as 

211 # positional arguments. 

212 kwargs = match.groupdict() 

213 args = () if kwargs else match.groups() 

214 kwargs = {k: v for k, v in kwargs.items() if v is not None} 

215 return path[match.end() :], args, kwargs 

216 return None 

217 

218 def check(self): 

219 warnings = [] 

220 warnings.extend(self._check_pattern_startswith_slash()) 

221 if not self._is_endpoint: 

222 warnings.extend(self._check_include_trailing_dollar()) 

223 return warnings 

224 

225 def _check_include_trailing_dollar(self): 

226 if self._regex.endswith("$") and not self._regex.endswith(r"\$"): 

227 return [ 

228 Warning( 

229 "Your URL pattern {} uses include with a route ending with a '$'. " 

230 "Remove the dollar from the route to avoid problems including " 

231 "URLs.".format(self.describe()), 

232 id="urls.W001", 

233 ) 

234 ] 

235 else: 

236 return [] 

237 

238 def __str__(self): 

239 return str(self._regex) 

240 

241 

242_PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile( 

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

244) 

245 

246whitespace_set = frozenset(string.whitespace) 

247 

248 

249@functools.lru_cache 

250def _route_to_regex(route, is_endpoint): 

251 """ 

252 Convert a path pattern into a regular expression. Return the regular 

253 expression and a dictionary mapping the capture names to the converters. 

254 For example, 'foo/<int:pk>' returns '^foo\\/(?P<pk>[0-9]+)' 

255 and {'pk': <django.urls.converters.IntConverter>}. 

256 """ 

257 parts = ["^"] 

258 all_converters = get_converters() 

259 converters = {} 

260 previous_end = 0 

261 for match_ in _PATH_PARAMETER_COMPONENT_RE.finditer(route): 

262 if not whitespace_set.isdisjoint(match_[0]): 

263 raise ImproperlyConfigured( 

264 f"URL route {route!r} cannot contain whitespace in angle brackets <…>." 

265 ) 

266 # Default to make converter "str" if unspecified (parameter always 

267 # matches something). 

268 raw_converter, parameter = match_.groups(default="str") 

269 if not parameter.isidentifier(): 

270 raise ImproperlyConfigured( 

271 f"URL route {route!r} uses parameter name {parameter!r} which " 

272 "isn't a valid Python identifier." 

273 ) 

274 try: 

275 converter = all_converters[raw_converter] 

276 except KeyError as e: 

277 raise ImproperlyConfigured( 

278 f"URL route {route!r} uses invalid converter {raw_converter!r}." 

279 ) from e 

280 converters[parameter] = converter 

281 

282 start, end = match_.span() 

283 parts.append(re.escape(route[previous_end:start])) 

284 previous_end = end 

285 parts.append(f"(?P<{parameter}>{converter.regex})") 

286 

287 parts.append(re.escape(route[previous_end:])) 

288 if is_endpoint: 

289 parts.append(r"\Z") 

290 return "".join(parts), converters 

291 

292 

293class LocaleRegexRouteDescriptor: 

294 def __get__(self, instance, cls=None): 

295 """ 

296 Return a compiled regular expression based on the active language. 

297 """ 

298 if instance is None: 

299 return self 

300 # As a performance optimization, if the given route is a regular string 

301 # (not a lazily-translated string proxy), compile it once and avoid 

302 # per-language compilation. 

303 if isinstance(instance._route, str): 

304 instance.__dict__["regex"] = re.compile(instance._regex) 

305 return instance.__dict__["regex"] 

306 language_code = get_language() 

307 if language_code not in instance._regex_dict: 

308 instance._regex_dict[language_code] = re.compile( 

309 _route_to_regex(str(instance._route), instance._is_endpoint)[0] 

310 ) 

311 return instance._regex_dict[language_code] 

312 

313 

314class RoutePattern(CheckURLMixin): 

315 regex = LocaleRegexRouteDescriptor() 

316 

317 def __init__(self, route, name=None, is_endpoint=False): 

318 self._route = route 

319 self._regex, self.converters = _route_to_regex(str(route), is_endpoint) 

320 self._regex_dict = {} 

321 self._is_endpoint = is_endpoint 

322 self.name = name 

323 

324 def match(self, path): 

325 match = self.regex.search(path) 

326 if match: 

327 # RoutePattern doesn't allow non-named groups so args are ignored. 

328 kwargs = match.groupdict() 

329 for key, value in kwargs.items(): 

330 converter = self.converters[key] 

331 try: 

332 kwargs[key] = converter.to_python(value) 

333 except ValueError: 

334 return None 

335 return path[match.end() :], (), kwargs 

336 return None 

337 

338 def check(self): 

339 warnings = [ 

340 *self._check_pattern_startswith_slash(), 

341 *self._check_pattern_unmatched_angle_brackets(), 

342 ] 

343 route = self._route 

344 if "(?P<" in route or route.startswith("^") or route.endswith("$"): 

345 warnings.append( 

346 Warning( 

347 "Your URL pattern {} has a route that contains '(?P<', begins " 

348 "with a '^', or ends with a '$'. This was likely an oversight " 

349 "when migrating to django.urls.path().".format(self.describe()), 

350 id="2_0.W001", 

351 ) 

352 ) 

353 return warnings 

354 

355 def _check_pattern_unmatched_angle_brackets(self): 

356 warnings = [] 

357 msg = "Your URL pattern %s has an unmatched '%s' bracket." 

358 brackets = re.findall(r"[<>]", str(self._route)) 

359 open_bracket_counter = 0 

360 for bracket in brackets: 

361 if bracket == "<": 

362 open_bracket_counter += 1 

363 elif bracket == ">": 

364 open_bracket_counter -= 1 

365 if open_bracket_counter < 0: 

366 warnings.append( 

367 Warning(msg % (self.describe(), ">"), id="urls.W010") 

368 ) 

369 open_bracket_counter = 0 

370 if open_bracket_counter > 0: 

371 warnings.append(Warning(msg % (self.describe(), "<"), id="urls.W010")) 

372 return warnings 

373 

374 def __str__(self): 

375 return str(self._route) 

376 

377 

378class LocalePrefixPattern: 

379 def __init__(self, prefix_default_language=True): 

380 self.prefix_default_language = prefix_default_language 

381 self.converters = {} 

382 

383 @property 

384 def regex(self): 

385 # This is only used by reverse() and cached in _reverse_dict. 

386 return re.compile(re.escape(self.language_prefix)) 

387 

388 @property 

389 def language_prefix(self): 

390 language_code = get_language() or settings.LANGUAGE_CODE 

391 if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language: 

392 return "" 

393 else: 

394 return "%s/" % language_code 

395 

396 def match(self, path): 

397 language_prefix = self.language_prefix 

398 if path.startswith(language_prefix): 

399 return path.removeprefix(language_prefix), (), {} 

400 return None 

401 

402 def check(self): 

403 return [] 

404 

405 def describe(self): 

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

407 

408 def __str__(self): 

409 return self.language_prefix 

410 

411 

412class URLPattern: 

413 def __init__(self, pattern, callback, default_args=None, name=None): 

414 self.pattern = pattern 

415 self.callback = callback # the view 

416 self.default_args = default_args or {} 

417 self.name = name 

418 

419 def __repr__(self): 

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

421 

422 def check(self): 

423 warnings = self._check_pattern_name() 

424 warnings.extend(self.pattern.check()) 

425 warnings.extend(self._check_callback()) 

426 return warnings 

427 

428 def _check_pattern_name(self): 

429 """ 

430 Check that the pattern name does not contain a colon. 

431 """ 

432 if self.pattern.name is not None and ":" in self.pattern.name: 

433 warning = Warning( 

434 "Your URL pattern {} has a name including a ':'. Remove the colon, to " 

435 "avoid ambiguous namespace references.".format(self.pattern.describe()), 

436 id="urls.W003", 

437 ) 

438 return [warning] 

439 else: 

440 return [] 

441 

442 def _check_callback(self): 

443 from django.views import View 

444 

445 view = self.callback 

446 if inspect.isclass(view) and issubclass(view, View): 

447 return [ 

448 Error( 

449 "Your URL pattern %s has an invalid view, pass %s.as_view() " 

450 "instead of %s." 

451 % ( 

452 self.pattern.describe(), 

453 view.__name__, 

454 view.__name__, 

455 ), 

456 id="urls.E009", 

457 ) 

458 ] 

459 return [] 

460 

461 def resolve(self, path): 

462 match = self.pattern.match(path) 

463 if match: 

464 new_path, args, captured_kwargs = match 

465 # Pass any default args as **kwargs. 

466 kwargs = {**captured_kwargs, **self.default_args} 

467 return ResolverMatch( 

468 self.callback, 

469 args, 

470 kwargs, 

471 self.pattern.name, 

472 route=str(self.pattern), 

473 captured_kwargs=captured_kwargs, 

474 extra_kwargs=self.default_args, 

475 ) 

476 

477 @cached_property 

478 def lookup_str(self): 

479 """ 

480 A string that identifies the view (e.g. 'path.to.view_function' or 

481 'path.to.ClassBasedView'). 

482 """ 

483 callback = self.callback 

484 if isinstance(callback, functools.partial): 

485 callback = callback.func 

486 if hasattr(callback, "view_class"): 

487 callback = callback.view_class 

488 elif not hasattr(callback, "__name__"): 

489 return callback.__module__ + "." + callback.__class__.__name__ 

490 return callback.__module__ + "." + callback.__qualname__ 

491 

492 

493class URLResolver: 

494 def __init__( 

495 self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None 

496 ): 

497 self.pattern = pattern 

498 # urlconf_name is the dotted Python path to the module defining 

499 # urlpatterns. It may also be an object with an urlpatterns attribute 

500 # or urlpatterns itself. 

501 self.urlconf_name = urlconf_name 

502 self.callback = None 

503 self.default_kwargs = default_kwargs or {} 

504 self.namespace = namespace 

505 self.app_name = app_name 

506 self._reverse_dict = {} 

507 self._namespace_dict = {} 

508 self._app_dict = {} 

509 # set of dotted paths to all functions and classes that are used in 

510 # urlpatterns 

511 self._callback_strs = set() 

512 self._populated = False 

513 self._local = Local() 

514 

515 def __repr__(self): 

516 if isinstance(self.urlconf_name, list) and self.urlconf_name: 

517 # Don't bother to output the whole list, it can be huge 

518 urlconf_repr = "<%s list>" % self.urlconf_name[0].__class__.__name__ 

519 else: 

520 urlconf_repr = repr(self.urlconf_name) 

521 return "<%s %s (%s:%s) %s>" % ( 

522 self.__class__.__name__, 

523 urlconf_repr, 

524 self.app_name, 

525 self.namespace, 

526 self.pattern.describe(), 

527 ) 

528 

529 def check(self): 

530 messages = [] 

531 for pattern in self.url_patterns: 

532 messages.extend(check_resolver(pattern)) 

533 return messages or self.pattern.check() 

534 

535 def _populate(self): 

536 # Short-circuit if called recursively in this thread to prevent 

537 # infinite recursion. Concurrent threads may call this at the same 

538 # time and will need to continue, so set 'populating' on a 

539 # thread-local variable. 

540 if getattr(self._local, "populating", False): 

541 return 

542 try: 

543 self._local.populating = True 

544 lookups = MultiValueDict() 

545 namespaces = {} 

546 apps = {} 

547 language_code = get_language() 

548 for url_pattern in reversed(self.url_patterns): 

549 p_pattern = url_pattern.pattern.regex.pattern 

550 p_pattern = p_pattern.removeprefix("^") 

551 if isinstance(url_pattern, URLPattern): 

552 self._callback_strs.add(url_pattern.lookup_str) 

553 bits = normalize(url_pattern.pattern.regex.pattern) 

554 lookups.appendlist( 

555 url_pattern.callback, 

556 ( 

557 bits, 

558 p_pattern, 

559 url_pattern.default_args, 

560 url_pattern.pattern.converters, 

561 ), 

562 ) 

563 if url_pattern.name is not None: 

564 lookups.appendlist( 

565 url_pattern.name, 

566 ( 

567 bits, 

568 p_pattern, 

569 url_pattern.default_args, 

570 url_pattern.pattern.converters, 

571 ), 

572 ) 

573 else: # url_pattern is a URLResolver. 

574 url_pattern._populate() 

575 if url_pattern.app_name: 

576 apps.setdefault(url_pattern.app_name, []).append( 

577 url_pattern.namespace 

578 ) 

579 namespaces[url_pattern.namespace] = (p_pattern, url_pattern) 

580 else: 

581 for name in url_pattern.reverse_dict: 

582 for ( 

583 matches, 

584 pat, 

585 defaults, 

586 converters, 

587 ) in url_pattern.reverse_dict.getlist(name): 

588 new_matches = normalize(p_pattern + pat) 

589 lookups.appendlist( 

590 name, 

591 ( 

592 new_matches, 

593 p_pattern + pat, 

594 {**defaults, **url_pattern.default_kwargs}, 

595 { 

596 **self.pattern.converters, 

597 **url_pattern.pattern.converters, 

598 **converters, 

599 }, 

600 ), 

601 ) 

602 for namespace, ( 

603 prefix, 

604 sub_pattern, 

605 ) in url_pattern.namespace_dict.items(): 

606 current_converters = url_pattern.pattern.converters 

607 sub_pattern.pattern.converters.update(current_converters) 

608 namespaces[namespace] = (p_pattern + prefix, sub_pattern) 

609 for app_name, namespace_list in url_pattern.app_dict.items(): 

610 apps.setdefault(app_name, []).extend(namespace_list) 

611 self._callback_strs.update(url_pattern._callback_strs) 

612 self._namespace_dict[language_code] = namespaces 

613 self._app_dict[language_code] = apps 

614 self._reverse_dict[language_code] = lookups 

615 self._populated = True 

616 finally: 

617 self._local.populating = False 

618 

619 @property 

620 def reverse_dict(self): 

621 language_code = get_language() 

622 if language_code not in self._reverse_dict: 

623 self._populate() 

624 return self._reverse_dict[language_code] 

625 

626 @property 

627 def namespace_dict(self): 

628 language_code = get_language() 

629 if language_code not in self._namespace_dict: 

630 self._populate() 

631 return self._namespace_dict[language_code] 

632 

633 @property 

634 def app_dict(self): 

635 language_code = get_language() 

636 if language_code not in self._app_dict: 

637 self._populate() 

638 return self._app_dict[language_code] 

639 

640 @staticmethod 

641 def _extend_tried(tried, pattern, sub_tried=None): 

642 if sub_tried is None: 

643 tried.append([pattern]) 

644 else: 

645 tried.extend([pattern, *t] for t in sub_tried) 

646 

647 @staticmethod 

648 def _join_route(route1, route2): 

649 """Join two routes, without the starting ^ in the second route.""" 

650 if not route1: 

651 return route2 

652 route2 = route2.removeprefix("^") 

653 return route1 + route2 

654 

655 def _is_callback(self, name): 

656 if not self._populated: 

657 self._populate() 

658 return name in self._callback_strs 

659 

660 def resolve(self, path): 

661 path = str(path) # path may be a reverse_lazy object 

662 tried = [] 

663 match = self.pattern.match(path) 

664 if match: 

665 new_path, args, kwargs = match 

666 for pattern in self.url_patterns: 

667 try: 

668 sub_match = pattern.resolve(new_path) 

669 except Resolver404 as e: 

670 self._extend_tried(tried, pattern, e.args[0].get("tried")) 

671 else: 

672 if sub_match: 

673 # Merge captured arguments in match with submatch 

674 sub_match_dict = {**kwargs, **self.default_kwargs} 

675 # Update the sub_match_dict with the kwargs from the sub_match. 

676 sub_match_dict.update(sub_match.kwargs) 

677 # If there are *any* named groups, ignore all non-named groups. 

678 # Otherwise, pass all non-named arguments as positional 

679 # arguments. 

680 sub_match_args = sub_match.args 

681 if not sub_match_dict: 

682 sub_match_args = args + sub_match.args 

683 current_route = ( 

684 "" 

685 if isinstance(pattern, URLPattern) 

686 else str(pattern.pattern) 

687 ) 

688 self._extend_tried(tried, pattern, sub_match.tried) 

689 return ResolverMatch( 

690 sub_match.func, 

691 sub_match_args, 

692 sub_match_dict, 

693 sub_match.url_name, 

694 [self.app_name] + sub_match.app_names, 

695 [self.namespace] + sub_match.namespaces, 

696 self._join_route(current_route, sub_match.route), 

697 tried, 

698 captured_kwargs=sub_match.captured_kwargs, 

699 extra_kwargs={ 

700 **self.default_kwargs, 

701 **sub_match.extra_kwargs, 

702 }, 

703 ) 

704 tried.append([pattern]) 

705 raise Resolver404({"tried": tried, "path": new_path}) 

706 raise Resolver404({"path": path}) 

707 

708 @cached_property 

709 def urlconf_module(self): 

710 if isinstance(self.urlconf_name, str): 

711 return import_module(self.urlconf_name) 

712 else: 

713 return self.urlconf_name 

714 

715 @cached_property 

716 def url_patterns(self): 

717 # urlconf_module might be a valid set of patterns, so we default to it 

718 patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module) 

719 try: 

720 iter(patterns) 

721 except TypeError as e: 

722 msg = ( 

723 "The included URLconf '{name}' does not appear to have " 

724 "any patterns in it. If you see the 'urlpatterns' variable " 

725 "with valid patterns in the file then the issue is probably " 

726 "caused by a circular import." 

727 ) 

728 raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e 

729 return patterns 

730 

731 def resolve_error_handler(self, view_type): 

732 callback = getattr(self.urlconf_module, "handler%s" % view_type, None) 

733 if not callback: 

734 # No handler specified in file; use lazy import, since 

735 # django.conf.urls imports this file. 

736 from django.conf import urls 

737 

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

739 return get_callable(callback) 

740 

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

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

743 

744 def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): 

745 if args and kwargs: 

746 raise ValueError("Don't mix *args and **kwargs in call to reverse()!") 

747 

748 if not self._populated: 

749 self._populate() 

750 

751 possibilities = self.reverse_dict.getlist(lookup_view) 

752 

753 for possibility, pattern, defaults, converters in possibilities: 

754 for result, params in possibility: 

755 if args: 

756 if len(args) != len(params): 

757 continue 

758 candidate_subs = dict(zip(params, args)) 

759 else: 

760 if set(kwargs).symmetric_difference(params).difference(defaults): 

761 continue 

762 matches = True 

763 for k, v in defaults.items(): 

764 if k in params: 

765 continue 

766 if kwargs.get(k, v) != v: 

767 matches = False 

768 break 

769 if not matches: 

770 continue 

771 candidate_subs = kwargs 

772 # Convert the candidate subs to text using Converter.to_url(). 

773 text_candidate_subs = {} 

774 match = True 

775 for k, v in candidate_subs.items(): 

776 if k in converters: 

777 try: 

778 text_candidate_subs[k] = converters[k].to_url(v) 

779 except ValueError: 

780 match = False 

781 break 

782 else: 

783 text_candidate_subs[k] = str(v) 

784 if not match: 

785 continue 

786 # WSGI provides decoded URLs, without %xx escapes, and the URL 

787 # resolver operates on such URLs. First substitute arguments 

788 # without quoting to build a decoded URL and look for a match. 

789 # Then, if we have a match, redo the substitution with quoted 

790 # arguments in order to return a properly encoded URL. 

791 candidate_pat = _prefix.replace("%", "%%") + result 

792 if re.search( 

793 "^%s%s" % (re.escape(_prefix), pattern), 

794 candidate_pat % text_candidate_subs, 

795 ): 

796 # safe characters from `pchar` definition of RFC 3986 

797 url = quote( 

798 candidate_pat % text_candidate_subs, 

799 safe=RFC3986_SUBDELIMS + "/~:@", 

800 ) 

801 # Don't allow construction of scheme relative urls. 

802 return escape_leading_slashes(url) 

803 # lookup_view can be URL name or callable, but callables are not 

804 # friendly in error messages. 

805 m = getattr(lookup_view, "__module__", None) 

806 n = getattr(lookup_view, "__name__", None) 

807 if m is not None and n is not None: 

808 lookup_view_s = "%s.%s" % (m, n) 

809 else: 

810 lookup_view_s = lookup_view 

811 

812 patterns = [pattern for (_, pattern, _, _) in possibilities] 

813 if patterns: 

814 if args: 

815 arg_msg = "arguments '%s'" % (args,) 

816 elif kwargs: 

817 arg_msg = "keyword arguments '%s'" % kwargs 

818 else: 

819 arg_msg = "no arguments" 

820 msg = "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % ( 

821 lookup_view_s, 

822 arg_msg, 

823 len(patterns), 

824 patterns, 

825 ) 

826 else: 

827 msg = ( 

828 "Reverse for '%(view)s' not found. '%(view)s' is not " 

829 "a valid view function or pattern name." % {"view": lookup_view_s} 

830 ) 

831 raise NoReverseMatch(msg)