@andrewgodwin @MarkusH
Actual approach is like this:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 'django.middleware.locale.LocaleMiddleware',
'ov2.localizedsites.conf.middleware.LocalizedSitesMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
A ov2.localizedsites.conf.utils.py which offers this functions:
is_site_prefix_patterns_used, get_site_from_request,
activate_site, get_language_from_site, get_site,
get_site_from_path,
The LocalizedSites Middleware
class LocalizedSitesMiddleware(MiddlewareMixin):
"""
Parse a request, set the site stuff and decide what translation object to install in the
current thread context. This allows pages to be dynamically translated to
the language the user desires (if the language is available, of course).
"""
response_redirect_class = HttpResponseRedirect
def process_request(self, request):
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
site_prefix_patterns_used = is_site_prefix_patterns_used(urlconf)
site = get_site_from_request(request, check_path=site_prefix_patterns_used)
activate_site(site)
language = get_language_from_site(site)
translation.activate(language)
request.LANGUAGE_CODE = translation.get_language()
request.SITE_PREFIX = site
def process_response(self, request, response):
language = translation.get_language()
site = get_site()
site_from_path = get_site_from_path(request.path_info)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
site_prefix_patterns_used = is_site_prefix_patterns_used(urlconf)
if (
response.status_code == 404
and not site_from_path
and site_prefix_patterns_used
):
site_path = '/%s%s' % (site, request.path_info)
path_valid = is_valid_path(site_path, urlconf)
path_needs_slash = not path_valid and (
settings.APPEND_SLASH
and not site_path.endswith('/')
and is_valid_path('%s/' % site_path, urlconf)
)
if path_valid or path_needs_slash:
script_prefix = get_script_prefix()
# Insert site after the script prefix and before the
# rest of the URL
site_url = request.get_full_path(
force_append_slash=path_needs_slash
).replace(script_prefix, '%s%s/' % (script_prefix, site), 1)
return self.response_redirect_class(site_url)
if not (site_prefix_patterns_used and site_from_path):
patch_vary_headers(response, ('Accept-Language',))
response.setdefault('Content-Language', language)
response.set_cookie(
settings.SITE_COOKIE_NAME,
site,
max_age=settings.SITE_COOKIE_AGE,
path=settings.SITE_COOKIE_PATH,
domain=settings.SITE_COOKIE_DOMAIN,
samesite='Strict',
)
return response
There is also our custom urlpatterns generator:
def localizedsites_patterns(*urls):
"""
Add the localizedsite prefix to every URL pattern within this function.
This may only be used in the root URLconf, not in an included URLconf.
"""
if not settings.USE_LOCALIZED_SITES:
return list(urls)
return [SiteURLResolver(LocalizedSitePrefixPattern(), list(urls))]
which is used in all the url entries that we want to work this way.
And finally, in our resolvers.py:
class LocalizedSitePrefixPattern:
def __init__(self):
self.converters = {}
@property
def regex(self):
# This is only used by reverse() and cached in _reverse_dict.
return re.compile(self.site_prefix)
@property
def site_prefix(self):
site_prefix = get_site()
return '%s/' % site_prefix
def match(self, path):
site_prefix = self.site_prefix
if path.startswith(site_prefix):
return path[len(site_prefix) :], (), {}
return None
def check(self):
return []
def describe(self):
return "'{}'".format(self)
def __str__(self):
return self.site_prefix
class SiteURLResolver(URLResolver):
def _populate(self):
# Short-circuit if called recursively in this thread to prevent
# infinite recursion. Concurrent threads may call this at the same
# time and will need to continue, so set 'populating' on a
# thread-local variable.
if getattr(self._local, 'populating', False):
return
try:
self._local.populating = True
lookups = MultiValueDict()
namespaces = {}
apps = {}
site_prefix = get_site()
for url_pattern in reversed(self.url_patterns):
p_pattern = url_pattern.pattern.regex.pattern
if p_pattern.startswith('^'):
p_pattern = p_pattern[1:]
if isinstance(url_pattern, URLPattern):
self._callback_strs.add(url_pattern.lookup_str)
bits = normalize(url_pattern.pattern.regex.pattern)
lookups.appendlist(
url_pattern.callback,
(bits, p_pattern, url_pattern.default_args, url_pattern.pattern.converters)
)
if url_pattern.name is not None:
lookups.appendlist(
url_pattern.name,
(bits, p_pattern, url_pattern.default_args, url_pattern.pattern.converters)
)
else: # url_pattern is a URLResolver.
url_pattern._populate()
if url_pattern.app_name:
apps.setdefault(url_pattern.app_name, []).append(url_pattern.namespace)
namespaces[url_pattern.namespace] = (p_pattern, url_pattern)
else:
for name in url_pattern.reverse_dict:
for matches, pat, defaults, converters in url_pattern.reverse_dict.getlist(name):
new_matches = normalize(p_pattern + pat)
lookups.appendlist(
name,
(
new_matches,
p_pattern + pat,
{**defaults, **url_pattern.default_kwargs},
{**self.pattern.converters, **url_pattern.pattern.converters, **converters}
)
)
for namespace, (prefix, sub_pattern) in url_pattern.namespace_dict.items():
current_converters = url_pattern.pattern.converters
sub_pattern.pattern.converters.update(current_converters)
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
for app_name, namespace_list in url_pattern.app_dict.items():
apps.setdefault(app_name, []).extend(namespace_list)
self._callback_strs.update(url_pattern._callback_strs)
self._namespace_dict[site_prefix] = namespaces
self._app_dict[site_prefix] = apps
self._reverse_dict[site_prefix] = lookups
self._populated = True
finally:
self._local.populating = False
print('#'*20,'_populate', site_prefix)
@property
def reverse_dict(self):
site_prefix = get_site()
print('reverse_dict', site_prefix)
if site_prefix not in self._reverse_dict:
self._populate()
print(self._reverse_dict)
return self._reverse_dict[site_prefix]
@property
def namespace_dict(self):
site_prefix = get_site()
print('namespace_dict', site_prefix)
if site_prefix not in self._namespace_dict:
self._populate()
print(self._namespace_dict)
return self._namespace_dict[site_prefix]
@property
def app_dict(self):
site_prefix = get_site()
print('app_dict', site_prefix)
if site_prefix not in self._app_dict:
self._populate()
print(self._app_dict)
return self._app_dict[site_prefix]