diff --git a/src/requirements.txt b/src/requirements.txt index 4af31dbad9..2572b5d91d 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,5 @@ Django>=1.7 +pytz django-bootstrap3 django-compressor BeautifulSoup4 diff --git a/src/setup.cfg b/src/setup.cfg index ff9ae58768..d9c39f6606 100644 --- a/src/setup.cfg +++ b/src/setup.cfg @@ -2,4 +2,4 @@ ignore = E128 max-line-length = 160 exclude = tests,migrations,.ropeproject,static -max-complexity = 12 +max-complexity = 16 diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 56647f277b..8cc507c94a 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -45,11 +45,11 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'tixlbase.middleware.LocaleMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'tixlcontrol.middleware.PermissionMiddleware', @@ -85,7 +85,7 @@ DATABASES = { # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'en' TIME_ZONE = 'UTC' diff --git a/src/tixlbase/admin.py b/src/tixlbase/admin.py index d9e5fcae4f..80d826ad6f 100644 --- a/src/tixlbase/admin.py +++ b/src/tixlbase/admin.py @@ -50,6 +50,7 @@ class TixlUserAdmin(UserAdmin): fieldsets = ( (None, {'fields': ('identifier', 'event', 'username', 'password')}), (_('Personal info'), {'fields': ('familyname', 'givenname', 'email')}), + (_('Locale'), {'fields': ('locale', 'timezone')}), (_('Permissions'), {'fields': ('is_active', 'is_staff', 'groups', 'user_permissions')}), ) diff --git a/src/tixlbase/middleware.py b/src/tixlbase/middleware.py new file mode 100644 index 0000000000..8d8fd99db8 --- /dev/null +++ b/src/tixlbase/middleware.py @@ -0,0 +1,118 @@ +import pytz + +from django.conf import settings +from django.core.urlresolvers import resolve +from django.middleware.locale import LocaleMiddleware as BaseLocaleMiddleware +from django.utils.translation.trans_real import ( + get_supported_language_variant, + parse_accept_lang_header, + language_code_re, + check_for_language, + _supported +) +from django.utils.translation import LANGUAGE_SESSION_KEY +from django.utils import translation, timezone +from collections import OrderedDict +from django.utils.cache import patch_vary_headers + +from tixlbase.models import Event + + +class LocaleMiddleware(BaseLocaleMiddleware): + + """ + This middleware sets the correct locale and timezone + for a request. + """ + + def process_request(self, request): + url = resolve(request.path_info) + if 'event' in url.kwargs and 'organizer' in url.kwargs: + try: + request.event = Event.objects.get( + slug=url.kwargs['event'], + organizer__slug=url.kwargs['organizer'], + ) + except Event.DoesNotExist: + pass + + language = get_language_from_request(request) + translation.activate(language) + request.LANGUAGE_CODE = translation.get_language() + + tzname = None + if request.user.is_authenticated(): + tzname = request.user.timezone + if hasattr(request, 'event'): + tzname = request.event.timezone + if tzname: + try: + timezone.activate(pytz.timezone(tzname)) + except pytz.UnknownTimeZoneError: + pass + else: + timezone.deactivate() + + def process_response(self, request, response): + language = translation.get_language() + patch_vary_headers(response, ('Accept-Language',)) + if 'Content-Language' not in response: + response['Content-Language'] = language + return response + + +def get_language_from_request(request): + """ + Analyzes the request to find what language the user wants the system to + show. Only languages listed in settings.LANGUAGES are taken into account. + If the user requests a sublanguage where we have a main language, we send + out the main language. + """ + global _supported + if _supported is None: + _supported = OrderedDict(settings.LANGUAGES) + + # Priority 1: User settings + if request.user.is_authenticated(): + lang_code = request.user.locale + if lang_code in _supported and lang_code is not None and check_for_language(lang_code): + return lang_code + + # Priority 2: Anonymous user settings (session, cookie) + if hasattr(request, 'session'): + lang_code = request.session.get(LANGUAGE_SESSION_KEY) + if lang_code in _supported and lang_code is not None and check_for_language(lang_code): + return lang_code + + lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME) + try: + return get_supported_language_variant(lang_code) + except LookupError: + pass + + # Priority 3: Event default + if hassattr(request, 'event'): + lang_code = request.event.locale + try: + return get_supported_language_variant(lang_code) + except LookupError: + pass + + # Priority 4: Browser default + accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '') + for accept_lang, unused in parse_accept_lang_header(accept): + if accept_lang == '*': + break + + if not language_code_re.search(accept_lang): + continue + + try: + return get_supported_language_variant(accept_lang) + except LookupError: + continue + + try: + return get_supported_language_variant(settings.LANGUAGE_CODE) + except LookupError: + return settings.LANGUAGE_CODE diff --git a/src/tixlbase/migrations/0007_auto_20140914_1301.py b/src/tixlbase/migrations/0007_auto_20140914_1301.py new file mode 100644 index 0000000000..2f12faca70 --- /dev/null +++ b/src/tixlbase/migrations/0007_auto_20140914_1301.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0006_auto_20140912_1855'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='locale', + field=models.CharField(choices=[('de', 'German'), ('en', 'English')], max_length=50, default='en'), + preserve_default=True, + ), + migrations.AddField( + model_name='user', + name='timezone', + field=models.CharField(max_length=100, default='UTC'), + preserve_default=True, + ), + ] diff --git a/src/tixlbase/migrations/0008_auto_20140914_1304.py b/src/tixlbase/migrations/0008_auto_20140914_1304.py new file mode 100644 index 0000000000..6f75d80519 --- /dev/null +++ b/src/tixlbase/migrations/0008_auto_20140914_1304.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0007_auto_20140914_1301'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='timezone', + field=models.CharField(max_length=100, default='UTC', verbose_name='Default timezone'), + preserve_default=True, + ), + migrations.AlterField( + model_name='user', + name='locale', + field=models.CharField(max_length=50, verbose_name='Language', default='en', choices=[('de', 'German'), ('en', 'English')]), + ), + migrations.AlterField( + model_name='user', + name='timezone', + field=models.CharField(max_length=100, default='UTC', verbose_name='Timezone'), + ), + ] diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index 06a87841aa..1d5099a965 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -83,6 +83,13 @@ class User(AbstractBaseUser, PermissionsMixin): verbose_name=('Is site admin')) date_joined = models.DateTimeField(auto_now_add=True, verbose_name=_('Date joined')) + locale = models.CharField(max_length=50, + choices=settings.LANGUAGES, + default=settings.LANGUAGE_CODE, + verbose_name=_('Language')) + timezone = models.CharField(max_length=100, + default=settings.TIME_ZONE, + verbose_name=('Timezone')) objects = UserManager() @@ -215,6 +222,9 @@ class Event(models.Model): locale = models.CharField(max_length=10, choices=settings.LANGUAGES, verbose_name=_("Default locale")) + timezone = models.CharField(max_length=100, + default=settings.TIME_ZONE, + verbose_name=_('Default timezone')) currency = models.CharField(max_length=10, verbose_name=_("Default currency")) date_from = models.DateTimeField(verbose_name=_("Event start time"))