188 lines
5.6 KiB
Python
188 lines
5.6 KiB
Python
|
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
|