Simplify language chooser (#328)
* Simplify language chooser
- Allows usage of i18n_patterns with prefix_default_language=False
- Change templatetag from simple tag to an inclusion tag
- Reduces complexity by relying on Django's behavior in the
set_language view: it will translate any url passed as 'next'.
This behavior has been present since Django 1.9.
- Remove individual forms for each language
Fixes #327
Reference: aa5ab114e3
* Add check to ensure LocaleMiddleware
* Remove check in favor of silent warning
* Fix template tag tests
master
parent
43dca1ae93
commit
b393c11ecb
|
|
@ -186,7 +186,6 @@ flat-theme admin-interface
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% get_admin_interface_theme as theme %}
|
{% get_admin_interface_theme as theme %}
|
||||||
{% if theme.language_chooser_active %}
|
{% if theme.language_chooser_active %}
|
||||||
{% get_admin_interface_languages as languages %}
|
{% admin_interface_language_chooser %}
|
||||||
{% include "admin_interface/language_chooser.html" %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
{% load admin_interface_tags %}
|
{% load admin_interface_tags %}
|
||||||
|
|
||||||
{% if languages %}
|
{% if set_language_url %}
|
||||||
<div class="language-chooser {% if theme.language_chooser_control == 'minimal-select' %}minimal {% endif %}">
|
<div class="language-chooser {% if theme.language_chooser_control == 'minimal-select' %}minimal {% endif %}">
|
||||||
{% for language in languages %}
|
<form class="language-chooser-select-form" action="{{ set_language_url }}" method="POST">
|
||||||
<form class="language-chooser-hidden-form" id="language-chooser-hidden-form-{{ language.code }}" action="{{ language.activation_url }}" method="POST">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="language" type="hidden" value="{{ language.code }}">
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
</form>
|
<select name="language" onchange="this.form.submit();">
|
||||||
{% endfor %}
|
{% for code, language in LANGUAGES %}
|
||||||
<form class="language-chooser-select-form">
|
<option value="{{ code }}" {% if code == LANGUAGE_CODE %}selected{% endif %}>
|
||||||
{% csrf_token %}
|
{% if theme.language_chooser_display == 'code' %}{{ code|upper }}{% elif theme.language_chooser_display == 'name' %}{{ language }}{% endif %}
|
||||||
<select name="language" onchange="document.getElementById(String('language-chooser-hidden-form-' + this.value)).submit();">
|
</option>
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language.code }}" {% if language.active %}selected{% endif %}>{% if theme.language_chooser_display == 'code' %}{{ language.code|upper }}{% elif theme.language_chooser_display == 'name' %}{{ language.name }}{% endif %}</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -16,45 +17,43 @@ from admin_interface.models import Theme
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.inclusion_tag("admin_interface/language_chooser.html", takes_context=True)
|
||||||
def get_admin_interface_languages(context):
|
def admin_interface_language_chooser(context):
|
||||||
if not settings.USE_I18N:
|
if not settings.USE_I18N:
|
||||||
# i18n disabled
|
# i18n disabled
|
||||||
return None
|
return None
|
||||||
if len(settings.LANGUAGES) < 2:
|
if len(settings.LANGUAGES) < 2:
|
||||||
# less than 2 languages
|
# less than 2 languages
|
||||||
return None
|
return None
|
||||||
|
if "django.middleware.locale.LocaleMiddleware" not in settings.MIDDLEWARE:
|
||||||
|
warnings.warn(
|
||||||
|
"Language chooser requires 'django.middleware.locale.LocaleMiddleware' "
|
||||||
|
"in your MIDDLEWARE to work.",
|
||||||
|
stacklevel=1,
|
||||||
|
)
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
set_language_url = reverse("set_language")
|
context["set_language_url"] = reverse("set_language")
|
||||||
except NoReverseMatch:
|
except NoReverseMatch:
|
||||||
# ImproperlyConfigured - must include i18n urls:
|
warnings.warn(
|
||||||
# urlpatterns += [url(r'^i18n/', include('django.conf.urls.i18n')),]
|
"Language chooser requires Django's `set_language` view: "
|
||||||
|
"`urlpatterns += [url(r'^i18n/', include('django.conf.urls.i18n'))]`.",
|
||||||
|
stacklevel=1,
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
request = context.get("request", None)
|
request = context.get("request", None)
|
||||||
if not request:
|
if not request:
|
||||||
return None
|
return None
|
||||||
|
context["LANGUAGES"] = settings.LANGUAGES
|
||||||
|
|
||||||
full_path = request.get_full_path()
|
full_path = request.get_full_path()
|
||||||
admin_nolang_url = re.sub(r"^\/([\w]{2})([\-\_]{1}[\w]{2,4})?\/", "/", full_path)
|
admin_nolang_url = re.sub(r"^\/([\w]{2})([\-\_]{1}[\w]{2,4})?\/", "/", full_path)
|
||||||
if admin_nolang_url == full_path:
|
|
||||||
# ImproperlyConfigured - must include admin urls using i18n_patterns:
|
|
||||||
# from django.conf.urls.i18n import i18n_patterns
|
|
||||||
# urlpatterns += i18n_patterns(url(r'^admin/', admin.site.urls))
|
|
||||||
return None
|
|
||||||
langs_data = []
|
|
||||||
default_lang_code = settings.LANGUAGE_CODE
|
default_lang_code = settings.LANGUAGE_CODE
|
||||||
current_lang_code = translation.get_language() or default_lang_code
|
current_lang_code = translation.get_language() or default_lang_code
|
||||||
for language in settings.LANGUAGES:
|
context["LANGUAGE_CODE"] = current_lang_code
|
||||||
lang_code = language[0].lower()
|
context["next"] = admin_nolang_url
|
||||||
lang_name = language[1].title()
|
return context
|
||||||
lang_data = {
|
|
||||||
"code": lang_code,
|
|
||||||
"name": lang_name,
|
|
||||||
"default": lang_code == default_lang_code,
|
|
||||||
"active": lang_code == current_lang_code,
|
|
||||||
"activation_url": f"{set_language_url}?next=/{lang_code}{admin_nolang_url}",
|
|
||||||
}
|
|
||||||
langs_data.append(lang_data)
|
|
||||||
return langs_data
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ MIDDLEWARE = [
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
|
|
|
||||||
|
|
@ -22,118 +22,70 @@ class AdminInterfaceTemplateTagsTestCase(TestCase):
|
||||||
def __render_template(self, string, context=None):
|
def __render_template(self, string, context=None):
|
||||||
return Template(string).render(Context(context or {}))
|
return Template(string).render(Context(context or {}))
|
||||||
|
|
||||||
def test_get_admin_interface_languages(self):
|
def test_admin_interface_language_chooser(self):
|
||||||
context = Context({"request": self.request_factory.get("/en/admin/")})
|
context = Context({"request": self.request_factory.get("/en/admin/")})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
context = templatetags.admin_interface_language_chooser(context)
|
||||||
|
languages = context["LANGUAGES"]
|
||||||
expected_languages = [
|
expected_languages = [
|
||||||
{
|
("de", "Deutsch"),
|
||||||
"code": "de",
|
("en", "English"),
|
||||||
"name": "Deutsch",
|
("es", "Español"),
|
||||||
"default": False,
|
("fa", "Farsi"),
|
||||||
"active": False,
|
("fr", "Français"),
|
||||||
"activation_url": "/i18n/setlang/?next=/de/admin/",
|
("it", "Italiano"),
|
||||||
},
|
("pl", "Polski"),
|
||||||
{
|
("pt-BR", "Português"),
|
||||||
"code": "en",
|
("ru", "Русский"),
|
||||||
"name": "English",
|
("tr", "Türk"),
|
||||||
"default": True,
|
|
||||||
"active": True,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/en/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "es",
|
|
||||||
"name": "Español",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/es/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "fa",
|
|
||||||
"name": "Farsi",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/fa/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "fr",
|
|
||||||
"name": "Français",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/fr/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "it",
|
|
||||||
"name": "Italiano",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/it/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "pl",
|
|
||||||
"name": "Polski",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/pl/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "pt-BR",
|
|
||||||
"name": "Português",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/pt-br/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ru",
|
|
||||||
"name": "Русский",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/ru/admin/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "tr",
|
|
||||||
"name": "Türk",
|
|
||||||
"default": False,
|
|
||||||
"active": False,
|
|
||||||
"activation_url": "/i18n/setlang/?next=/tr/admin/",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
self.assertEqual(len(languages), len(expected_languages))
|
self.assertEqual(len(languages), len(expected_languages))
|
||||||
self.assertEqual(languages[0], expected_languages[0])
|
self.assertEqual(languages[0], expected_languages[0])
|
||||||
self.assertEqual(languages[1], expected_languages[1])
|
self.assertEqual(languages[1], expected_languages[1])
|
||||||
|
self.assertEqual(context["next"], "/admin/")
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
USE_I18N=False,
|
USE_I18N=False,
|
||||||
)
|
)
|
||||||
def test_get_admin_interface_languages_with_i18n_disabled(self):
|
def test_admin_interface_language_chooser_with_i18n_disabled(self):
|
||||||
context = Context({"request": self.request_factory.get("/en/admin/")})
|
context = Context({"request": self.request_factory.get("/en/admin/")})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
self.assertEqual(languages, None)
|
self.assertEqual(tag_context, None)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
ROOT_URLCONF="tests.urls_without_i18n_patterns",
|
ROOT_URLCONF="tests.urls_without_i18n_patterns",
|
||||||
)
|
)
|
||||||
def test_get_admin_interface_languages_without_i18n_url_patterns(self):
|
def test_admin_interface_language_chooser_without_i18n_url_patterns(self):
|
||||||
context = Context({"request": self.request_factory.get("/en/admin/")})
|
context = Context({"request": self.request_factory.get("/en/admin/")})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
with self.assertWarnsMessage(UserWarning, "django.conf.urls.i18n"):
|
||||||
self.assertEqual(languages, None)
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
|
self.assertEqual(tag_context, None)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[],
|
||||||
|
)
|
||||||
|
def test_admin_interface_language_chooser_without_locale_middleware(self):
|
||||||
|
context = Context({"request": self.request_factory.get("/en/admin/")})
|
||||||
|
with self.assertWarnsMessage(UserWarning, "LocaleMiddleware"):
|
||||||
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
|
self.assertEqual(tag_context, None)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
LANGUAGES=(("en", "English"),),
|
LANGUAGES=(("en", "English"),),
|
||||||
)
|
)
|
||||||
def test_get_admin_interface_languages_without_multiple_languages(self):
|
def test_admin_interface_language_chooser_without_multiple_languages(self):
|
||||||
context = Context({"request": self.request_factory.get("/en/admin/")})
|
context = Context({"request": self.request_factory.get("/en/admin/")})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
self.assertEqual(languages, None)
|
self.assertEqual(tag_context, None)
|
||||||
|
|
||||||
def test_get_admin_interface_languages_without_request(self):
|
def test_admin_interface_language_chooser_without_request(self):
|
||||||
context = Context({})
|
context = Context({})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
self.assertEqual(languages, None)
|
self.assertEqual(tag_context, None)
|
||||||
|
|
||||||
def test_get_admin_interface_languages_without_language_prefix_in_url(self):
|
def test_admin_interface_language_chooser_without_language_prefix_in_url(self):
|
||||||
context = Context({"request": self.request_factory.get("/admin/")})
|
context = Context({"request": self.request_factory.get("/admin/")})
|
||||||
languages = templatetags.get_admin_interface_languages(context)
|
tag_context = templatetags.admin_interface_language_chooser(context)
|
||||||
self.assertEqual(languages, None)
|
self.assertEqual(tag_context["next"], "/admin/")
|
||||||
|
|
||||||
def test_get_theme(self):
|
def test_get_theme(self):
|
||||||
Theme.objects.all().delete()
|
Theme.objects.all().delete()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue