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.locale import LANG_INFO
|
||||
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
|
||||
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
|
||||
|
@ -18,56 +11,3 @@ try:
|
|||
except ValueError:
|
||||
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form"
|
||||
" '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.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):
|
||||
Locale = apps.get_model("wagtailcore.Locale")
|
||||
|
||||
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.search import index
|
||||
|
||||
from .compat import get_languages, get_supported_language_variant
|
||||
from .utils import find_available_slug
|
||||
from .utils import (
|
||||
find_available_slug, get_content_languages, get_supported_content_language_variant)
|
||||
|
||||
logger = logging.getLogger('wagtail.core')
|
||||
|
||||
|
@ -296,13 +296,13 @@ def pk(obj):
|
|||
class LocaleManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
# 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):
|
||||
"""
|
||||
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):
|
||||
|
@ -336,7 +336,7 @@ class Locale(models.Model):
|
|||
return cls.get_default()
|
||||
|
||||
def get_display_name(self):
|
||||
return get_languages().get(self.language_code)
|
||||
return get_content_languages().get(self.language_code)
|
||||
|
||||
def __str__(self):
|
||||
return self.get_display_name() or self.language_code
|
||||
|
@ -448,7 +448,7 @@ def bootstrap_translatable_model(model, locale):
|
|||
class BootstrapTranslatableModel(migrations.RunPython):
|
||||
def __init__(self, model_string, language_code=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):
|
||||
model = apps.get_model(model_string)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# -*- 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 wagtail.core.models import Page
|
||||
from wagtail.core.utils import (
|
||||
accepts_kwarg, camelcase_to_underscore, cautious_slugify, find_available_slug, safe_snake_case,
|
||||
string_to_ascii)
|
||||
accepts_kwarg, camelcase_to_underscore, cautious_slugify, find_available_slug,
|
||||
get_content_languages, get_supported_content_language_variant, safe_snake_case, string_to_ascii)
|
||||
|
||||
|
||||
class TestCamelCaseToUnderscore(TestCase):
|
||||
|
@ -138,3 +139,115 @@ class TestFindAvailableSlug(TestCase):
|
|||
slug = find_available_slug(self.root_page, "home")
|
||||
|
||||
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 re
|
||||
import unicodedata
|
||||
|
@ -5,9 +6,14 @@ from anyascii import anyascii
|
|||
|
||||
from django.apps import apps
|
||||
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.dispatch import receiver
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import check_for_language
|
||||
|
||||
WAGTAIL_APPEND_SLASH = getattr(settings, 'WAGTAIL_APPEND_SLASH', True)
|
||||
|
||||
|
@ -195,3 +201,74 @@ def find_available_slug(parent, requested_slug):
|
|||
number += 1
|
||||
|
||||
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
|
||||
# admin API still works with session-based auth regardless of this setting
|
||||
# (see https://github.com/wagtail/wagtail/issues/5585)
|
||||
|
|
Ładowanie…
Reference in New Issue