You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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