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

463 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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 captured_kwargs=None, 

45 extra_kwargs=None, 

46 ): 

47 self.func = func 

48 self.args = args 

49 self.kwargs = kwargs 

50 self.url_name = url_name 

51 self.route = route 

52 self.tried = tried 

53 self.captured_kwargs = captured_kwargs 

54 self.extra_kwargs = extra_kwargs 

55 

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

57 # in an empty value. 

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

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

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

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

62 

63 if hasattr(func, "view_class"): 

64 func = func.view_class 

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

66 # A class-based view 

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

68 else: 

69 # A function-based view 

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

71 

72 view_path = url_name or self._func_path 

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

74 

75 def __getitem__(self, index): 

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

77 

78 def __repr__(self): 

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

80 func = repr(self.func) 

81 else: 

82 func = self._func_path 

83 return ( 

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

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

86 % ( 

87 func, 

88 self.args, 

89 self.kwargs, 

90 self.url_name, 

91 self.app_names, 

92 self.namespaces, 

93 self.route, 

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

95 if self.captured_kwargs 

96 else "", 

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

98 ) 

99 ) 

100 

101 def __reduce_ex__(self, protocol): 

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

103 

104 

105def get_resolver(urlconf=None): 

106 if urlconf is None: 

107 urlconf = settings.ROOT_URLCONF 

108 return _get_cached_resolver(urlconf) 

109 

110 

111@functools.lru_cache(maxsize=None) 

112def _get_cached_resolver(urlconf=None): 

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

114 

115 

116@functools.lru_cache(maxsize=None) 

117def get_ns_resolver(ns_pattern, resolver, converters): 

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

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

120 # URLconf pattern. 

121 pattern = RegexPattern(ns_pattern) 

122 pattern.converters = dict(converters) 

123 ns_resolver = URLResolver(pattern, resolver.url_patterns) 

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

125 

126 

127class LocaleRegexDescriptor: 

128 def __init__(self, attr): 

129 self.attr = attr 

130 

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 = getattr(instance, self.attr) 

141 if isinstance(pattern, str): 

142 instance.__dict__["regex"] = instance._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] = instance._compile(str(pattern)) 

147 return instance._regex_dict[language_code] 

148 

149 

150class CheckURLMixin: 

151 def describe(self): 

152 """ 

153 Format the URL pattern for display in warning messages. 

154 """ 

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

156 if self.name: 

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

158 return description 

159 

160 def _check_pattern_startswith_slash(self): 

161 """ 

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

163 """ 

164 regex_pattern = self.regex.pattern 

165 if not settings.APPEND_SLASH: 

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

167 # when APPEND_SLASH=False. 

168 return [] 

169 if regex_pattern.startswith(("/", "^/", "^\\/")) and not regex_pattern.endswith( 

170 "/" 

171 ): 

172 warning = Warning( 

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

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

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

176 self.describe() 

177 ), 

178 id="urls.W002", 

179 ) 

180 return [warning] 

181 else: 

182 return [] 

183 

184 

185class RegexPattern(CheckURLMixin): 

186 regex = LocaleRegexDescriptor("_regex") 

187 

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

189 self._regex = regex 

190 self._regex_dict = {} 

191 self._is_endpoint = is_endpoint 

192 self.name = name 

193 self.converters = {} 

194 

195 def match(self, path): 

196 match = ( 

197 self.regex.fullmatch(path) 

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

199 else self.regex.search(path) 

200 ) 

201 if match: 

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

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

204 # positional arguments. 

205 kwargs = match.groupdict() 

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

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

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

209 return None 

210 

211 def check(self): 

212 warnings = [] 

213 warnings.extend(self._check_pattern_startswith_slash()) 

214 if not self._is_endpoint: 

215 warnings.extend(self._check_include_trailing_dollar()) 

216 return warnings 

217 

218 def _check_include_trailing_dollar(self): 

219 regex_pattern = self.regex.pattern 

220 if regex_pattern.endswith("$") and not regex_pattern.endswith(r"\$"): 

221 return [ 

222 Warning( 

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

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

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

226 id="urls.W001", 

227 ) 

228 ] 

229 else: 

230 return [] 

231 

232 def _compile(self, regex): 

233 """Compile and return the given regular expression.""" 

234 try: 

235 return re.compile(regex) 

236 except re.error as e: 

237 raise ImproperlyConfigured( 

238 '"%s" is not a valid regular expression: %s' % (regex, e) 

239 ) from e 

240 

241 def __str__(self): 

242 return str(self._regex) 

243 

244 

245_PATH_PARAMETER_COMPONENT_RE = _lazy_re_compile( 

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

247) 

248 

249 

250def _route_to_regex(route, is_endpoint=False): 

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 original_route = route 

258 parts = ["^"] 

259 converters = {} 

260 while True: 

261 match = _PATH_PARAMETER_COMPONENT_RE.search(route) 

262 if not match: 

263 parts.append(re.escape(route)) 

264 break 

265 elif not set(match.group()).isdisjoint(string.whitespace): 

266 raise ImproperlyConfigured( 

267 "URL route '%s' cannot contain whitespace in angle brackets " 

268 "<…>." % original_route 

269 ) 

270 parts.append(re.escape(route[: match.start()])) 

271 route = route[match.end() :] 

272 parameter = match["parameter"] 

273 if not parameter.isidentifier(): 

274 raise ImproperlyConfigured( 

275 "URL route '%s' uses parameter name %r which isn't a valid " 

276 "Python identifier." % (original_route, parameter) 

277 ) 

278 raw_converter = match["converter"] 

279 if raw_converter is None: 

280 # If a converter isn't specified, the default is `str`. 

281 raw_converter = "str" 

282 try: 

283 converter = get_converter(raw_converter) 

284 except KeyError as e: 

285 raise ImproperlyConfigured( 

286 "URL route %r uses invalid converter %r." 

287 % (original_route, raw_converter) 

288 ) from e 

289 converters[parameter] = converter 

290 parts.append("(?P<" + parameter + ">" + converter.regex + ")") 

291 if is_endpoint: 

292 parts.append(r"\Z") 

293 return "".join(parts), converters 

294 

295 

296class RoutePattern(CheckURLMixin): 

297 regex = LocaleRegexDescriptor("_route") 

298 

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

300 self._route = route 

301 self._regex_dict = {} 

302 self._is_endpoint = is_endpoint 

303 self.name = name 

304 self.converters = _route_to_regex(str(route), is_endpoint)[1] 

305 

306 def match(self, path): 

307 match = self.regex.search(path) 

308 if match: 

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

310 kwargs = match.groupdict() 

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

312 converter = self.converters[key] 

313 try: 

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

315 except ValueError: 

316 return None 

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

318 return None 

319 

320 def check(self): 

321 warnings = self._check_pattern_startswith_slash() 

322 route = self._route 

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

324 warnings.append( 

325 Warning( 

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

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

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

329 id="2_0.W001", 

330 ) 

331 ) 

332 return warnings 

333 

334 def _compile(self, route): 

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

336 

337 def __str__(self): 

338 return str(self._route) 

339 

340 

341class LocalePrefixPattern: 

342 def __init__(self, prefix_default_language=True): 

343 self.prefix_default_language = prefix_default_language 

344 self.converters = {} 

345 

346 @property 

347 def regex(self): 

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

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

350 

351 @property 

352 def language_prefix(self): 

353 language_code = get_language() or settings.LANGUAGE_CODE 

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

355 return "" 

356 else: 

357 return "%s/" % language_code 

358 

359 def match(self, path): 

360 language_prefix = self.language_prefix 

361 if path.startswith(language_prefix): 

362 return path[len(language_prefix) :], (), {} 

363 return None 

364 

365 def check(self): 

366 return [] 

367 

368 def describe(self): 

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

370 

371 def __str__(self): 

372 return self.language_prefix 

373 

374 

375class URLPattern: 

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

377 self.pattern = pattern 

378 self.callback = callback # the view 

379 self.default_args = default_args or {} 

380 self.name = name 

381 

382 def __repr__(self): 

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

384 

385 def check(self): 

386 warnings = self._check_pattern_name() 

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

388 warnings.extend(self._check_callback()) 

389 return warnings 

390 

391 def _check_pattern_name(self): 

392 """ 

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

394 """ 

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

396 warning = Warning( 

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

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

399 id="urls.W003", 

400 ) 

401 return [warning] 

402 else: 

403 return [] 

404 

405 def _check_callback(self): 

406 from django.views import View 

407 

408 view = self.callback 

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

410 return [ 

411 Error( 

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

413 "instead of %s." 

414 % ( 

415 self.pattern.describe(), 

416 view.__name__, 

417 view.__name__, 

418 ), 

419 id="urls.E009", 

420 ) 

421 ] 

422 return [] 

423 

424 def resolve(self, path): 

425 match = self.pattern.match(path) 

426 if match: 

427 new_path, args, captured_kwargs = match 

428 # Pass any default args as **kwargs. 

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

430 return ResolverMatch( 

431 self.callback, 

432 args, 

433 kwargs, 

434 self.pattern.name, 

435 route=str(self.pattern), 

436 captured_kwargs=captured_kwargs, 

437 extra_kwargs=self.default_args, 

438 ) 

439 

440 @cached_property 

441 def lookup_str(self): 

442 """ 

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

444 'path.to.ClassBasedView'). 

445 """ 

446 callback = self.callback 

447 if isinstance(callback, functools.partial): 

448 callback = callback.func 

449 if hasattr(callback, "view_class"): 

450 callback = callback.view_class 

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

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

453 return callback.__module__ + "." + callback.__qualname__ 

454 

455 

456class URLResolver: 

457 def __init__( 

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

459 ): 

460 self.pattern = pattern 

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

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

463 # or urlpatterns itself. 

464 self.urlconf_name = urlconf_name 

465 self.callback = None 

466 self.default_kwargs = default_kwargs or {} 

467 self.namespace = namespace 

468 self.app_name = app_name 

469 self._reverse_dict = {} 

470 self._namespace_dict = {} 

471 self._app_dict = {} 

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

473 # urlpatterns 

474 self._callback_strs = set() 

475 self._populated = False 

476 self._local = Local() 

477 

478 def __repr__(self): 

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

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

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

482 else: 

483 urlconf_repr = repr(self.urlconf_name) 

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

485 self.__class__.__name__, 

486 urlconf_repr, 

487 self.app_name, 

488 self.namespace, 

489 self.pattern.describe(), 

490 ) 

491 

492 def check(self): 

493 messages = [] 

494 for pattern in self.url_patterns: 

495 messages.extend(check_resolver(pattern)) 

496 messages.extend(self._check_custom_error_handlers()) 

497 return messages or self.pattern.check() 

498 

499 def _check_custom_error_handlers(self): 

500 messages = [] 

501 # All handlers take (request, exception) arguments except handler500 

502 # which takes (request). 

503 for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]: 

504 try: 

505 handler = self.resolve_error_handler(status_code) 

506 except (ImportError, ViewDoesNotExist) as e: 

507 path = getattr(self.urlconf_module, "handler%s" % status_code) 

508 msg = ( 

509 "The custom handler{status_code} view '{path}' could not be " 

510 "imported." 

511 ).format(status_code=status_code, path=path) 

512 messages.append(Error(msg, hint=str(e), id="urls.E008")) 

513 continue 

514 signature = inspect.signature(handler) 

515 args = [None] * num_parameters 

516 try: 

517 signature.bind(*args) 

518 except TypeError: 

519 msg = ( 

520 "The custom handler{status_code} view '{path}' does not " 

521 "take the correct number of arguments ({args})." 

522 ).format( 

523 status_code=status_code, 

524 path=handler.__module__ + "." + handler.__qualname__, 

525 args="request, exception" if num_parameters == 2 else "request", 

526 ) 

527 messages.append(Error(msg, id="urls.E007")) 

528 return messages 

529 

530 def _populate(self): 

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

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

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

534 # thread-local variable. 

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

536 return 

537 try: 

538 self._local.populating = True 

539 lookups = MultiValueDict() 

540 namespaces = {} 

541 apps = {} 

542 language_code = get_language() 

543 for url_pattern in reversed(self.url_patterns): 

544 p_pattern = url_pattern.pattern.regex.pattern 

545 if p_pattern.startswith("^"): 

546 p_pattern = p_pattern[1:] 

547 if isinstance(url_pattern, URLPattern): 

548 self._callback_strs.add(url_pattern.lookup_str) 

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

550 lookups.appendlist( 

551 url_pattern.callback, 

552 ( 

553 bits, 

554 p_pattern, 

555 url_pattern.default_args, 

556 url_pattern.pattern.converters, 

557 ), 

558 ) 

559 if url_pattern.name is not None: 

560 lookups.appendlist( 

561 url_pattern.name, 

562 ( 

563 bits, 

564 p_pattern, 

565 url_pattern.default_args, 

566 url_pattern.pattern.converters, 

567 ), 

568 ) 

569 else: # url_pattern is a URLResolver. 

570 url_pattern._populate() 

571 if url_pattern.app_name: 

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

573 url_pattern.namespace 

574 ) 

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

576 else: 

577 for name in url_pattern.reverse_dict: 

578 for ( 

579 matches, 

580 pat, 

581 defaults, 

582 converters, 

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

584 new_matches = normalize(p_pattern + pat) 

585 lookups.appendlist( 

586 name, 

587 ( 

588 new_matches, 

589 p_pattern + pat, 

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

591 { 

592 **self.pattern.converters, 

593 **url_pattern.pattern.converters, 

594 **converters, 

595 }, 

596 ), 

597 ) 

598 for namespace, ( 

599 prefix, 

600 sub_pattern, 

601 ) in url_pattern.namespace_dict.items(): 

602 current_converters = url_pattern.pattern.converters 

603 sub_pattern.pattern.converters.update(current_converters) 

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

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

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

607 self._callback_strs.update(url_pattern._callback_strs) 

608 self._namespace_dict[language_code] = namespaces 

609 self._app_dict[language_code] = apps 

610 self._reverse_dict[language_code] = lookups 

611 self._populated = True 

612 finally: 

613 self._local.populating = False 

614 

615 @property 

616 def reverse_dict(self): 

617 language_code = get_language() 

618 if language_code not in self._reverse_dict: 

619 self._populate() 

620 return self._reverse_dict[language_code] 

621 

622 @property 

623 def namespace_dict(self): 

624 language_code = get_language() 

625 if language_code not in self._namespace_dict: 

626 self._populate() 

627 return self._namespace_dict[language_code] 

628 

629 @property 

630 def app_dict(self): 

631 language_code = get_language() 

632 if language_code not in self._app_dict: 

633 self._populate() 

634 return self._app_dict[language_code] 

635 

636 @staticmethod 

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

638 if sub_tried is None: 

639 tried.append([pattern]) 

640 else: 

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

642 

643 @staticmethod 

644 def _join_route(route1, route2): 

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

646 if not route1: 

647 return route2 

648 if route2.startswith("^"): 

649 route2 = route2[1:] 

650 return route1 + route2 

651 

652 def _is_callback(self, name): 

653 if not self._populated: 

654 self._populate() 

655 return name in self._callback_strs 

656 

657 def resolve(self, path): 

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

659 tried = [] 

660 match = self.pattern.match(path) 

661 if match: 

662 new_path, args, kwargs = match 

663 for pattern in self.url_patterns: 

664 try: 

665 sub_match = pattern.resolve(new_path) 

666 except Resolver404 as e: 

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

668 else: 

669 if sub_match: 

670 # Merge captured arguments in match with submatch 

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

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

673 sub_match_dict.update(sub_match.kwargs) 

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

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

676 # arguments. 

677 sub_match_args = sub_match.args 

678 if not sub_match_dict: 

679 sub_match_args = args + sub_match.args 

680 current_route = ( 

681 "" 

682 if isinstance(pattern, URLPattern) 

683 else str(pattern.pattern) 

684 ) 

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

686 return ResolverMatch( 

687 sub_match.func, 

688 sub_match_args, 

689 sub_match_dict, 

690 sub_match.url_name, 

691 [self.app_name] + sub_match.app_names, 

692 [self.namespace] + sub_match.namespaces, 

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

694 tried, 

695 captured_kwargs=sub_match.captured_kwargs, 

696 extra_kwargs={ 

697 **self.default_kwargs, 

698 **sub_match.extra_kwargs, 

699 }, 

700 ) 

701 tried.append([pattern]) 

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

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

704 

705 @cached_property 

706 def urlconf_module(self): 

707 if isinstance(self.urlconf_name, str): 

708 return import_module(self.urlconf_name) 

709 else: 

710 return self.urlconf_name 

711 

712 @cached_property 

713 def url_patterns(self): 

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

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

716 try: 

717 iter(patterns) 

718 except TypeError as e: 

719 msg = ( 

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

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

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

723 "caused by a circular import." 

724 ) 

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

726 return patterns 

727 

728 def resolve_error_handler(self, view_type): 

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

730 if not callback: 

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

732 # django.conf.urls imports this file. 

733 from django.conf import urls 

734 

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

736 return get_callable(callback) 

737 

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

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

740 

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

742 if args and kwargs: 

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

744 

745 if not self._populated: 

746 self._populate() 

747 

748 possibilities = self.reverse_dict.getlist(lookup_view) 

749 

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

751 for result, params in possibility: 

752 if args: 

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

754 continue 

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

756 else: 

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

758 continue 

759 matches = True 

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

761 if k in params: 

762 continue 

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

764 matches = False 

765 break 

766 if not matches: 

767 continue 

768 candidate_subs = kwargs 

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

770 text_candidate_subs = {} 

771 match = True 

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

773 if k in converters: 

774 try: 

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

776 except ValueError: 

777 match = False 

778 break 

779 else: 

780 text_candidate_subs[k] = str(v) 

781 if not match: 

782 continue 

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

784 # resolver operates on such URLs. First substitute arguments 

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

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

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

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

789 if re.search( 

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

791 candidate_pat % text_candidate_subs, 

792 ): 

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

794 url = quote( 

795 candidate_pat % text_candidate_subs, 

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

797 ) 

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

799 return escape_leading_slashes(url) 

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

801 # friendly in error messages. 

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

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

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

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

806 else: 

807 lookup_view_s = lookup_view 

808 

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

810 if patterns: 

811 if args: 

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

813 elif kwargs: 

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

815 else: 

816 arg_msg = "no arguments" 

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

818 lookup_view_s, 

819 arg_msg, 

820 len(patterns), 

821 patterns, 

822 ) 

823 else: 

824 msg = ( 

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

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

827 ) 

828 raise NoReverseMatch(msg)