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 <saikiran.rguiiit@gmail.com>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Sai Kiran Naragam 2018-01-30 11:51:07 +05:30 committed by James Valleroy
parent 5cff5629d8
commit 837e0b8384
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
7 changed files with 82 additions and 8 deletions

View File

@ -60,7 +60,7 @@ class LanguageSelectionForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
supported_languages = [] supported_languages = [(None, '-----------')]
for language_code, language_name in settings.LANGUAGES: for language_code, language_name in settings.LANGUAGES:
locale_code = translation.to_locale(language_code) locale_code = translation.to_locale(language_code)
plinth_dir = os.path.dirname(plinth.__file__) plinth_dir = os.path.dirname(plinth.__file__)
@ -70,4 +70,4 @@ class LanguageSelectionForm(forms.Form):
self.fields['language'].choices = supported_languages self.fields['language'].choices = supported_languages
language = forms.ChoiceField(label='Language', choices=[]) language = forms.ChoiceField(label='Language', choices=[], required=False)

View File

@ -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),
]

View File

@ -20,6 +20,7 @@ Django models for the main application
""" """
from django.db import models from django.db import models
from django.contrib.auth.models import User
import json import json
@ -43,3 +44,9 @@ class Module(models.Model):
"""Model to store current setup versions of a module.""" """Model to store current setup versions of a module."""
name = models.TextField(primary_key=True) name = models.TextField(primary_key=True)
setup_version = models.IntegerField() 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)

View File

@ -25,6 +25,7 @@ import urllib
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import LoginView, LogoutView from django.contrib.auth.views import LoginView, LogoutView
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils import translation
from axes.decorators import axes_form_invalid from axes.decorators import axes_form_invalid
from axes.utils import reset from axes.utils import reset
@ -65,6 +66,7 @@ class SSOLoginView(LoginView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
response = super(SSOLoginView, self).dispatch(request, *args, **kwargs) response = super(SSOLoginView, self).dispatch(request, *args, **kwargs)
if request.user.is_authenticated: if request.user.is_authenticated:
request.session[translation.LANGUAGE_SESSION_KEY] = request.user.userprofile.preferred_language
return set_ticket_cookie(request.user, response) return set_ticket_cookie(request.user, response)
else: else:
return response return response
@ -109,6 +111,11 @@ class SSOLogoutView(LogoutView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
response = super(SSOLogoutView, self).dispatch(request, *args, response = super(SSOLogoutView, self).dispatch(request, *args,
**kwargs) **kwargs)
try:
del request.session[translation.LANGUAGE_SESSION_KEY]
except KeyError:
pass
response.delete_cookie(SSO_COOKIE_NAME) response.delete_cookie(SSO_COOKIE_NAME)
return response return response

View File

@ -22,14 +22,17 @@ from django.contrib import auth, messages
from django.contrib.auth.forms import SetPasswordForm, UserCreationForm from django.contrib.auth.forms import SetPasswordForm, UserCreationForm
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import translation
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from plinth import actions, module_loader from plinth import actions, module_loader
from plinth import forms as plinthForms
from plinth.errors import ActionError from plinth.errors import ActionError
from plinth.modules import first_boot, users from plinth.modules import first_boot, users
from plinth.modules.security import set_restricted_access from plinth.modules.security import set_restricted_access
from plinth.utils import is_user_admin from plinth.utils import is_user_admin
from plinth.models import UserProfile
def get_group_choices(): def get_group_choices():
@ -72,7 +75,7 @@ class ValidNewUsernameCheckMixin(object):
return True return True
class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm): class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm, plinthForms.LanguageSelectionForm):
"""Custom user create form. """Custom user create form.
Include options to add user to groups. Include options to add user to groups.
@ -89,6 +92,9 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm):
'log in to the system through SSH and have ' 'log in to the system through SSH and have '
'administrative privileges (sudo).')) 'administrative privileges (sudo).'))
class Meta(UserCreationForm.Meta):
fields = ('username', 'password1', 'password2', 'groups', 'language')
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
"""Initialize the form with extra request argument.""" """Initialize the form with extra request argument."""
self.request = request self.request = request
@ -98,6 +104,7 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm):
def save(self, commit=True): def save(self, commit=True):
"""Save the user model and create LDAP user if required.""" """Save the user model and create LDAP user if required."""
user = super(CreateUserForm, self).save(commit) user = super(CreateUserForm, self).save(commit)
UserProfile(user=user, preferred_language=self.cleaned_data['language']).save()
if commit: if commit:
try: try:
@ -125,7 +132,7 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm):
return user return user
class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm): class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm, plinthForms.LanguageSelectionForm):
"""When user info is changed, also updates LDAP user.""" """When user info is changed, also updates LDAP user."""
ssh_keys = forms.CharField( ssh_keys = forms.CharField(
label=ugettext_lazy('SSH Keys'), label=ugettext_lazy('SSH Keys'),
@ -138,7 +145,7 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
class Meta: class Meta:
"""Metadata to control automatic form building.""" """Metadata to control automatic form building."""
fields = ('username', 'groups', 'ssh_keys', 'is_active') fields = ('username', 'groups', 'ssh_keys', 'language', 'is_active')
model = User model = User
widgets = { widgets = {
'groups': forms.widgets.CheckboxSelectMultiple(), 'groups': forms.widgets.CheckboxSelectMultiple(),
@ -172,7 +179,14 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
def save(self, commit=True): def save(self, commit=True):
"""Update LDAP user name and groups after saving user model.""" """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: if commit:
output = actions.superuser_run('users', output = actions.superuser_run('users',
@ -259,6 +273,7 @@ class FirstBootForm(ValidNewUsernameCheckMixin, auth.forms.UserCreationForm):
def save(self, commit=True): def save(self, commit=True):
"""Create and log the user in.""" """Create and log the user in."""
user = super().save(commit=commit) user = super().save(commit=commit)
UserProfile(user=user).save()
if commit: if commit:
first_boot.mark_step_done('users_firstboot') first_boot.mark_step_done('users_firstboot')

View File

@ -104,6 +104,8 @@ class UserUpdate(ContextMixin, SuccessMessageMixin, UpdateView):
ssh_keys = actions.superuser_run( ssh_keys = actions.superuser_run(
'ssh', ['get-keys', '--username', self.object.username]) 'ssh', ['get-keys', '--username', self.object.username])
initial['ssh_keys'] = ssh_keys.strip() 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: except ActionError:
pass pass

View File

@ -82,9 +82,12 @@ class LanguageSelectionView(FormView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
selected_language = form.cleaned_data['language'] 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) translation.activate(selected_language)
# set selected language in session
request.session[translation.LANGUAGE_SESSION_KEY] = selected_language
response = HttpResponseRedirect(reverse('language-selection')) response = HttpResponseRedirect(reverse('language-selection'))
# send a cookie for selected language # send a cookie for selected language
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, selected_language) response.set_cookie(settings.LANGUAGE_COOKIE_NAME, selected_language)