123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- from urllib.parse import unquote, urlsplit, urlunsplit
-
- from asgiref.local import Local
-
- from django.utils.functional import lazy
- from django.utils.translation import override
-
- from .exceptions import NoReverseMatch, Resolver404
- from .resolvers import _get_cached_resolver, get_ns_resolver, get_resolver
- from .utils import get_callable
-
- # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
- # the current thread (which is the only one we ever access), it is assumed to
- # be empty.
- _prefixes = Local()
-
- # Overridden URLconfs for each thread are stored here.
- _urlconfs = Local()
-
-
- def resolve(path, urlconf=None):
- if urlconf is None:
- urlconf = get_urlconf()
- return get_resolver(urlconf).resolve(path)
-
-
- def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
- if urlconf is None:
- urlconf = get_urlconf()
- resolver = get_resolver(urlconf)
- args = args or []
- kwargs = kwargs or {}
-
- prefix = get_script_prefix()
-
- if not isinstance(viewname, str):
- view = viewname
- else:
- *path, view = viewname.split(":")
-
- if current_app:
- current_path = current_app.split(":")
- current_path.reverse()
- else:
- current_path = None
-
- resolved_path = []
- ns_pattern = ""
- ns_converters = {}
- for ns in path:
- current_ns = current_path.pop() if current_path else None
- # Lookup the name to see if it could be an app identifier.
- try:
- app_list = resolver.app_dict[ns]
- # Yes! Path part matches an app in the current Resolver.
- if current_ns and current_ns in app_list:
- # If we are reversing for a particular app, use that
- # namespace.
- ns = current_ns
- elif ns not in app_list:
- # The name isn't shared by one of the instances (i.e.,
- # the default) so pick the first instance as the default.
- ns = app_list[0]
- except KeyError:
- pass
-
- if ns != current_ns:
- current_path = None
-
- try:
- extra, resolver = resolver.namespace_dict[ns]
- resolved_path.append(ns)
- ns_pattern = ns_pattern + extra
- ns_converters.update(resolver.pattern.converters)
- except KeyError as key:
- if resolved_path:
- raise NoReverseMatch(
- "%s is not a registered namespace inside '%s'"
- % (key, ":".join(resolved_path))
- )
- else:
- raise NoReverseMatch("%s is not a registered namespace" % key)
- if ns_pattern:
- resolver = get_ns_resolver(
- ns_pattern, resolver, tuple(ns_converters.items())
- )
-
- return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
-
-
- reverse_lazy = lazy(reverse, str)
-
-
- def clear_url_caches():
- get_callable.cache_clear()
- _get_cached_resolver.cache_clear()
- get_ns_resolver.cache_clear()
-
-
- def set_script_prefix(prefix):
- """
- Set the script prefix for the current thread.
- """
- if not prefix.endswith("/"):
- prefix += "/"
- _prefixes.value = prefix
-
-
- def get_script_prefix():
- """
- Return the currently active script prefix. Useful for client code that
- wishes to construct their own URLs manually (although accessing the request
- instance is normally going to be a lot cleaner).
- """
- return getattr(_prefixes, "value", "/")
-
-
- def clear_script_prefix():
- """
- Unset the script prefix for the current thread.
- """
- try:
- del _prefixes.value
- except AttributeError:
- pass
-
-
- def set_urlconf(urlconf_name):
- """
- Set the URLconf for the current thread (overriding the default one in
- settings). If urlconf_name is None, revert back to the default.
- """
- if urlconf_name:
- _urlconfs.value = urlconf_name
- else:
- if hasattr(_urlconfs, "value"):
- del _urlconfs.value
-
-
- def get_urlconf(default=None):
- """
- Return the root URLconf to use for the current thread if it has been
- changed from the default one.
- """
- return getattr(_urlconfs, "value", default)
-
-
- def is_valid_path(path, urlconf=None):
- """
- Return the ResolverMatch if the given path resolves against the default URL
- resolver, False otherwise. This is a convenience method to make working
- with "is this a match?" cases easier, avoiding try...except blocks.
- """
- try:
- return resolve(path, urlconf)
- except Resolver404:
- return False
-
-
- def translate_url(url, lang_code):
- """
- Given a URL (absolute or relative), try to get its translated version in
- the `lang_code` language (either by i18n_patterns or by translated regex).
- Return the original URL if no translated version is found.
- """
- parsed = urlsplit(url)
- try:
- # URL may be encoded.
- match = resolve(unquote(parsed.path))
- except Resolver404:
- pass
- else:
- to_be_reversed = (
- "%s:%s" % (match.namespace, match.url_name)
- if match.namespace
- else match.url_name
- )
- with override(lang_code):
- try:
- url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
- except NoReverseMatch:
- pass
- else:
- url = urlunsplit(
- (parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment)
- )
- return url
|