From 837e0b8384fddac84f527ef3607587de0d1504fc Mon Sep 17 00:00:00 2001 From: Sai Kiran Naragam Date: Tue, 30 Jan 2018 11:51:07 +0530 Subject: [PATCH] locale: Adds preferred language for logged in user - UserProfile model is created, it has one-to-one relationship with User. - Language selection dropdown added to UserCreate and UserUpdate forms. - Adds None to language selection dropdown to explicitly unselect. - LANGUAGE_SESSION_KEY is set to User's preferred language on LogIn activity. - LANGUAGE_SESSION_KEY is deleted on User's LogOut activity. Signed-off-by: Sai Kiran Naragam Reviewed-by: Sunil Mohan Adapa --- plinth/forms.py | 4 +-- plinth/migrations/0004_userprofile.py | 40 +++++++++++++++++++++++++++ plinth/models.py | 7 +++++ plinth/modules/sso/views.py | 7 +++++ plinth/modules/users/forms.py | 23 ++++++++++++--- plinth/modules/users/views.py | 2 ++ plinth/views.py | 7 +++-- 7 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 plinth/migrations/0004_userprofile.py diff --git a/plinth/forms.py b/plinth/forms.py index 548be1345..a46088c97 100644 --- a/plinth/forms.py +++ b/plinth/forms.py @@ -60,7 +60,7 @@ class LanguageSelectionForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - supported_languages = [] + supported_languages = [(None, '-----------')] for language_code, language_name in settings.LANGUAGES: locale_code = translation.to_locale(language_code) plinth_dir = os.path.dirname(plinth.__file__) @@ -70,4 +70,4 @@ class LanguageSelectionForm(forms.Form): self.fields['language'].choices = supported_languages - language = forms.ChoiceField(label='Language', choices=[]) + language = forms.ChoiceField(label='Language', choices=[], required=False) diff --git a/plinth/migrations/0004_userprofile.py b/plinth/migrations/0004_userprofile.py new file mode 100644 index 000000000..205c871db --- /dev/null +++ b/plinth/migrations/0004_userprofile.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-01-29 10:21 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.contrib.auth.models import User +from plinth.models import UserProfile + + +def insert_users(apps, schema_editor): + for user in User.objects.all(): + UserProfile(user=user).save() + + +def truncate_user_profile(apps, schema_editor): + UserProfile.objects.all().delete() + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('plinth', '0003_merge_firstboot_completed_fields'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('preferred_language', models.CharField(default=None, max_length=10, null=True)), + ( + 'user', + models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.RunPython(code=insert_users, reverse_code=truncate_user_profile), + + ] diff --git a/plinth/models.py b/plinth/models.py index 03843a8d0..a7ed19f50 100644 --- a/plinth/models.py +++ b/plinth/models.py @@ -20,6 +20,7 @@ Django models for the main application """ from django.db import models +from django.contrib.auth.models import User import json @@ -43,3 +44,9 @@ class Module(models.Model): """Model to store current setup versions of a module.""" name = models.TextField(primary_key=True) setup_version = models.IntegerField() + + +class UserProfile(models.Model): + """Model that stores User details that are not related to authentication""" + user = models.OneToOneField(User, on_delete=models.CASCADE) + preferred_language = models.CharField(max_length=10, null=True, default=None) diff --git a/plinth/modules/sso/views.py b/plinth/modules/sso/views.py index eea5cd02d..3f0de87a8 100644 --- a/plinth/modules/sso/views.py +++ b/plinth/modules/sso/views.py @@ -25,6 +25,7 @@ import urllib from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.views import LoginView, LogoutView from django.http import HttpResponseRedirect +from django.utils import translation from axes.decorators import axes_form_invalid from axes.utils import reset @@ -65,6 +66,7 @@ class SSOLoginView(LoginView): def dispatch(self, request, *args, **kwargs): response = super(SSOLoginView, self).dispatch(request, *args, **kwargs) if request.user.is_authenticated: + request.session[translation.LANGUAGE_SESSION_KEY] = request.user.userprofile.preferred_language return set_ticket_cookie(request.user, response) else: return response @@ -109,6 +111,11 @@ class SSOLogoutView(LogoutView): def dispatch(self, request, *args, **kwargs): response = super(SSOLogoutView, self).dispatch(request, *args, **kwargs) + try: + del request.session[translation.LANGUAGE_SESSION_KEY] + except KeyError: + pass + response.delete_cookie(SSO_COOKIE_NAME) return response diff --git a/plinth/modules/users/forms.py b/plinth/modules/users/forms.py index 8350eac89..336cb9e45 100644 --- a/plinth/modules/users/forms.py +++ b/plinth/modules/users/forms.py @@ -22,14 +22,17 @@ from django.contrib import auth, messages from django.contrib.auth.forms import SetPasswordForm, UserCreationForm from django.contrib.auth.models import Group, User from django.core.exceptions import ValidationError +from django.utils import translation from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy from plinth import actions, module_loader +from plinth import forms as plinthForms from plinth.errors import ActionError from plinth.modules import first_boot, users from plinth.modules.security import set_restricted_access from plinth.utils import is_user_admin +from plinth.models import UserProfile def get_group_choices(): @@ -72,7 +75,7 @@ class ValidNewUsernameCheckMixin(object): return True -class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): +class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm, plinthForms.LanguageSelectionForm): """Custom user create form. Include options to add user to groups. @@ -89,6 +92,9 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): 'log in to the system through SSH and have ' 'administrative privileges (sudo).')) + class Meta(UserCreationForm.Meta): + fields = ('username', 'password1', 'password2', 'groups', 'language') + def __init__(self, request, *args, **kwargs): """Initialize the form with extra request argument.""" self.request = request @@ -98,6 +104,7 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): def save(self, commit=True): """Save the user model and create LDAP user if required.""" user = super(CreateUserForm, self).save(commit) + UserProfile(user=user, preferred_language=self.cleaned_data['language']).save() if commit: try: @@ -125,7 +132,7 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): return user -class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm): +class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm, plinthForms.LanguageSelectionForm): """When user info is changed, also updates LDAP user.""" ssh_keys = forms.CharField( label=ugettext_lazy('SSH Keys'), @@ -138,7 +145,7 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm): class Meta: """Metadata to control automatic form building.""" - fields = ('username', 'groups', 'ssh_keys', 'is_active') + fields = ('username', 'groups', 'ssh_keys', 'language', 'is_active') model = User widgets = { 'groups': forms.widgets.CheckboxSelectMultiple(), @@ -172,7 +179,14 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm): def save(self, commit=True): """Update LDAP user name and groups after saving user model.""" - user = super(UserUpdateForm, self).save(commit) + user = super(UserUpdateForm, self).save(commit=False) + user.userprofile.preferred_language = self.cleaned_data['language'] + user.userprofile.save() + user.save() + + # If user is updating their own profile then only translate the pages + if self.username == self.request.user.username: + self.request.session[translation.LANGUAGE_SESSION_KEY] = user.userprofile.preferred_language if commit: output = actions.superuser_run('users', @@ -259,6 +273,7 @@ class FirstBootForm(ValidNewUsernameCheckMixin, auth.forms.UserCreationForm): def save(self, commit=True): """Create and log the user in.""" user = super().save(commit=commit) + UserProfile(user=user).save() if commit: first_boot.mark_step_done('users_firstboot') diff --git a/plinth/modules/users/views.py b/plinth/modules/users/views.py index 859379e00..ba72d44a5 100644 --- a/plinth/modules/users/views.py +++ b/plinth/modules/users/views.py @@ -104,6 +104,8 @@ class UserUpdate(ContextMixin, SuccessMessageMixin, UpdateView): ssh_keys = actions.superuser_run( 'ssh', ['get-keys', '--username', self.object.username]) initial['ssh_keys'] = ssh_keys.strip() + user_being_edited = User.objects.get(username=self.kwargs['slug']) + initial['language'] = user_being_edited.userprofile.preferred_language except ActionError: pass diff --git a/plinth/views.py b/plinth/views.py index c6d45e729..1544884bf 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -82,9 +82,12 @@ class LanguageSelectionView(FormView): form = self.form_class(request.POST) if form.is_valid(): selected_language = form.cleaned_data['language'] + if not selected_language: + response = HttpResponseRedirect(reverse('language-selection')) + response.delete_cookie(settings.LANGUAGE_COOKIE_NAME) + return response + translation.activate(selected_language) - # set selected language in session - request.session[translation.LANGUAGE_SESSION_KEY] = selected_language response = HttpResponseRedirect(reverse('language-selection')) # send a cookie for selected language response.set_cookie(settings.LANGUAGE_COOKIE_NAME, selected_language)