Skip to content

Commit e090d29

Browse files
authored
Merge pull request #156 from farridav/farridav/language_menu
Adds optional language menu, fixes locale settings in demo
2 parents c339d08 + 7d6b706 commit e090d29

File tree

10 files changed

+91
-37
lines changed

10 files changed

+91
-37
lines changed

docs/configuration.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ JAZZMIN_SETTINGS = {
118118
"changeform_format": "horizontal_tabs",
119119
# override change forms on a per modeladmin basis
120120
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs",},
121+
# Add a language dropdown into the admin
122+
"language_chooser": True,
121123
}
122124
```
123125

@@ -226,6 +228,24 @@ Puts fieldsets and inlines into a bootstrap carousel, and allows paginaton with
226228

227229
![Carousel](./img/changeform_carousel.png)
228230

231+
## Language Chooser
232+
You can enable a language chooser dropdown using `"language_chooser": True` in your `JAZZMIN_SETTINGS`, we mainly use this for
233+
assisting with translations, but it could be of use to some people in their admin site.
234+
235+
To make proper use of this, please ensure you have internationalisation setup properly, See https://docs.djangoproject.com/en/3.1/topics/i18n/translation/
236+
237+
Namely:
238+
239+
- i18n urls for your admin
240+
- `LocaleMiddleware` is used, and in the right place
241+
- `LOCALE_DIRS` is setup
242+
- `LANGUAGES` have been defined
243+
244+
See our [test app settings](https://github.com/farridav/django-jazzmin/tree/master/tests/test_app/settings.py)
245+
for a practical example.
246+
247+
![Language chooser](./img/language_chooser.png)
248+
229249
## UI Tweaks
230250

231251
### UI Customiser

docs/img/language_chooser.png

11.1 KB
Loading

jazzmin/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
"changeform_format": "horizontal_tabs",
7373
# override change forms on a per modeladmin basis
7474
"changeform_format_overrides": {},
75+
# Add a language dropdown into the admin
76+
"language_chooser": False,
7577
}
7678

7779
#######################################

jazzmin/templates/admin/base.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@
9999
</li>
100100
{% endif %}
101101

102+
{% if jazzmin_settings.language_chooser %}
103+
{% get_available_languages as LANGUAGES %}
104+
{% get_language_info_list for LANGUAGES as languages %}
105+
106+
<li class="nav-item dropdown">
107+
<a class="nav-link btn" data-toggle="dropdown" href="#" title="Choose language">
108+
<i class="fas fa-globe pr-2" aria-hidden="true"></i>
109+
</a>
110+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-left" id="jazzy-languagemenu">
111+
{% for language in languages %}
112+
<a href="{{ request|change_lang:language.code }}" class="dropdown-item{% if language.code == LANGUAGE_CODE %} active{% endif %}" lang="{{ language.code }}">
113+
{{ language.name_local }}
114+
</a>
115+
{% endfor %}
116+
</div>
117+
</li>
118+
{% endif %}
119+
102120
<li class="nav-item dropdown">
103121
<a class="nav-link btn" data-toggle="dropdown" href="#" title="{{ request.user }}">
104122
<i class="far fa-user pr-2" aria-hidden="true"></i>

jazzmin/templatetags/jazzmin.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from django.template import Library, Context
1919
from django.template.loader import get_template
2020
from django.templatetags.static import static
21+
from django.utils import translation
2122
from django.utils.html import format_html
2223
from django.utils.safestring import mark_safe, SafeText
2324
from django.utils.text import get_text_list, slugify
@@ -242,6 +243,15 @@ def has_fieldsets(adminform: AdminForm) -> bool:
242243
return has_fieldsets_check(adminform)
243244

244245

246+
@register.filter
247+
def change_lang(request: HttpRequest, language_code: str) -> str:
248+
"""
249+
Change the url to use the given language
250+
"""
251+
current_language = translation.get_language()
252+
return request.get_full_path().replace(current_language, language_code)
253+
254+
245255
@register.filter
246256
def debug(value: Any) -> Any:
247257
"""
@@ -387,9 +397,7 @@ def deleted(x: str) -> Dict:
387397
)
388398
if "name" in sub_message["changed"]:
389399
sub_message["changed"]["name"] = gettext(sub_message["changed"]["name"])
390-
messages.append(
391-
changed(gettext("Changed {fields}.").format(sub_message["changed"]["fields"]))
392-
)
400+
messages.append(changed(gettext("Changed {fields}.").format(**sub_message["changed"])))
393401
else:
394402
messages.append(changed(gettext("Changed {fields}.").format(**sub_message["changed"])))
395403

tests/test_app/settings.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
MIDDLEWARE = [
3030
"django.middleware.security.SecurityMiddleware",
3131
"django.contrib.sessions.middleware.SessionMiddleware",
32+
"django.middleware.locale.LocaleMiddleware",
3233
"django.middleware.common.CommonMiddleware",
3334
"django.middleware.csrf.CsrfViewMiddleware",
3435
"django.contrib.admindocs.middleware.XViewMiddleware",
@@ -75,7 +76,7 @@
7576
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
7677
]
7778

78-
LANGUAGE_CODE = "en-gb"
79+
LANGUAGE_CODE = "en"
7980
TIME_ZONE = "Europe/London"
8081
USE_I18N = True
8182
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
@@ -88,8 +89,8 @@
8889
("en", gettext("English")),
8990
("de", gettext("German")),
9091
("es", gettext("Spanish")),
91-
("zh_Hans", gettext("Chinese (Simplified)")),
92-
("zh_Hant", gettext("Chinese (Traditional)")),
92+
("zh-hans", gettext("Simplified Chinese")),
93+
("zh-hant", gettext("Traditional Chinese")),
9394
)
9495

9596
STATIC_URL = "/static/"
@@ -207,7 +208,9 @@
207208
# - carousel
208209
"changeform_format": "horizontal_tabs",
209210
# override change forms on a per modeladmin basis
210-
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs",},
211+
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"},
212+
# Add a language dropdown into the admin
213+
"language_chooser": True,
211214
}
212215

213216
if not DEBUG and not TEST:

tests/test_app/urls.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from django.conf import settings
2+
from django.conf.urls.i18n import i18n_patterns
23
from django.contrib import admin, messages
34
from django.http import HttpResponseRedirect
4-
from django.urls import re_path, reverse
5+
from django.urls import re_path, reverse, path
56
from django.views.generic import RedirectView
67
from django.views.static import serve
78

@@ -23,10 +24,12 @@ def make_messages(request):
2324
urlpatterns = [
2425
url(r"^$", RedirectView.as_view(pattern_name="admin:index", permanent=False)),
2526
url(r"admin/doc/", include("django.contrib.admindocs.urls")),
26-
url(r"admin/", admin.site.urls),
2727
url(r"make_messages/", make_messages, name="make_messages"),
28+
path("i18n/", include("django.conf.urls.i18n")),
2829
]
2930

31+
urlpatterns += i18n_patterns(path("admin/", admin.site.urls))
32+
3033
if settings.DEBUG:
3134
urlpatterns.append(re_path(r"^static/(?P<path>.*)$", serve, kwargs={"document_root": settings.STATIC_ROOT}))
3235

tests/test_jazzmin_menus.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,34 @@ def test_side_menu(admin_client, settings):
1414
response = admin_client.get(url)
1515

1616
assert parse_sidemenu(response) == {
17-
"Global": ["/admin/"],
17+
"Global": ["/en/admin/"],
1818
"Polls": [
19-
"/admin/polls/campaign/",
20-
"/admin/polls/cheese/",
21-
"/admin/polls/choice/",
22-
"/admin/polls/poll/",
23-
"/admin/polls/vote/",
19+
"/en/admin/polls/campaign/",
20+
"/en/admin/polls/cheese/",
21+
"/en/admin/polls/choice/",
22+
"/en/admin/polls/poll/",
23+
"/en/admin/polls/vote/",
2424
"/make_messages/",
2525
],
26-
"Administration": ["/admin/admin/logentry/"],
27-
"Authentication and Authorization": ["/admin/auth/group/", "/admin/auth/user/"],
26+
"Administration": ["/en/admin/admin/logentry/"],
27+
"Authentication and Authorization": ["/en/admin/auth/group/", "/en/admin/auth/user/"],
2828
}
2929

3030
settings.JAZZMIN_SETTINGS = override_jazzmin_settings(hide_models=["auth.user"])
3131
response = admin_client.get(url)
3232

3333
assert parse_sidemenu(response) == {
34-
"Global": ["/admin/"],
34+
"Global": ["/en/admin/"],
3535
"Polls": [
36-
"/admin/polls/campaign/",
37-
"/admin/polls/cheese/",
38-
"/admin/polls/choice/",
39-
"/admin/polls/poll/",
40-
"/admin/polls/vote/",
36+
"/en/admin/polls/campaign/",
37+
"/en/admin/polls/cheese/",
38+
"/en/admin/polls/choice/",
39+
"/en/admin/polls/poll/",
40+
"/en/admin/polls/vote/",
4141
"/make_messages/",
4242
],
43-
"Administration": ["/admin/admin/logentry/"],
44-
"Authentication and Authorization": ["/admin/auth/group/"],
43+
"Administration": ["/en/admin/admin/logentry/"],
44+
"Authentication and Authorization": ["/en/admin/auth/group/"],
4545
}
4646

4747

@@ -70,11 +70,11 @@ def test_permissions_on_custom_links(client, settings):
7070

7171
client.force_login(user)
7272
response = client.get(url)
73-
assert parse_sidemenu(response) == {"Global": ["/admin/"]}
73+
assert parse_sidemenu(response) == {"Global": ["/en/admin/"]}
7474

7575
client.force_login(user2)
7676
response = client.get(url)
77-
assert parse_sidemenu(response) == {"Global": ["/admin/"], "Polls": ["/admin/polls/poll/", "/make_messages/"]}
77+
assert parse_sidemenu(response) == {"Global": ["/en/admin/"], "Polls": ["/en/admin/polls/poll/", "/make_messages/"]}
7878

7979

8080
@pytest.mark.django_db
@@ -96,9 +96,9 @@ def test_top_menu(admin_client, settings):
9696
response = admin_client.get(url)
9797

9898
assert parse_topmenu(response) == [
99-
{"name": "Home", "link": "/admin/"},
99+
{"name": "Home", "link": "/en/admin/"},
100100
{"name": "Support", "link": "https://github.com/farridav/django-jazzmin/issues"},
101-
{"name": "Users", "link": "/admin/auth/user/"},
101+
{"name": "Users", "link": "/en/admin/auth/user/"},
102102
{
103103
"name": "Polls",
104104
"link": "#",
@@ -132,10 +132,10 @@ def test_user_menu(admin_user, client, settings):
132132
response = client.get(url)
133133

134134
assert parse_usermenu(response) == [
135-
{"link": "/admin/password_change/", "name": "Change password"},
136-
{"link": "/admin/logout/", "name": "Log out"},
137-
{"link": "/admin/", "name": "Home"},
135+
{"link": "/en/admin/password_change/", "name": "Change password"},
136+
{"link": "/en/admin/logout/", "name": "Log out"},
137+
{"link": "/en/admin/", "name": "Home"},
138138
{"link": "https://github.com/farridav/django-jazzmin/issues", "name": "Support"},
139-
{"link": "/admin/auth/user/", "name": "Users"},
140-
{"link": "/admin/auth/user/{}/change/".format(admin_user.pk), "name": "See Profile"},
139+
{"link": "/en/admin/auth/user/", "name": "Users"},
140+
{"link": "/en/admin/auth/user/{}/change/".format(admin_user.pk), "name": "See Profile"},
141141
]

tests/test_permissions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_delete_but_no_view_permission(client):
4949
client.force_login(user)
5050

5151
response = client.get(url)
52-
assert parse_sidemenu(response) == {"Global": ["/admin/"], "Polls": [None]}
52+
assert parse_sidemenu(response) == {"Global": ["/en/admin/"], "Polls": [None]}
5353

5454

5555
@pytest.mark.django_db
@@ -65,4 +65,4 @@ def test_no_permission(client):
6565
client.force_login(user)
6666

6767
response = client.get(url)
68-
assert parse_sidemenu(response) == {"Global": ["/admin/"]}
68+
assert parse_sidemenu(response) == {"Global": ["/en/admin/"]}

tests/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_get_custom_url():
5151
"""
5252
assert get_custom_url("http://somedomain.com") == "http://somedomain.com"
5353
assert get_custom_url("/relative/path") == "/relative/path"
54-
assert get_custom_url("admin:polls_poll_changelist") == "/admin/polls/poll/"
54+
assert get_custom_url("admin:polls_poll_changelist") == "/en/admin/polls/poll/"
5555

5656

5757
@pytest.mark.django_db

0 commit comments

Comments
 (0)