diff --git a/CHANGELOG b/CHANGELOG index 3f4d57e98e..e707b61395 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Store default language in database * Add icon attribute on fields * Prevent Wizard State name to start with '_' * Add completion attribute for Many2One, Many2Many and One2Many diff --git a/etc/trytond.conf b/etc/trytond.conf index 14a037c9aa..aea2683979 100644 --- a/etc/trytond.conf +++ b/etc/trytond.conf @@ -83,7 +83,7 @@ jsonrpc = localhost:8000 # Number of retries on database operational error # retry = 5 -# Default database language code +# Default language code # language = en_US # Timezone of the server diff --git a/trytond/backend/mysql/init.sql b/trytond/backend/mysql/init.sql index 797b6a80c1..bcd83fd80e 100644 --- a/trytond/backend/mysql/init.sql +++ b/trytond/backend/mysql/init.sql @@ -1,3 +1,9 @@ +CREATE TABLE ir_configuration ( + id BIGINT AUTO_INCREMENT NOT NULL, + language VARCHAR(255), + PRIMARY KEY(id) +) ENGINE=InnoDB; + CREATE TABLE ir_model ( id BIGINT AUTO_INCREMENT NOT NULL, model VARCHAR(255) NOT NULL, diff --git a/trytond/backend/postgresql/init.sql b/trytond/backend/postgresql/init.sql index a82b3f201a..13fbc750e7 100644 --- a/trytond/backend/postgresql/init.sql +++ b/trytond/backend/postgresql/init.sql @@ -1,3 +1,11 @@ +CREATE SEQUENCE ir_configuration_id_seq; + +CREATE TABLE ir_configuration ( + id INTEGER DEFAULT NEXTVAL('ir_configuration_id_seq') NOT NULL, + language VARCHAR, + PRIMARY KEY(id) +); + CREATE SEQUENCE ir_model_id_seq; CREATE TABLE ir_model ( diff --git a/trytond/backend/sqlite/init.sql b/trytond/backend/sqlite/init.sql index 52f6239520..188abe85b5 100644 --- a/trytond/backend/sqlite/init.sql +++ b/trytond/backend/sqlite/init.sql @@ -1,3 +1,8 @@ +CREATE TABLE ir_configuration ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + language VARCHAR +); + CREATE TABLE ir_model ( id INTEGER PRIMARY KEY AUTOINCREMENT, model VARCHAR, diff --git a/trytond/ir/__init__.py b/trytond/ir/__init__.py index 355e46a65e..ed3c7db7b4 100644 --- a/trytond/ir/__init__.py +++ b/trytond/ir/__init__.py @@ -1,6 +1,7 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. from ..pool import Pool +from .configuration import * from .translation import * from .sequence import * from .ui.menu import * @@ -23,6 +24,7 @@ from .session import * def register(): Pool.register( + Configuration, Translation, ReportTranslationSetStart, ReportTranslationSetSucceed, diff --git a/trytond/ir/configuration.py b/trytond/ir/configuration.py new file mode 100644 index 0000000000..abe72db70e --- /dev/null +++ b/trytond/ir/configuration.py @@ -0,0 +1,30 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +from ..model import ModelSQL, ModelSingleton, fields +from ..cache import Cache +from ..config import CONFIG + +__all__ = ['Configuration'] + + +class Configuration(ModelSingleton, ModelSQL): + 'Configuration' + __name__ = 'ir.configuration' + language = fields.Char('language') + _get_language_cache = Cache('ir_configuration.get_language') + + @staticmethod + def default_language(): + return CONFIG['language'] + + @classmethod + def get_language(cls): + language = cls._get_language_cache.get(None) + if language is not None: + return language + config = cls(1) + language = config.language + if not language: + language = CONFIG['language'] + cls._get_language_cache.set(None, language) + return language diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py index 8374249fc7..a03002d4d1 100644 --- a/trytond/ir/cron.py +++ b/trytond/ir/cron.py @@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta import traceback import sys import logging -from ..config import CONFIG + from ..model import ModelView, ModelSQL, fields from ..tools import safe_eval from ..transaction import Transaction @@ -147,6 +147,7 @@ class Cron(ModelSQL, ModelView): @classmethod def _callback(cls, cron): pool = Pool() + Config = pool.get('ir.configuration') try: args = (cron.args or []) and safe_eval(cron.args) Model = pool.get(cron.model) @@ -158,7 +159,7 @@ class Cron(ModelSQL, ModelView): Request = pool.get('res.request') req_user = cron.request_user language = (req_user.language.code if req_user.language - else CONFIG['language']) + else Config.get_language()) with contextlib.nested(Transaction().set_user(cron.user.id), Transaction().set_context(language=language)): values = cls._get_request_values(cron) diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index 881c47d048..6dc890a2b2 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -8,7 +8,6 @@ from ..cache import Cache from ..tools import safe_eval, datetime_strftime from ..transaction import Transaction from ..pool import Pool -from ..config import CONFIG from .time_locale import TIME_LOCALE warnings.filterwarnings('ignore', "", ImportWarning) @@ -49,6 +48,7 @@ class Lang(ModelSQL, ModelView): cls._constraints += [ ('check_grouping', 'invalid_grouping'), ('check_date', 'invalid_date'), + ('check_translatable', 'default_translatable'), ] cls._sql_constraints += [ ('check_decimal_point_thousands_sep', @@ -58,6 +58,9 @@ class Lang(ModelSQL, ModelView): cls._error_messages.update({ 'invalid_grouping': 'Invalid Grouping!', 'invalid_date': 'The date format is not valid!', + 'default_translatable': + 'The default language must be translatable', + 'delete_default': 'Default language can not be deleted', }) @classmethod @@ -72,11 +75,12 @@ class Lang(ModelSQL, ModelView): def read(cls, ids, fields_names=None): pool = Pool() Translation = pool.get('ir.translation') + Config = pool.get('ir.configuration') res = super(Lang, cls).read(ids, fields_names=fields_names) if (Transaction().context.get('translate_name') and (not fields_names or 'name' in fields_names)): with Transaction().set_context( - language=CONFIG['language'], + language=Config.get_language(), translate_name=False): res2 = cls.read(ids, fields_names=['id', 'code', 'name']) for record2 in res2: @@ -162,6 +166,20 @@ class Lang(ModelSQL, ModelView): return False return True + @classmethod + def check_translatable(cls, langs): + pool = Pool() + Config = pool.get('ir.configuration') + # Skip check for root because when languages are created from XML file, + # translatable is not yet set. + if Transaction().user == 0: + return True + for lang in langs: + if (lang.code == Config.get_language() + and not lang.translatable): + return False + return True + @staticmethod def check_xml_record(langs, values): return True @@ -191,6 +209,11 @@ class Lang(ModelSQL, ModelView): @classmethod def delete(cls, langs): + pool = Pool() + Config = pool.get('ir.configuration') + for lang in langs: + if lang.code == Config.get_language(): + cls.raise_user_error('delete_default') # Clear cache cls._lang_cache.clear() super(Lang, cls).delete(langs) diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 6f36d687cb..b488ebc08f 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -15,7 +15,7 @@ try: except ImportError: from md5 import md5 from functools import reduce -from ..config import CONFIG + from ..model import ModelView, ModelSQL, fields from ..wizard import Wizard, StateView, StateTransition, StateAction, \ Button @@ -251,6 +251,7 @@ class Translation(ModelSQL, ModelView): pool = Pool() ModelFields = pool.get('ir.model.field') Model = pool.get('ir.model') + Config = pool.get('ir.configuration') model_name, field_name = name.split(',') if model_name in ('ir.model.field', 'ir.model'): @@ -291,7 +292,7 @@ class Translation(ModelSQL, ModelView): }) return len(ids) Model = pool.get(model_name) - with Transaction().set_context(language=CONFIG['language']): + with Transaction().set_context(language=Config.get_language()): records = Model.browse(ids) for record in records: translation2 = cls.search([ @@ -317,7 +318,7 @@ class Translation(ModelSQL, ModelView): 'src': getattr(record, field_name), 'fuzzy': False, }) - if (lang == CONFIG['language'] + if (lang == Config.get_language() and Transaction().context.get('fuzzy_translation', True)): other_langs = cls.search([ @@ -610,6 +611,7 @@ class Translation(ModelSQL, ModelView): def translation_export(cls, lang, module): pool = Pool() ModelData = pool.get('ir.model.data') + Config = pool.get('ir.configuration') models_data = ModelData.search([ ('module', '=', module), @@ -624,7 +626,7 @@ class Translation(ModelSQL, ModelView): 'Content-Type': 'text/plain; charset=utf-8', } - with Transaction().set_context(language=CONFIG['language']): + with Transaction().set_context(language=Config.get_language()): translations = cls.search([ ('lang', '=', lang), ('module', '=', module), diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 2052d347d9..e871a26cb7 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -6,7 +6,7 @@ import re from functools import reduce from decimal import Decimal from itertools import islice -from trytond.config import CONFIG + from trytond.model import ModelStorage from trytond.model import fields from trytond.backend import FIELDS, TableHandler @@ -707,6 +707,7 @@ class ModelSQL(ModelStorage): cursor = Transaction().cursor pool = Pool() Translation = pool.get('ir.translation') + Config = pool.get('ir.configuration') ids = map(int, records) # Call before cursor cache cleaning @@ -777,7 +778,8 @@ class ModelSQL(ModelStorage): if field in cls._fields: if not hasattr(cls._fields[field], 'set'): if ((not getattr(cls._fields[field], 'translate', False)) - or (Transaction().language == CONFIG['language'])): + or (Transaction().language + == Config.get_language())): upd0.append(('"' + field + '"', '%s')) upd1.append(FIELDS[cls._fields[field]._type]\ .sql_format(values[field])) diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 90007f119a..1779eeac20 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -229,6 +229,7 @@ class ModelStorage(Model): ''' pool = Pool() Lang = pool.get('ir.lang') + Config = pool.get('ir.configuration') if default is None: default = {} @@ -307,9 +308,6 @@ class ModelStorage(Model): ('translatable', '=', True), ]) if langs: - langs += Lang.search([ - ('code', '=', CONFIG['language']), - ]) for lang in langs: # Prevent fuzzing translations when copying as the terms # should be the same. diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py index de37e16fc7..4a5abbf19f 100644 --- a/trytond/protocols/dispatcher.py +++ b/trytond/protocols/dispatcher.py @@ -217,6 +217,8 @@ def create(database_name, password, lang, admin_password): with Transaction().start(database_name, 0) as transaction: database.init(transaction.cursor) + transaction.cursor.execute('INSERT INTO ir_configuration ' + '(language) VALUES (%s)', (lang,)) transaction.cursor.commit() pool = Pool(database_name) diff --git a/trytond/report/report.py b/trytond/report/report.py index 329dca405b..c6478a86b5 100644 --- a/trytond/report/report.py +++ b/trytond/report/report.py @@ -314,6 +314,7 @@ class Report(URLMixin): date=False, currency=None, symbol=True): pool = Pool() Lang = pool.get('ir.lang') + Config = pool.get('ir.configuration') if date: if lang: @@ -321,7 +322,7 @@ class Report(URLMixin): code = lang.code else: locale_format = Lang.default_date() - code = CONFIG['language'] + code = Config.get_language() if not isinstance(value, time.struct_time): # assume string, parse it if len(str(value)) == 10: diff --git a/trytond/res/user.py b/trytond/res/user.py index 6825a4416b..f89e2ce90b 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -56,7 +56,6 @@ class User(ModelSQL, ModelView): language = fields.Many2One('ir.lang', 'Language', domain=['OR', ('translatable', '=', True), - ('code', '=', CONFIG['language']), ]) language_direction = fields.Function(fields.Char('Language Direction'), 'get_language_direction') @@ -282,6 +281,7 @@ class User(ModelSQL, ModelView): ModelData = pool.get('ir.model.data') Action = pool.get('ir.action') ConfigItem = pool.get('ir.module.module.config_wizard.item') + Config = pool.get('ir.configuration') res = {} if context_only: @@ -294,7 +294,7 @@ class User(ModelSQL, ModelView): if user.language: res['language'] = user.language.code else: - res['language'] = CONFIG['language'] + res['language'] = Config.get_language() else: res[field] = None if getattr(user, field): @@ -396,10 +396,7 @@ class User(ModelSQL, ModelView): if 'language' in res['fields']: selection = convert2selection(res['fields'], 'language') - langs = Lang.search(['OR', - ('translatable', '=', True), - ('code', '=', CONFIG['language']), - ]) + langs = Lang.search(cls.language.domain) lang_ids = [l.id for l in langs] with Transaction().set_context(translate_name=True): for lang in Lang.browse(lang_ids): diff --git a/trytond/transaction.py b/trytond/transaction.py index 39cd3703cc..b2c27833d5 100644 --- a/trytond/transaction.py +++ b/trytond/transaction.py @@ -3,7 +3,6 @@ from threading import local from trytond.tools.singleton import Singleton from trytond.backend import Database -from trytond.config import CONFIG class _TransactionManager(object): @@ -132,6 +131,10 @@ class Transaction(local): @property def language(self): + def get_language(): + from trytond.pool import Pool + Config = Pool().get('ir.configuration') + return Config.get_language() if self.context: - return self.context.get('language') or CONFIG['language'] - return CONFIG['language'] + return self.context.get('language') or get_language() + return get_language()