From 72a935a8f09e0c108f2d94d585bcb1e5e6ed2d0a Mon Sep 17 00:00:00 2001 From: Daniel Chimeno Date: Fri, 27 Jan 2017 23:33:48 +0100 Subject: [PATCH] Adds user-prefered admin language (#3310). added prefered language field and migration added form and view to select prefered language Added some tests modified tests fix styleguide errors activate middleware only for authenticated users fixed typos fixed test undoing language preferences fixed lint issues fixed tests fixed message change behaviour and more test added added utf8 coding [skip ci] initial documentation for preferred language added contributor refactor get_available_admin_languages refactor get_available_admin_languages make language name language aware translate notifications to recipient language make language name language aware fixed lint --- CONTRIBUTORS.rst | 1 + docs/advanced_topics/i18n/index.rst | 20 ++++++++ wagtail/wagtailadmin/decorators.py | 4 ++ .../wagtailadmin/account/account.html | 14 +++++- .../account/language_preferences.html | 20 ++++++++ .../tests/test_account_management.py | 47 +++++++++++++++++++ wagtail/wagtailadmin/urls/__init__.py | 5 ++ wagtail/wagtailadmin/utils.py | 34 ++++++++++++-- wagtail/wagtailadmin/views/account.py | 24 +++++++++- wagtail/wagtailcore/middleware.py | 2 - wagtail/wagtailusers/forms.py | 12 +++++ .../0006_userprofile_prefered_language.py | 20 ++++++++ wagtail/wagtailusers/models.py | 10 ++++ 13 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 wagtail/wagtailadmin/templates/wagtailadmin/account/language_preferences.html create mode 100644 wagtail/wagtailusers/migrations/0006_userprofile_prefered_language.py diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index d79f61e567..186e6589a6 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -228,6 +228,7 @@ Contributors * Thijs Kramer * Ramon de Jezus * Ross Curzon-Butler +* Daniel Chimeno Translators =========== diff --git a/docs/advanced_topics/i18n/index.rst b/docs/advanced_topics/i18n/index.rst index 1f248f488c..8e11b1de8f 100644 --- a/docs/advanced_topics/i18n/index.rst +++ b/docs/advanced_topics/i18n/index.rst @@ -17,6 +17,26 @@ The Wagtail admin backend has been translated into many different languages. You If your language isn't listed on that page, you can easily contribute new languages or correct mistakes. Sign up and submit changes to `Transifex `_. Translation updates are typically merged into an official release within one month of being submitted. +Change Wagtail admin language on a per user basis +================================================= +.. versionadded:: 1.10 + + +Logged users can set their preferred language from ``/admin/account/``. +By default, Wagtail provides a list of languages that have a coverage >= 90% translation. +It is possible to override this list by adding a setting configuration. + +Example: + +.. code-block:: python + + from django.conf import settings + WAGTAILADMIN_PERMITTED_LANGUAGES = [ ('en' , 'English'), ('pt', 'Portuguese') ] + +In case there is zero or one language permitted, the form will be hidden. + +If there is no language selected either by the user or by the Site Administrator, the ``LANGUAGE_CODE`` wil be used. + Changing the primary language of your Wagtail installation ========================================================== diff --git a/wagtail/wagtailadmin/decorators.py b/wagtail/wagtailadmin/decorators.py index fe0f583323..73d015dc80 100644 --- a/wagtail/wagtailadmin/decorators.py +++ b/wagtail/wagtailadmin/decorators.py @@ -2,7 +2,9 @@ from __future__ import absolute_import, unicode_literals from django.contrib.auth.views import redirect_to_login as auth_redirect_to_login from django.core.urlresolvers import reverse + from django.utils.translation import ugettext as _ +from django.utils.translation import activate from wagtail.utils.compat import user_is_anonymous from wagtail.wagtailadmin import messages @@ -21,6 +23,8 @@ def require_admin_access(view_func): return redirect_to_login(request) if user.has_perms(['wagtailadmin.access_admin']): + if hasattr(user, 'wagtail_userprofile'): + activate(user.wagtail_userprofile.get_preferred_language()) return view_func(request, *args, **kwargs) messages.error(request, _('You do not have permission to access the admin')) diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/account/account.html b/wagtail/wagtailadmin/templates/wagtailadmin/account/account.html index 9d11704753..4527f8902a 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/account/account.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/account/account.html @@ -16,7 +16,7 @@ {% trans "Your avatar image is provided by Gravatar and is connected to your email address. With a Gravatar account you can set an avatar for any number of other email addresses you use." %} - + {% if show_change_password %}
  • @@ -40,6 +40,18 @@
  • {% endif %} + {% if show_preferred_language_preferences %} +
  • + + + + {% trans "Choose the language you want to use here." %} + +
  • + {% endif %} + {% endblock %} diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/account/language_preferences.html b/wagtail/wagtailadmin/templates/wagtailadmin/account/language_preferences.html new file mode 100644 index 0000000000..3b63b40380 --- /dev/null +++ b/wagtail/wagtailadmin/templates/wagtailadmin/account/language_preferences.html @@ -0,0 +1,20 @@ +{% extends "wagtailadmin/base.html" %} +{% load i18n %} + +{% block titletag %}{% trans "Language Preferences" %}{% endblock %} +{% block content %} + {% trans "Language Preferences" as prefs_str %} + {% include "wagtailadmin/shared/header.html" with title=prefs_str %} + +
    +
    + {% csrf_token %} +
      + {% for field in form %} + {% include "wagtailadmin/shared/field_as_li.html" with field=field %} + {% endfor %} +
    • +
    +
    +
    +{% endblock %} diff --git a/wagtail/wagtailadmin/tests/test_account_management.py b/wagtail/wagtailadmin/tests/test_account_management.py index 7a7b9078f0..7c3c8fd3d4 100644 --- a/wagtail/wagtailadmin/tests/test_account_management.py +++ b/wagtail/wagtailadmin/tests/test_account_management.py @@ -8,6 +8,8 @@ from django.core.urlresolvers import reverse from django.test import TestCase, override_settings from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailadmin.utils import ( + WAGTAILADMIN_PROVIDED_LANGUAGES, get_available_admin_languages) from wagtail.wagtailusers.models import UserProfile @@ -275,6 +277,51 @@ class TestAccountSection(TestCase, WagtailTestUtils): self.assertFalse(profile.approved_notifications) self.assertTrue(profile.rejected_notifications) + def test_language_preferences_view(self): + """ + This tests that the language preferences view responds with an index page + """ + # Get account page + response = self.client.get(reverse('wagtailadmin_account_language_preferences')) + + # Check that the user received an account page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/language_preferences.html') + + # Page should contain a 'Language Preferences' title + self.assertContains(response, "Language Preferences") + + def test_language_preferences_view_post(self): + """ + This post to the language preferences view and checks that the + user's profile is updated + """ + # Post new values to the language preferences page + post_data = { + 'preferred_language': 'es' + } + response = self.client.post(reverse('wagtailadmin_account_language_preferences'), post_data) + + # Check that the user was redirected to the account language preferences page + self.assertEqual(response.status_code, 200) + + profile = UserProfile.get_for_user(get_user_model().objects.get(pk=self.user.pk)) + + # Check that the language preferences are stored + self.assertEqual(profile.preferred_language, 'es') + + @override_settings(WAGTAILADMIN_PERMITTED_LANGUAGES=[('en', 'English'), ('es', 'Spanish')]) + def test_available_admin_languages_with_permitted_languages(self): + self.assertListEqual(get_available_admin_languages(), [('en', 'English'), ('es', 'Spanish')]) + + def test_available_admin_languages_by_default(self): + self.assertListEqual(get_available_admin_languages(), WAGTAILADMIN_PROVIDED_LANGUAGES) + + @override_settings(WAGTAILADMIN_PERMITTED_LANGUAGES=[('en', 'English')]) + def test_not_show_options_if_only_one_language_is_permitted(self): + response = self.client.post(reverse('wagtailadmin_account')) + self.assertNotContains(response, 'Language Preferences') + class TestAccountManagementForNonModerator(TestCase, WagtailTestUtils): """ diff --git a/wagtail/wagtailadmin/urls/__init__.py b/wagtail/wagtailadmin/urls/__init__.py index 56549d3545..57b206083e 100644 --- a/wagtail/wagtailadmin/urls/__init__.py +++ b/wagtail/wagtailadmin/urls/__init__.py @@ -44,6 +44,11 @@ urlpatterns = [ account.notification_preferences, name='wagtailadmin_account_notification_preferences' ), + url( + r'^account/language_preferences/$', + account.language_preferences, + name='wagtailadmin_account_language_preferences' + ), url(r'^logout/$', account.logout, name='wagtailadmin_logout'), ] diff --git a/wagtail/wagtailadmin/utils.py b/wagtail/wagtailadmin/utils.py index 30502f0da9..572649fe69 100644 --- a/wagtail/wagtailadmin/utils.py +++ b/wagtail/wagtailadmin/utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import logging @@ -11,6 +12,7 @@ from django.db.models import Count, Q from django.shortcuts import redirect from django.template.loader import render_to_string from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy, override from modelcluster.fields import ParentalKey from taggit.models import Tag @@ -19,6 +21,30 @@ from wagtail.wagtailusers.models import UserProfile logger = logging.getLogger('wagtail.admin') +# Wagtail languages with >=90% coverage +# This list is manually maintained +WAGTAILADMIN_PROVIDED_LANGUAGES = [ + ('en', _('English')), + ('de', gettext_lazy('German')), + ('pt-br', gettext_lazy('Brazilian Portuguese')), + ('es', gettext_lazy('Spanish')), + ('ro', gettext_lazy('Romanian')), + ('fr', gettext_lazy('French')), + ('is-is', gettext_lazy('Icelandic')), + ('it', gettext_lazy('Italian')), + ('nb', gettext_lazy('Norwegian Bokmål')), + ('pl', gettext_lazy('Polish')), + ('pt-pt', gettext_lazy('Portuguese')), + ('ru', gettext_lazy('Russian')), + ('nl-nl', gettext_lazy('Netherlands Dutch')), + ('fi', gettext_lazy('Finish')), + ('ga', gettext_lazy('Galician')) +] + + +def get_available_admin_languages(): + return getattr(settings, 'WAGTAILADMIN_PERMITTED_LANGUAGES', WAGTAILADMIN_PROVIDED_LANGUAGES) + def get_object_usage(obj): "Returns a queryset of pages that link to a particular object" @@ -216,9 +242,11 @@ def send_notification(page_revision_id, notification, excluded_user_id): # update context with this recipient context["user"] = recipient - # Get email subject and content - email_subject = render_to_string(template_subject, context).strip() - email_content = render_to_string(template_text, context).strip() + # Translate text to the recipient language settings + with override(recipient.wagtail_userprofile.get_preferred_language()): + # Get email subject and content + email_subject = render_to_string(template_subject, context).strip() + email_content = render_to_string(template_text, context).strip() kwargs = {} if getattr(settings, 'WAGTAILADMIN_NOTIFICATION_USE_HTML', False): diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py index 5ae0581dee..6a396355aa 100644 --- a/wagtail/wagtailadmin/views/account.py +++ b/wagtail/wagtailadmin/views/account.py @@ -10,13 +10,15 @@ from django.contrib.auth.forms import PasswordChangeForm from django.http import Http404 from django.shortcuts import redirect, render from django.utils.translation import ugettext as _ +from django.utils.translation import activate from django.views.decorators.cache import never_cache from django.views.decorators.debug import sensitive_post_parameters from wagtail.utils.compat import user_is_authenticated from wagtail.wagtailadmin import forms +from wagtail.wagtailadmin.utils import get_available_admin_languages from wagtail.wagtailcore.models import UserPagePermissionsProxy -from wagtail.wagtailusers.forms import NotificationPreferencesForm +from wagtail.wagtailusers.forms import NotificationPreferencesForm, PreferredLanguageForm from wagtail.wagtailusers.models import UserProfile @@ -40,7 +42,8 @@ def account(request): return render(request, 'wagtailadmin/account/account.html', { 'show_change_password': password_management_enabled() and request.user.has_usable_password(), - 'show_notification_preferences': show_notification_preferences + 'show_notification_preferences': show_notification_preferences, + 'show_preferred_language_preferences': len(get_available_admin_languages()) > 1 }) @@ -107,6 +110,23 @@ def notification_preferences(request): }) +def language_preferences(request): + if request.method == 'POST': + form = PreferredLanguageForm(request.POST, instance=UserProfile.get_for_user(request.user)) + + if form.is_valid(): + user_profile = form.save() + # This will set the language only for this request/thread + activate(user_profile.preferred_language) + messages.success(request, _("Your preferences have been updated.")) + else: + form = PreferredLanguageForm(instance=UserProfile.get_for_user(request.user)) + + return render(request, 'wagtailadmin/account/language_preferences.html', { + 'form': form, + }) + + @sensitive_post_parameters() @never_cache def login(request): diff --git a/wagtail/wagtailcore/middleware.py b/wagtail/wagtailcore/middleware.py index 0658b75275..09442510d2 100644 --- a/wagtail/wagtailcore/middleware.py +++ b/wagtail/wagtailcore/middleware.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, unicode_literals import django - from wagtail.wagtailcore.models import Site @@ -11,7 +10,6 @@ else: MiddlewareMixin = object - class SiteMiddleware(MiddlewareMixin): def process_request(self, request): """ diff --git a/wagtail/wagtailusers/forms.py b/wagtail/wagtailusers/forms.py index 7f15ffc1f5..af8527c72b 100644 --- a/wagtail/wagtailusers/forms.py +++ b/wagtail/wagtailusers/forms.py @@ -12,6 +12,7 @@ from django.template.loader import render_to_string from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _ +from wagtail.wagtailadmin.utils import get_available_admin_languages from wagtail.wagtailadmin.widgets import AdminPageChooser from wagtail.wagtailcore import hooks from wagtail.wagtailcore.models import ( @@ -352,3 +353,14 @@ class NotificationPreferencesForm(forms.ModelForm): class Meta: model = UserProfile fields = ("submitted_notifications", "approved_notifications", "rejected_notifications") + + +class PreferredLanguageForm(forms.ModelForm): + preferred_language = forms.ChoiceField(choices=get_available_admin_languages()) + + def __init__(self, *args, **kwargs): + super(PreferredLanguageForm, self).__init__(*args, **kwargs) + + class Meta: + model = UserProfile + fields = ("preferred_language",) diff --git a/wagtail/wagtailusers/migrations/0006_userprofile_prefered_language.py b/wagtail/wagtailusers/migrations/0006_userprofile_prefered_language.py new file mode 100644 index 0000000000..de2b5b27a9 --- /dev/null +++ b/wagtail/wagtailusers/migrations/0006_userprofile_prefered_language.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-27 22:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailusers', '0005_make_related_name_wagtail_specific'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='preferred_language', + field=models.CharField(default='', help_text='Select language for the admin', max_length=10, verbose_name='preferred language'), + ), + ] diff --git a/wagtail/wagtailusers/models.py b/wagtail/wagtailusers/models.py index f4af3276bf..00a034d50d 100644 --- a/wagtail/wagtailusers/models.py +++ b/wagtail/wagtailusers/models.py @@ -30,10 +30,20 @@ class UserProfile(models.Model): help_text=_("Receive notification when your page edit is rejected") ) + preferred_language = models.CharField( + verbose_name=_('preferred language'), + max_length=10, + help_text=_("Select language for the admin"), + default='' + ) + @classmethod def get_for_user(cls, user): return cls.objects.get_or_create(user=user)[0] + def get_preferred_language(self): + return self.preferred_language or settings.LANGUAGE_CODE + def __str__(self): return self.user.get_username()