diff --git a/doc/changelog.rst b/doc/changelog.rst index 59c4a6bced5..352b0919398 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -21,3 +21,14 @@ Changelog ``openerp.exceptions.RedirectWarning``. - Give a pair of new methods to ``res.config.settings`` and a helper to make them easier to use: ``get_config_warning()``. + + +`7.0` +----- + +- Modules may now include an ``i18n_extra`` directory that will be treated like the + default ``i18n`` directory. This is typically useful for manual translation files + that are not managed by Launchpad's translation system. An example is l10n modules + that depend on ``l10n_multilang``. + + diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 165fd8a4be9..437095e5455 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -195,7 +195,8 @@ class ir_model(osv.osv): ctx = dict(context, field_name=vals['name'], field_state='manual', - select=vals.get('select_level', '0')) + select=vals.get('select_level', '0'), + update_custom_fields=True) self.pool[vals['model']]._auto_init(cr, ctx) openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname) return res diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index acb6daa7785..0f9b6ea3886 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -445,6 +445,13 @@ class ir_translation(osv.osv): tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context) context['overwrite'] = True # make sure the requested translation will override the base terms later + # i18n_extra folder is for additional translations handle manually (eg: for l10n_be) + base_trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', base_lang_code + '.po') + if base_trans_extra_file: + _logger.info('module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang) + tools.trans_load(cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context) + context['overwrite'] = True # make sure the requested translation will override the base terms later + # Step 2: then load the main translation file, possibly overriding the terms coming from the base language trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po') if trans_file: @@ -452,6 +459,11 @@ class ir_translation(osv.osv): tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context) elif lang_code != 'en_US': _logger.warning('module %s: no translation for language %s', module_name, lang_code) + + trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', lang_code + '.po') + if trans_extra_file: + _logger.info('module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang) + tools.trans_load(cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context) return True diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 1d666f2f5c8..e40f6eb5c3a 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -100,7 +100,7 @@ class view(osv.osv): else: inferred_type = etree.fromstring(values['arch'].encode('utf8')).tag values['name'] = "%s %s" % (values['model'], inferred_type) - return super(osv.osv, self).create(cr, uid, values, context) + return super(view, self).create(cr, uid, values, context) def _relaxng(self): if not self._relaxng_validator: @@ -179,7 +179,7 @@ class view(osv.osv): if self.pool._init: # Module init currently in progress, only consider views from modules whose code was already loaded query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) - WHERE v.inherit_id=%s AND v.model=%s AND md.module in %s + WHERE v.inherit_id=%s AND v.model=%s AND (md.module IS NULL or md.module in %s) ORDER BY priority""" query_params = (view_id, model, tuple(self.pool._init_modules)) else: diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index efe33f32fd9..a496a1eccdf 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -30,11 +30,13 @@ from openerp.tools.translate import _ CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?') class res_currency(osv.osv): - def _current_rate(self, cr, uid, ids, name, arg, context=None): - return self._get_current_rate(cr, uid, ids, name, arg, context=context) + return self._get_current_rate(cr, uid, ids, context=context) - def _get_current_rate(self, cr, uid, ids, name, arg, context=None): + def _current_rate_silent(self, cr, uid, ids, name, arg, context=None): + return self._get_current_rate(cr, uid, ids, raise_on_no_rate=False, context=context) + + def _get_current_rate(self, cr, uid, ids, raise_on_no_rate=True, context=None): if context is None: context = {} res = {} @@ -52,9 +54,12 @@ class res_currency(osv.osv): if cr.rowcount: id, rate = cr.fetchall()[0] res[id] = rate + elif not raise_on_no_rate: + res[id] = 0 else: raise osv.except_osv(_('Error!'),_("No currency rate associated for currency %d for the given period" % (id))) return res + _name = "res.currency" _description = "Currency" _columns = { @@ -63,6 +68,10 @@ class res_currency(osv.osv): 'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."), 'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6), help='The rate of the currency to the currency of rate 1.'), + + # Do not use for computation ! Same as rate field with silent failing + 'rate_silent': fields.function(_current_rate_silent, string='Current Rate', digits=(12,6), + help='The rate of the currency to the currency of rate 1 (0 if no rate defined).'), 'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'), 'accuracy': fields.integer('Computational Accuracy'), 'rounding': fields.float('Rounding Factor', digits=(12,6)), diff --git a/openerp/addons/base/res/res_currency_view.xml b/openerp/addons/base/res/res_currency_view.xml index 907eec866fb..c74b677ea72 100644 --- a/openerp/addons/base/res/res_currency_view.xml +++ b/openerp/addons/base/res/res_currency_view.xml @@ -22,7 +22,7 @@ - + @@ -37,7 +37,7 @@
- + diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index ef02882422c..18a1e9508f5 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -398,7 +398,9 @@ def is_leaf(element, internal=False): INTERNAL_OPS += ('inselect',) return (isinstance(element, tuple) or isinstance(element, list)) \ and len(element) == 3 \ - and element[1] in INTERNAL_OPS + and element[1] in INTERNAL_OPS \ + and ((isinstance(element[0], basestring) and element[0]) + or element in (TRUE_LEAF, FALSE_LEAF)) # -------------------------------------------------- diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index caada0906eb..6e3a6277384 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -380,16 +380,18 @@ class browse_record(object): raise KeyError(error_msg) # if the field is a classic one or a many2one, we'll fetch all classic and many2one fields - if col._prefetch: + if col._prefetch and not col.groups: # gen the list of "local" (ie not inherited) fields which are classic or many2one - fields_to_fetch = filter(lambda x: x[1]._classic_write and x[1]._prefetch, self._table._columns.items()) + field_filter = lambda x: x[1]._classic_write and x[1]._prefetch and not x[1].groups + fields_to_fetch = filter(field_filter, self._table._columns.items()) # gen the list of inherited fields inherits = map(lambda x: (x[0], x[1][2]), self._table._inherit_fields.items()) # complete the field list with the inherited fields which are classic or many2one - fields_to_fetch += filter(lambda x: x[1]._classic_write and x[1]._prefetch, inherits) + fields_to_fetch += filter(field_filter, inherits) # otherwise we fetch only that field else: fields_to_fetch = [(name, col)] + ids = filter(lambda id: name not in self._data[id], self._data.keys()) # read the results field_names = map(lambda x: x[0], fields_to_fetch) diff --git a/openerp/report/report_sxw.py b/openerp/report/report_sxw.py index fceb318401b..548a3f833f5 100644 --- a/openerp/report/report_sxw.py +++ b/openerp/report/report_sxw.py @@ -109,7 +109,7 @@ class _date_format(str, _format): if self.val: if getattr(self,'name', None): date = datetime.strptime(self.name[:get_date_length()], DEFAULT_SERVER_DATE_FORMAT) - return date.strftime(str(self.lang_obj.date_format)) + return date.strftime(self.lang_obj.date_format.encode('utf-8')) return self.val class _dttime_format(str, _format): @@ -120,8 +120,8 @@ class _dttime_format(str, _format): def __str__(self): if self.val and getattr(self,'name', None): return datetime.strptime(self.name, DEFAULT_SERVER_DATETIME_FORMAT)\ - .strftime("%s %s"%(str(self.lang_obj.date_format), - str(self.lang_obj.time_format))) + .strftime("%s %s"%((self.lang_obj.date_format).encode('utf-8'), + (self.lang_obj.time_format).encode('utf-8'))) return self.val @@ -313,7 +313,7 @@ class rml_parse(object): date = datetime_field.context_timestamp(self.cr, self.uid, timestamp=date, context=self.localcontext) - return date.strftime(date_format) + return date.strftime(date_format.encode('utf-8')) res = self.lang_dict['lang_obj'].format('%.' + str(digits) + 'f', value, grouping=grouping, monetary=monetary) if currency_obj: diff --git a/openerp/tests/test_acl.py b/openerp/tests/test_acl.py index 1d8d6bfb22a..e80390cd5a2 100644 --- a/openerp/tests/test_acl.py +++ b/openerp/tests/test_acl.py @@ -9,6 +9,7 @@ import common # test group that demo user should not have GROUP_TECHNICAL_FEATURES = 'base.group_no_one' + class TestACL(common.TransactionCase): def setUp(self): @@ -22,25 +23,25 @@ class TestACL(common.TransactionCase): def test_field_visibility_restriction(self): """Check that model-level ``groups`` parameter effectively restricts access to that - field for users who do not belong to one of the explicitly allowed groups""" + field for users who do not belong to one of the explicitly allowed groups""" # Verify the test environment first original_fields = self.res_currency.fields_get(self.cr, self.demo_uid, []) form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form') view_arch = etree.fromstring(form_view.get('arch')) has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES) self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group before the test") - self.assertTrue('rate' in original_fields, "'rate' field must be properly visible before the test") - self.assertNotEquals(view_arch.xpath("//field[@name='rate']"), [], - "Field 'rate' must be found in view definition before the test") + self.assertTrue('accuracy' in original_fields, "'accuracy' field must be properly visible before the test") + self.assertNotEquals(view_arch.xpath("//field[@name='accuracy']"), [], + "Field 'accuracy' must be found in view definition before the test") # Restrict access to the field and check it's gone - self.res_currency._columns['rate'].groups = GROUP_TECHNICAL_FEATURES + self.res_currency._columns['accuracy'].groups = GROUP_TECHNICAL_FEATURES fields = self.res_currency.fields_get(self.cr, self.demo_uid, []) form_view = self.res_currency.fields_view_get(self.cr, self.demo_uid, False, 'form') view_arch = etree.fromstring(form_view.get('arch')) - self.assertFalse('rate' in fields, "'rate' field should be gone") - self.assertEquals(view_arch.xpath("//field[@name='rate']"), [], - "Field 'rate' must not be found in view definition") + self.assertFalse('accuracy' in fields, "'accuracy' field should be gone") + self.assertEquals(view_arch.xpath("//field[@name='accuracy']"), [], + "Field 'accuracy' must not be found in view definition") # Make demo user a member of the restricted group and check that the field is back self.tech_group.write({'users': [(4, self.demo_uid)]}) @@ -50,13 +51,13 @@ class TestACL(common.TransactionCase): view_arch = etree.fromstring(form_view.get('arch')) #import pprint; pprint.pprint(fields); pprint.pprint(form_view) self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group") - self.assertTrue('rate' in fields, "'rate' field must be properly visible again") - self.assertNotEquals(view_arch.xpath("//field[@name='rate']"), [], - "Field 'rate' must be found in view definition again") + self.assertTrue('accuracy' in fields, "'accuracy' field must be properly visible again") + self.assertNotEquals(view_arch.xpath("//field[@name='accuracy']"), [], + "Field 'accuracy' must be found in view definition again") #cleanup self.tech_group.write({'users': [(3, self.demo_uid)]}) - self.res_currency._columns['rate'].groups = False + self.res_currency._columns['accuracy'].groups = False @mute_logger('openerp.osv.orm') def test_field_crud_restriction(self): @@ -65,7 +66,7 @@ class TestACL(common.TransactionCase): has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES) self.assertFalse(has_tech_feat, "`demo` user should not belong to the restricted group") self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids'])) - self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})) + self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})) # Now restrict access to the field and check it's forbidden self.res_partner._columns['bank_ids'].groups = GROUP_TECHNICAL_FEATURES @@ -79,12 +80,30 @@ class TestACL(common.TransactionCase): has_tech_feat = self.res_users.has_group(self.cr, self.demo_uid, GROUP_TECHNICAL_FEATURES) self.assertTrue(has_tech_feat, "`demo` user should now belong to the restricted group") self.assert_(self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids'])) - self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})) - + self.assert_(self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})) + #cleanup self.tech_group.write({'users': [(3, self.demo_uid)]}) self.res_partner._columns['bank_ids'].groups = False + def test_fields_browse_restriction(self): + """Test access to records having restricted fields""" + self.res_partner._columns['email'].groups = GROUP_TECHNICAL_FEATURES + try: + P = self.res_partner + pid = P.search(self.cr, self.demo_uid, [], limit=1)[0] + part = P.browse(self.cr, self.demo_uid, pid) + # accessing fields must no raise exceptions... + part.name + # ... except they are restricted + with self.assertRaises(openerp.osv.orm.except_orm) as cm: + part.email + + self.assertEqual(cm.exception.args[0], 'Access Denied') + finally: + self.res_partner._columns['email'].groups = False + + if __name__ == '__main__': unittest2.main() diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 7a54d6b9b6e..91271ffbda0 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -433,6 +433,7 @@ def get_iso_codes(lang): ALL_LANGUAGES = { 'ab_RU': u'Abkhazian / аҧсуа', + 'am_ET': u'Amharic / አምሃርኛ', 'ar_SY': u'Arabic / الْعَرَبيّة', 'bg_BG': u'Bulgarian / български език', 'bs_BS': u'Bosnian / bosanski jezik',