kopia lustrzana https://github.com/wagtail/wagtail
Add WAGTAIL_CONTENT_LANGUAGES setting
rodzic
e78db08d24
commit
a88129236c
|
@ -1,13 +1,6 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.locale import LANG_INFO
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from django.core.signals import setting_changed
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils.translation import check_for_language
|
|
||||||
|
|
||||||
|
|
||||||
# A setting that can be used in foreign key declarations
|
# A setting that can be used in foreign key declarations
|
||||||
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
||||||
|
@ -18,56 +11,3 @@ try:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form"
|
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form"
|
||||||
" 'app_label.model_name'")
|
" 'app_label.model_name'")
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
|
||||||
def get_languages():
|
|
||||||
"""
|
|
||||||
Cache of settings.LANGUAGES in a dictionary for easy lookups by key.
|
|
||||||
"""
|
|
||||||
# TODO: Add support for WAGTAIL_LANGUAGES
|
|
||||||
return dict(settings.LANGUAGES)
|
|
||||||
|
|
||||||
|
|
||||||
# Added in Django 2.1
|
|
||||||
@functools.lru_cache(maxsize=1000)
|
|
||||||
def get_supported_language_variant(lang_code, strict=False):
|
|
||||||
"""
|
|
||||||
Return the language code that's listed in supported languages, possibly
|
|
||||||
selecting a more generic variant. Raise LookupError if nothing is found.
|
|
||||||
If `strict` is False (the default), look for a country-specific variant
|
|
||||||
when neither the language code nor its generic variant is found.
|
|
||||||
lru_cache should have a maxsize to prevent from memory exhaustion attacks,
|
|
||||||
as the provided language codes are taken from the HTTP request. See also
|
|
||||||
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
|
|
||||||
"""
|
|
||||||
if lang_code:
|
|
||||||
# If 'fr-ca' is not supported, try special fallback or language-only 'fr'.
|
|
||||||
possible_lang_codes = [lang_code]
|
|
||||||
try:
|
|
||||||
possible_lang_codes.extend(LANG_INFO[lang_code]["fallback"])
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
generic_lang_code = lang_code.split("-")[0]
|
|
||||||
possible_lang_codes.append(generic_lang_code)
|
|
||||||
supported_lang_codes = get_languages()
|
|
||||||
|
|
||||||
for code in possible_lang_codes:
|
|
||||||
if code in supported_lang_codes and check_for_language(code):
|
|
||||||
return code
|
|
||||||
if not strict:
|
|
||||||
# if fr-fr is not supported, try fr-ca.
|
|
||||||
for supported_code in supported_lang_codes:
|
|
||||||
if supported_code.startswith(generic_lang_code + "-"):
|
|
||||||
return supported_code
|
|
||||||
raise LookupError(lang_code)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(setting_changed)
|
|
||||||
def reset_cache(**kwargs):
|
|
||||||
"""
|
|
||||||
Clear cache when global LANGUAGES/LANGUAGE_CODE settings are changed
|
|
||||||
"""
|
|
||||||
if kwargs["setting"] in ("LANGUAGES", "LANGUAGE_CODE"):
|
|
||||||
get_languages.cache_clear()
|
|
||||||
get_supported_language_variant.cache_clear()
|
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
from wagtail.core.compat import get_supported_language_variant
|
from wagtail.core.utils import get_supported_content_language_variant
|
||||||
|
|
||||||
|
|
||||||
def initial_locale(apps, schema_editor):
|
def initial_locale(apps, schema_editor):
|
||||||
Locale = apps.get_model("wagtailcore.Locale")
|
Locale = apps.get_model("wagtailcore.Locale")
|
||||||
|
|
||||||
Locale.objects.create(
|
Locale.objects.create(
|
||||||
language_code=get_supported_language_variant(settings.LANGUAGE_CODE),
|
language_code=get_supported_content_language_variant(settings.LANGUAGE_CODE),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,8 @@ from wagtail.core.url_routing import RouteResult
|
||||||
from wagtail.core.utils import WAGTAIL_APPEND_SLASH, camelcase_to_underscore, resolve_model_string
|
from wagtail.core.utils import WAGTAIL_APPEND_SLASH, camelcase_to_underscore, resolve_model_string
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
|
||||||
from .compat import get_languages, get_supported_language_variant
|
from .utils import (
|
||||||
from .utils import find_available_slug
|
find_available_slug, get_content_languages, get_supported_content_language_variant)
|
||||||
|
|
||||||
logger = logging.getLogger('wagtail.core')
|
logger = logging.getLogger('wagtail.core')
|
||||||
|
|
||||||
|
@ -296,13 +296,13 @@ def pk(obj):
|
||||||
class LocaleManager(models.Manager):
|
class LocaleManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Exclude any locales that have an invalid language code
|
# Exclude any locales that have an invalid language code
|
||||||
return super().get_queryset().filter(language_code__in=get_languages().keys())
|
return super().get_queryset().filter(language_code__in=get_content_languages().keys())
|
||||||
|
|
||||||
def get_for_language(self, language_code):
|
def get_for_language(self, language_code):
|
||||||
"""
|
"""
|
||||||
Gets a Locale from a language code.
|
Gets a Locale from a language code.
|
||||||
"""
|
"""
|
||||||
return self.get(language_code=get_supported_language_variant(language_code))
|
return self.get(language_code=get_supported_content_language_variant(language_code))
|
||||||
|
|
||||||
|
|
||||||
class Locale(models.Model):
|
class Locale(models.Model):
|
||||||
|
@ -336,7 +336,7 @@ class Locale(models.Model):
|
||||||
return cls.get_default()
|
return cls.get_default()
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
return get_languages().get(self.language_code)
|
return get_content_languages().get(self.language_code)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_display_name() or self.language_code
|
return self.get_display_name() or self.language_code
|
||||||
|
@ -448,7 +448,7 @@ def bootstrap_translatable_model(model, locale):
|
||||||
class BootstrapTranslatableModel(migrations.RunPython):
|
class BootstrapTranslatableModel(migrations.RunPython):
|
||||||
def __init__(self, model_string, language_code=None):
|
def __init__(self, model_string, language_code=None):
|
||||||
if language_code is None:
|
if language_code is None:
|
||||||
language_code = get_supported_language_variant(settings.LANGUAGE_CODE)
|
language_code = get_supported_content_language_variant(settings.LANGUAGE_CODE)
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
def forwards(apps, schema_editor):
|
||||||
model = apps.get_model(model_string)
|
model = apps.get_model(model_string)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
# -*- coding: utf-8 -*
|
# -*- coding: utf-8 -*
|
||||||
from django.test import TestCase
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from wagtail.core.models import Page
|
from wagtail.core.models import Page
|
||||||
from wagtail.core.utils import (
|
from wagtail.core.utils import (
|
||||||
accepts_kwarg, camelcase_to_underscore, cautious_slugify, find_available_slug, safe_snake_case,
|
accepts_kwarg, camelcase_to_underscore, cautious_slugify, find_available_slug,
|
||||||
string_to_ascii)
|
get_content_languages, get_supported_content_language_variant, safe_snake_case, string_to_ascii)
|
||||||
|
|
||||||
|
|
||||||
class TestCamelCaseToUnderscore(TestCase):
|
class TestCamelCaseToUnderscore(TestCase):
|
||||||
|
@ -138,3 +139,115 @@ class TestFindAvailableSlug(TestCase):
|
||||||
slug = find_available_slug(self.root_page, "home")
|
slug = find_available_slug(self.root_page, "home")
|
||||||
|
|
||||||
self.assertEqual(slug, "home-2")
|
self.assertEqual(slug, "home-2")
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
USE_I18N=True,
|
||||||
|
WAGTAIL_I18N_ENABLED=True,
|
||||||
|
LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
('de-at', 'Austrian German'),
|
||||||
|
('pt-br', 'Portuguese (Brazil)'),
|
||||||
|
],
|
||||||
|
WAGTAIL_CONTENT_LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
('de-at', 'Austrian German'),
|
||||||
|
('pt-br', 'Portuguese (Brazil)'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class TestGetContentLanguages(TestCase):
|
||||||
|
def test_get_content_languages(self):
|
||||||
|
self.assertEqual(get_content_languages(), {
|
||||||
|
'de': 'German',
|
||||||
|
'de-at': 'Austrian German',
|
||||||
|
'en': 'English',
|
||||||
|
'pt-br': 'Portuguese (Brazil)'
|
||||||
|
})
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
WAGTAIL_CONTENT_LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_can_be_different_to_django_languages(self):
|
||||||
|
self.assertEqual(get_content_languages(), {
|
||||||
|
'de': 'German',
|
||||||
|
'en': 'English',
|
||||||
|
})
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
WAGTAIL_CONTENT_LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
('zh', 'Chinese'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_must_be_subset_of_django_languages(self):
|
||||||
|
with self.assertRaises(ImproperlyConfigured) as e:
|
||||||
|
get_content_languages()
|
||||||
|
|
||||||
|
self.assertEqual(e.exception.args, ("The language zh is specified in WAGTAIL_CONTENT_LANGUAGES but not LANGUAGES. WAGTAIL_CONTENT_LANGUAGES must be a subset of LANGUAGES.", ))
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
USE_I18N=True,
|
||||||
|
WAGTAIL_I18N_ENABLED=True,
|
||||||
|
LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
('de-at', 'Austrian German'),
|
||||||
|
('pt-br', 'Portuguese (Brazil)'),
|
||||||
|
],
|
||||||
|
WAGTAIL_CONTENT_LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
('de-at', 'Austrian German'),
|
||||||
|
('pt-br', 'Portuguese (Brazil)'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class TestGetSupportedContentLanguageVariant(TestCase):
|
||||||
|
# From: https://github.com/django/django/blob/9e57b1efb5205bd94462e9de35254ec5ea6eb04e/tests/i18n/tests.py#L1481
|
||||||
|
def test_get_supported_content_language_variant(self):
|
||||||
|
g = get_supported_content_language_variant
|
||||||
|
self.assertEqual(g('en'), 'en')
|
||||||
|
self.assertEqual(g('en-gb'), 'en')
|
||||||
|
self.assertEqual(g('de'), 'de')
|
||||||
|
self.assertEqual(g('de-at'), 'de-at')
|
||||||
|
self.assertEqual(g('de-ch'), 'de')
|
||||||
|
self.assertEqual(g('pt-br'), 'pt-br')
|
||||||
|
self.assertEqual(g('pt'), 'pt-br')
|
||||||
|
self.assertEqual(g('pt-pt'), 'pt-br')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('pt', strict=True)
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('pt-pt', strict=True)
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('xyz')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('xy-zz')
|
||||||
|
|
||||||
|
@override_settings(WAGTAIL_CONTENT_LANGUAGES=[
|
||||||
|
('en', 'English'),
|
||||||
|
('de', 'German'),
|
||||||
|
])
|
||||||
|
def test_uses_wagtail_content_languages(self):
|
||||||
|
# be sure it's not using Django's LANGUAGES
|
||||||
|
g = get_supported_content_language_variant
|
||||||
|
self.assertEqual(g('en'), 'en')
|
||||||
|
self.assertEqual(g('en-gb'), 'en')
|
||||||
|
self.assertEqual(g('de'), 'de')
|
||||||
|
self.assertEqual(g('de-at'), 'de')
|
||||||
|
self.assertEqual(g('de-ch'), 'de')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('pt-br')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('pt')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('pt-pt')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('xyz')
|
||||||
|
with self.assertRaises(LookupError):
|
||||||
|
g('xy-zz')
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
@ -5,9 +6,14 @@ from anyascii import anyascii
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.conf.locale import LANG_INFO
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.signals import setting_changed
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from django.dispatch import receiver
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.utils.translation import check_for_language
|
||||||
|
|
||||||
WAGTAIL_APPEND_SLASH = getattr(settings, 'WAGTAIL_APPEND_SLASH', True)
|
WAGTAIL_APPEND_SLASH = getattr(settings, 'WAGTAIL_APPEND_SLASH', True)
|
||||||
|
|
||||||
|
@ -195,3 +201,74 @@ def find_available_slug(parent, requested_slug):
|
||||||
number += 1
|
number += 1
|
||||||
|
|
||||||
return slug
|
return slug
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
|
def get_content_languages():
|
||||||
|
"""
|
||||||
|
Cache of settings.WAGTAIL_CONTENT_LANGUAGES in a dictionary for easy lookups by key.
|
||||||
|
"""
|
||||||
|
content_languages = getattr(settings, 'WAGTAIL_CONTENT_LANGUAGES', None)
|
||||||
|
languages = dict(settings.LANGUAGES)
|
||||||
|
|
||||||
|
if content_languages is None:
|
||||||
|
# Default to a single language based on LANGUAGE_CODE
|
||||||
|
content_languages = [
|
||||||
|
(settings.LANGUAGE_CODE, languages[settings.LANGUAGE_CODE]),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check that each content language is in LANGUAGES
|
||||||
|
for language_code, name in content_languages:
|
||||||
|
if language_code not in languages:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"The language {} is specified in WAGTAIL_CONTENT_LANGUAGES but not LANGUAGES. "
|
||||||
|
"WAGTAIL_CONTENT_LANGUAGES must be a subset of LANGUAGES.".format(language_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(content_languages)
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=1000)
|
||||||
|
def get_supported_content_language_variant(lang_code, strict=False):
|
||||||
|
"""
|
||||||
|
Return the language code that's listed in supported languages, possibly
|
||||||
|
selecting a more generic variant. Raise LookupError if nothing is found.
|
||||||
|
If `strict` is False (the default), look for a country-specific variant
|
||||||
|
when neither the language code nor its generic variant is found.
|
||||||
|
lru_cache should have a maxsize to prevent from memory exhaustion attacks,
|
||||||
|
as the provided language codes are taken from the HTTP request. See also
|
||||||
|
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
|
||||||
|
|
||||||
|
This is equvilant to Django's `django.utils.translation.get_supported_content_language_variant`
|
||||||
|
but reads the `WAGTAIL_CONTENT_LANGUAGES` setting instead.
|
||||||
|
"""
|
||||||
|
if lang_code:
|
||||||
|
# If 'fr-ca' is not supported, try special fallback or language-only 'fr'.
|
||||||
|
possible_lang_codes = [lang_code]
|
||||||
|
try:
|
||||||
|
possible_lang_codes.extend(LANG_INFO[lang_code]["fallback"])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
generic_lang_code = lang_code.split("-")[0]
|
||||||
|
possible_lang_codes.append(generic_lang_code)
|
||||||
|
supported_lang_codes = get_content_languages()
|
||||||
|
|
||||||
|
for code in possible_lang_codes:
|
||||||
|
if code in supported_lang_codes and check_for_language(code):
|
||||||
|
return code
|
||||||
|
if not strict:
|
||||||
|
# if fr-fr is not supported, try fr-ca.
|
||||||
|
for supported_code in supported_lang_codes:
|
||||||
|
if supported_code.startswith(generic_lang_code + "-"):
|
||||||
|
return supported_code
|
||||||
|
raise LookupError(lang_code)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(setting_changed)
|
||||||
|
def reset_cache(**kwargs):
|
||||||
|
"""
|
||||||
|
Clear cache when global WAGTAIL_CONTENT_LANGUAGES/LANGUAGES/LANGUAGE_CODE settings are changed
|
||||||
|
"""
|
||||||
|
if kwargs["setting"] in ("WAGTAIL_CONTENT_LANGUAGES", "LANGUAGES", "LANGUAGE_CODE"):
|
||||||
|
get_content_languages.cache_clear()
|
||||||
|
get_supported_content_language_variant.cache_clear()
|
||||||
|
|
|
@ -228,6 +228,12 @@ WAGTAILADMIN_RICH_TEXT_EDITORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WAGTAIL_CONTENT_LANGUAGES = [
|
||||||
|
("en", "English"),
|
||||||
|
("fr", "French"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Set a non-standard DEFAULT_AUTHENTICATION_CLASSES value, to verify that the
|
# Set a non-standard DEFAULT_AUTHENTICATION_CLASSES value, to verify that the
|
||||||
# admin API still works with session-based auth regardless of this setting
|
# admin API still works with session-based auth regardless of this setting
|
||||||
# (see https://github.com/wagtail/wagtail/issues/5585)
|
# (see https://github.com/wagtail/wagtail/issues/5585)
|
||||||
|
|
Ładowanie…
Reference in New Issue