base.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from urllib.parse import urlsplit, urlunsplit
  2. from asgiref.local import Local
  3. from django.utils.encoding import iri_to_uri
  4. from django.utils.functional import lazy
  5. from django.utils.translation import override
  6. from .exceptions import NoReverseMatch, Resolver404
  7. from .resolvers import get_ns_resolver, get_resolver
  8. from .utils import get_callable
  9. # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
  10. # the current thread (which is the only one we ever access), it is assumed to
  11. # be empty.
  12. _prefixes = Local()
  13. # Overridden URLconfs for each thread are stored here.
  14. _urlconfs = Local()
  15. def resolve(path, urlconf=None):
  16. if urlconf is None:
  17. urlconf = get_urlconf()
  18. return get_resolver(urlconf).resolve(path)
  19. def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
  20. if urlconf is None:
  21. urlconf = get_urlconf()
  22. resolver = get_resolver(urlconf)
  23. args = args or []
  24. kwargs = kwargs or {}
  25. prefix = get_script_prefix()
  26. if not isinstance(viewname, str):
  27. view = viewname
  28. else:
  29. *path, view = viewname.split(':')
  30. if current_app:
  31. current_path = current_app.split(':')
  32. current_path.reverse()
  33. else:
  34. current_path = None
  35. resolved_path = []
  36. ns_pattern = ''
  37. ns_converters = {}
  38. for ns in path:
  39. current_ns = current_path.pop() if current_path else None
  40. # Lookup the name to see if it could be an app identifier.
  41. try:
  42. app_list = resolver.app_dict[ns]
  43. # Yes! Path part matches an app in the current Resolver.
  44. if current_ns and current_ns in app_list:
  45. # If we are reversing for a particular app, use that
  46. # namespace.
  47. ns = current_ns
  48. elif ns not in app_list:
  49. # The name isn't shared by one of the instances (i.e.,
  50. # the default) so pick the first instance as the default.
  51. ns = app_list[0]
  52. except KeyError:
  53. pass
  54. if ns != current_ns:
  55. current_path = None
  56. try:
  57. extra, resolver = resolver.namespace_dict[ns]
  58. resolved_path.append(ns)
  59. ns_pattern = ns_pattern + extra
  60. ns_converters.update(resolver.pattern.converters)
  61. except KeyError as key:
  62. if resolved_path:
  63. raise NoReverseMatch(
  64. "%s is not a registered namespace inside '%s'" %
  65. (key, ':'.join(resolved_path))
  66. )
  67. else:
  68. raise NoReverseMatch("%s is not a registered namespace" % key)
  69. if ns_pattern:
  70. resolver = get_ns_resolver(ns_pattern, resolver, tuple(ns_converters.items()))
  71. return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
  72. reverse_lazy = lazy(reverse, str)
  73. def clear_url_caches():
  74. get_callable.cache_clear()
  75. get_resolver.cache_clear()
  76. get_ns_resolver.cache_clear()
  77. def set_script_prefix(prefix):
  78. """
  79. Set the script prefix for the current thread.
  80. """
  81. if not prefix.endswith('/'):
  82. prefix += '/'
  83. _prefixes.value = prefix
  84. def get_script_prefix():
  85. """
  86. Return the currently active script prefix. Useful for client code that
  87. wishes to construct their own URLs manually (although accessing the request
  88. instance is normally going to be a lot cleaner).
  89. """
  90. return getattr(_prefixes, "value", '/')
  91. def clear_script_prefix():
  92. """
  93. Unset the script prefix for the current thread.
  94. """
  95. try:
  96. del _prefixes.value
  97. except AttributeError:
  98. pass
  99. def set_urlconf(urlconf_name):
  100. """
  101. Set the URLconf for the current thread (overriding the default one in
  102. settings). If urlconf_name is None, revert back to the default.
  103. """
  104. if urlconf_name:
  105. _urlconfs.value = urlconf_name
  106. else:
  107. if hasattr(_urlconfs, "value"):
  108. del _urlconfs.value
  109. def get_urlconf(default=None):
  110. """
  111. Return the root URLconf to use for the current thread if it has been
  112. changed from the default one.
  113. """
  114. return getattr(_urlconfs, "value", default)
  115. def is_valid_path(path, urlconf=None):
  116. """
  117. Return True if the given path resolves against the default URL resolver,
  118. False otherwise. This is a convenience method to make working with "is
  119. this a match?" cases easier, avoiding try...except blocks.
  120. """
  121. try:
  122. resolve(path, urlconf)
  123. return True
  124. except Resolver404:
  125. return False
  126. def translate_url(url, lang_code):
  127. """
  128. Given a URL (absolute or relative), try to get its translated version in
  129. the `lang_code` language (either by i18n_patterns or by translated regex).
  130. Return the original URL if no translated version is found.
  131. """
  132. parsed = urlsplit(url)
  133. try:
  134. match = resolve(parsed.path)
  135. except Resolver404:
  136. pass
  137. else:
  138. to_be_reversed = "%s:%s" % (match.namespace, match.url_name) if match.namespace else match.url_name
  139. with override(lang_code):
  140. try:
  141. url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
  142. except NoReverseMatch:
  143. pass
  144. else:
  145. url = urlunsplit((parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment))
  146. return url