Implement GUI language and timezone as user preferences, issue #114

This commit is contained in:
Roberto Rosario
2015-01-11 17:38:47 -04:00
parent 305c1aaa6e
commit 090302676d
11 changed files with 311 additions and 18 deletions

View File

@@ -3,27 +3,33 @@ from __future__ import absolute_import
import logging
import tempfile
from django.conf import settings
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
from south.signals import post_migrate
from navigation.api import register_links, register_top_menu
from common import settings as common_settings
from .links import (link_about, link_current_user_details,
link_current_user_edit, link_license,
link_current_user_edit,
link_current_user_locale_profile_details,
link_current_user_locale_profile_edit, link_license,
link_password_change)
from .models import AnonymousUserSingleton, AutoAdminSingleton
from .models import (AnonymousUserSingleton, AutoAdminSingleton,
UserLocaleProfile)
from .settings import (AUTO_ADMIN_USERNAME, AUTO_ADMIN_PASSWORD,
AUTO_CREATE_ADMIN, TEMPORARY_DIRECTORY)
from .utils import validate_path
logger = logging.getLogger(__name__)
register_links(['common:current_user_details', 'common:current_user_edit', 'common:password_change_view'], [link_current_user_details, link_current_user_edit, link_password_change], menu_name='secondary_menu')
register_links(['common:current_user_details', 'common:current_user_edit', 'common:current_user_locale_profile_details', 'common:current_user_locale_profile_edit', 'common:password_change_view'], [link_current_user_details, link_current_user_edit, link_current_user_locale_profile_details, link_current_user_locale_profile_edit, link_password_change], menu_name='secondary_menu')
register_links(['common:about_view', 'common:license_view', 'registration:form_view'], [link_about, link_license], menu_name='secondary_menu')
register_top_menu('about', link_about, position=-1)
@@ -73,5 +79,21 @@ def auto_admin_account_passwd_change(sender, instance, **kwargs):
auto_admin_properties.save()
@receiver(user_logged_in, dispatch_uid='user_locale_profile_session_config', sender=User)
def user_locale_profile_session_config(sender, request, user, **kwargs):
if hasattr(request, 'session'):
user_locale_profile, created = UserLocaleProfile.objects.get_or_create(user=user)
request.session['django_language'] = user_locale_profile.language
request.session['django_timezone'] = user_locale_profile.timezone
else:
request.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_locale_profile.language)
@receiver(post_save, dispatch_uid='user_locale_profile_create', sender=User)
def user_locale_profile_create(sender, instance, created, **kwargs):
if created:
UserLocaleProfile.objects.create(user=instance)
if (not validate_path(TEMPORARY_DIRECTORY)) or (not TEMPORARY_DIRECTORY):
setattr(common_settings, 'TEMPORARY_DIRECTORY', tempfile.mkdtemp())

View File

@@ -11,6 +11,7 @@ from django.db import models
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from .models import UserLocaleProfile
from .utils import return_attrib
from .widgets import DetailSelectMultiple, EmailInput, PlainWidget
@@ -107,6 +108,7 @@ class UserForm_view(DetailForm):
"""
Form used to display an user's public details
"""
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'is_staff', 'is_superuser', 'last_login', 'date_joined', 'groups')
@@ -116,11 +118,24 @@ class UserForm(forms.ModelForm):
"""
Form used to edit an user's mininal fields by the user himself
"""
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class LocaleProfileForm(forms.ModelForm):
class Meta:
model = UserLocaleProfile
fields = ('language', 'timezone')
class LocaleProfileForm_view(DetailForm):
class Meta:
model = UserLocaleProfile
fields = ('language', 'timezone')
class EmailAuthenticationForm(forms.Form):
"""
A form to use email address authentication

View File

@@ -13,3 +13,6 @@ link_current_user_edit = {'text': _(u'Edit details'), 'view': 'common:current_us
link_about = {'text': _(u'About'), 'view': 'common:about_view', 'famfam': 'information'}
link_license = {'text': _(u'License'), 'view': 'common:license_view', 'famfam': 'script'}
link_current_user_locale_profile_details = {'text': _(u'Locale profile'), 'view': 'common:current_user_locale_profile_details', 'famfam': 'world'}
link_current_user_locale_profile_edit = {'text': _(u'Edit locale profile'), 'view': 'common:current_user_locale_profile_edit', 'famfam': 'world_edit'}

View File

@@ -0,0 +1,16 @@
import pytz
from django.utils import timezone
class TimezoneMiddleware(object):
def process_request(self, request):
if hasattr(request, 'session'):
tzname = request.session.get('django_timezone')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
timezone.deactivate()
else:
# TODO: Cookie, browser based timezone
timezone.deactivate()

View File

@@ -1,9 +1,12 @@
from __future__ import absolute_import
from pytz import common_timezones
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from solo.models import SingletonModel
@@ -52,3 +55,17 @@ class SharedUploadedFile(models.Model):
def delete(self, *args, **kwargs):
self.file.storage.delete(self.file.path)
return super(SharedUploadedFile, self).delete(*args, **kwargs)
class UserLocaleProfile(models.Model):
user = models.OneToOneField(User, related_name='locale_profile', verbose_name=_('User'))
timezone = models.CharField(choices=zip(common_timezones, common_timezones), max_length=48, verbose_name=_('Timezone'))
language = models.CharField(choices=settings.LANGUAGES, max_length=8, verbose_name=_('Language'))
def __unicode__(self):
return unicode(self.user)
class Meta:
verbose_name = _(u'User locale profile')
verbose_name_plural = _(u'User locale profiles')

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'UserLocaleProfile'
db.create_table(u'common_userlocaleprofile', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
('timezone', self.gf('django.db.models.fields.CharField')(max_length=48)),
('language', self.gf('django.db.models.fields.CharField')(max_length=8)),
))
db.send_create_signal(u'common', ['UserLocaleProfile'])
def backwards(self, orm):
# Deleting model 'UserLocaleProfile'
db.delete_table(u'common_userlocaleprofile')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'common.anonymoususersingleton': {
'Meta': {'object_name': 'AnonymousUserSingleton'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'common.autoadminsingleton': {
'Meta': {'object_name': 'AutoAdminSingleton'},
'account': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'auto_admin_account'", 'null': 'True', 'to': u"orm['auth.User']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
'password_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
},
u'common.shareduploadedfile': {
'Meta': {'object_name': 'SharedUploadedFile'},
'datatime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'common.userlocaleprofile': {
'Meta': {'object_name': 'UserLocaleProfile'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '8'}),
'timezone': ('django.db.models.fields.CharField', [], {'max_length': '48'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['common']

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
# Note: Don't use "from appname.models import ModelName".
# Use orm.ModelName to refer to models in this application,
# and orm['appname.ModelName'] for models in other applications.
for user in orm['auth.user'].objects.all():
try:
orm.UserLocaleProfile.objects.create(user=user)
except Exception:
pass
def backwards(self, orm):
"Write your backwards methods here."
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'common.anonymoususersingleton': {
'Meta': {'object_name': 'AnonymousUserSingleton'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'common.autoadminsingleton': {
'Meta': {'object_name': 'AutoAdminSingleton'},
'account': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'auto_admin_account'", 'null': 'True', 'to': u"orm['auth.User']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
'password_hash': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'})
},
u'common.shareduploadedfile': {
'Meta': {'object_name': 'SharedUploadedFile'},
'datatime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'common.userlocaleprofile': {
'Meta': {'object_name': 'UserLocaleProfile'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '8'}),
'timezone': ('django.db.models.fields.CharField', [], {'max_length': '48'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'locale_profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['common', 'auth', 'common']
symmetrical = True

View File

@@ -12,6 +12,9 @@ urlpatterns = patterns('common.views',
url(r'^user/$', 'current_user_details', (), name='current_user_details'),
url(r'^user/edit/$', 'current_user_edit', (), name='current_user_edit'),
url(r'^user/locale/$', 'current_user_locale_profile_details', (), name='current_user_locale_profile_details'),
url(r'^user/locale/edit/$', 'current_user_locale_profile_edit', (), name='current_user_locale_profile_edit'),
url(r'^login/$', 'login_view', (), name='login_view'),
url(r'^password/change/$', 'password_change_view', (), name='password_change_view'),
)

View File

@@ -22,7 +22,8 @@ from acls.models import AccessEntry
from permissions.models import Permission
from .forms import (ChoiceForm, EmailAuthenticationForm, LicenseForm,
UserForm, UserForm_view)
LocaleProfileForm, LocaleProfileForm_view, UserForm,
UserForm_view)
from .settings import LOGIN_METHOD
@@ -173,6 +174,21 @@ def current_user_details(request):
context_instance=RequestContext(request))
def current_user_locale_profile_details(request):
"""
Display the current user's locale profile details
"""
form = LocaleProfileForm_view(instance=request.user.locale_profile)
return render_to_response(
'main/generic_form.html', {
'form': form,
'title': _(u'Current user locale profile details'),
'read_only': True,
},
context_instance=RequestContext(request))
def current_user_edit(request):
"""
Allow an user to edit his own details
@@ -201,6 +217,38 @@ def current_user_edit(request):
context_instance=RequestContext(request))
def current_user_locale_profile_edit(request):
"""
Allow an user to edit his own locale profile
"""
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', reverse('common:current_user_locale_profile_details'))))
if request.method == 'POST':
form = LocaleProfileForm(instance=request.user.locale_profile, data=request.POST)
if form.is_valid():
form.save()
if hasattr(request, 'session'):
request.session['django_language'] = form.cleaned_data['language']
request.session['django_timezone'] = form.cleaned_data['timezone']
else:
request.set_cookie(settings.LANGUAGE_COOKIE_NAME, form.cleaned_data['language'])
messages.success(request, _(u'Current user\'s locale profile details updated.'))
return HttpResponseRedirect(next)
else:
form = LocaleProfileForm(instance=request.user.locale_profile)
return render_to_response(
'main/generic_form.html', {
'form': form,
'next': next,
'title': _(u'Edit current user locale profile details'),
},
context_instance=RequestContext(request))
def login_view(request):
"""
Control how the use is to be authenticated, options are 'email' and

View File

@@ -214,7 +214,7 @@
</div>
<div id="user-navigation">
<ul class="wat-cf">
<li><strong>{% trans 'User' %}: </strong>
<li>
{% if not user.is_authenticated %}
{% trans 'Anonymous' %}
{% else %}
@@ -222,18 +222,6 @@
{% endif %}
</li>
{% get_setting "MIDDLEWARE_CLASSES" as middleware_classes %}
{% if 'django.middleware.locale.LocaleMiddleware' in middleware_classes %}
<li>
<form action="{% url 'common:set_language' %}" method="post">{% csrf_token %}
<select name="language" onchange="this.form.submit()">
{% for lang in LANGUAGES %}
<option value="{{ lang.0 }}" {% if lang.0 == LANGUAGE_CODE %}selected="selected"{% endif %}>{{ lang.0|language_name_local }}</option>
{% endfor %}
</select>
</form>
</li>
{% endif %}
{% get_setting "LOGIN_URL" as login_url %}
<li><a class="logout" href="{% if not user.is_authenticated %}{% url 'common:login_view' %}?next=/{% else %}{% url 'common:logout_view' %}{% endif %}">{% if not user.is_authenticated %}{% trans 'Login' %}{% else %}{% trans 'Logout' %}{% endif %}</a></li>
</ul>

View File

@@ -109,6 +109,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'common.middleware.timezone.TimezoneMiddleware',
'common.middleware.strip_spaces_widdleware.SpacelessMiddleware',
'common.middleware.login_required_middleware.LoginRequiredMiddleware',
'permissions.middleware.permission_denied_middleware.PermissionDeniedMiddleware',