diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-01-14 10:34:50 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-01-14 10:34:50 +0100 |
| commit | 3cbc9dd6c034f8bbba8a75def07d063e3f625bdb (patch) | |
| tree | 55d7e9b940e27a1dad5415b6b721ab464d0bc418 /odoo | |
| parent | 976429c8c69b2225e96ccb0e254ee4849832ed27 (diff) | |
| parent | 6a0675d36dd888daa4183f3d609ce334ed3cb493 (diff) | |
[MERGE] forward port branch 12.0 up to 6a0675d36dd
Diffstat (limited to 'odoo')
20 files changed, 231 insertions, 48 deletions
diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index 0463170ea90..ffa57d97e50 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -609,6 +609,7 @@ class IrActionsReport(models.Model): def render_qweb_pdf(self, res_ids=None, data=None): if not data: data = {} + data.setdefault('report_type', 'pdf') # remove editor feature in pdf generation data.update(enable_editor=False) @@ -703,6 +704,9 @@ class IrActionsReport(models.Model): @api.model def render_qweb_text(self, docids, data=None): + if not data: + data = {} + data.setdefault('report_type', 'text') data = self._get_rendering_context(docids, data) return self.render_template(self.report_name, data), 'text' @@ -710,6 +714,9 @@ class IrActionsReport(models.Model): def render_qweb_html(self, docids, data=None): """This method generates and returns html version of a report. """ + if not data: + data = {} + data.setdefault('report_type', 'html') data = self._get_rendering_context(docids, data) return self.render_template(self.report_name, data), 'html' diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 624a2a32aeb..3b957792299 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -1540,7 +1540,8 @@ class IrModelData(models.Model): raise AccessError(_('Administrator access is required to uninstall a module')) # enable model/field deletion - self = self.with_context(**{MODULE_UNINSTALL_FLAG: True}) + # we deactivate prefetching to not try to read a column that has been deleted + self = self.with_context(**{MODULE_UNINSTALL_FLAG: True, 'prefetch_fields': False}) datas = self.search([('module', 'in', modules_to_remove)]) to_unlink = tools.OrderedSet() @@ -1625,6 +1626,16 @@ class IrModelData(models.Model): for (id, xmlid, model, res_id) in self._cr.fetchall(): if xmlid not in loaded_xmlids: if model in self.env: + if self.search_count([ + ("model", "=", model), + ("res_id", "=", res_id), + ("id", "!=", id), + ("id", "not in", bad_imd_ids), + ]): + # another external id is still linked to the same record, only deleting the old imd + bad_imd_ids.append(id) + continue + _logger.info('Deleting %s@%s (%s)', res_id, model, xmlid) record = self.env[model].browse(res_id) if record.exists(): diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index 824365424cc..e673ac016dd 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -22,7 +22,9 @@ import lxml.html import odoo from odoo import api, fields, models, modules, tools, _ +from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG from odoo.exceptions import AccessDenied, UserError +from odoo.osv import expression from odoo.tools.parse_version import parse_version from odoo.tools.misc import topological_sort from odoo.http import request @@ -454,10 +456,25 @@ class Module(models.Model): """ modules_to_remove = self.mapped('name') self.env['ir.model.data']._module_data_uninstall(modules_to_remove) + self._remove_copied_views() self.write({'state': 'uninstalled', 'latest_version': False}) return True @api.multi + def _remove_copied_views(self): + """ Remove the copies of the views installed by the modules in `self`. + + Those copies do not have an external id so they will not be cleaned by + `_module_data_uninstall`. This is why we rely on `key` instead. + + It is important to remove these copies because using them will crash if + they rely on data that don't exist anymore if the module is removed. + """ + domain = expression.OR([[('key', '=like', m.name + '.%')] for m in self]) + orphans = self.env['ir.ui.view'].with_context(**{'active_test': False, MODULE_UNINSTALL_FLAG: True}).search(domain) + orphans.unlink() + + @api.multi @api.returns('self') def downstream_dependencies(self, known_deps=None, exclude_states=('uninstalled', 'uninstallable', 'to remove')): diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index 4aeae7c2cd6..2d9744abada 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -140,7 +140,8 @@ class IrTranslationImport(object): WHERE type = 'code' AND noupdate IS NOT TRUE ON CONFLICT (type, lang, md5(src)) WHERE type = 'code' - DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments); + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; """ % (self._model_table, self._table)) count += cr.rowcount cr.execute(""" INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments) @@ -149,7 +150,8 @@ class IrTranslationImport(object): WHERE type = 'model' AND noupdate IS NOT TRUE ON CONFLICT (type, lang, name, res_id) WHERE type = 'model' - DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments); + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; """ % (self._model_table, self._table)) count += cr.rowcount cr.execute(""" INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments) @@ -158,7 +160,8 @@ class IrTranslationImport(object): WHERE type IN ('selection', 'constraint', 'sql_constraint') AND noupdate IS NOT TRUE ON CONFLICT (type, lang, name, md5(src)) WHERE type IN ('selection', 'constraint', 'sql_constraint') - DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments); + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; """ % (self._model_table, self._table)) count += cr.rowcount cr.execute(""" INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments) @@ -167,7 +170,8 @@ class IrTranslationImport(object): WHERE type = 'model_terms' AND noupdate IS NOT TRUE ON CONFLICT (type, name, lang, res_id, md5(src)) - DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments); + DO UPDATE SET (name, lang, res_id, src, type, value, module, state, comments) = (EXCLUDED.name, EXCLUDED.lang, EXCLUDED.res_id, EXCLUDED.src, EXCLUDED.type, EXCLUDED.value, EXCLUDED.module, EXCLUDED.state, EXCLUDED.comments) + WHERE EXCLUDED.value IS NOT NULL AND EXCLUDED.value != ''; """ % (self._model_table, self._table)) count += cr.rowcount cr.execute(""" INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments) @@ -483,6 +487,8 @@ class IrTranslation(models.Model): for translation in translations: if not translation.value: discarded += translation + # consider it done to avoid being matched against another term + done.add((translation.src, translation.lang)) elif translation.src in terms: done.add((translation.src, translation.lang)) else: diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 4a72f20abb3..d8a15f000d9 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1174,6 +1174,9 @@ actual arch. """ Return the view ID corresponding to ``template``, which may be a view ID or an XML ID. Note that this method may be overridden for other kinds of template values. + + This method could return the ID of something that is not a view (when + using fallback to `xmlid_to_res_id`). """ if isinstance(template, int): return template diff --git a/odoo/addons/base/models/res_company.py b/odoo/addons/base/models/res_company.py index 64fb3ee6fb2..d62096fe561 100644 --- a/odoo/addons/base/models/res_company.py +++ b/odoo/addons/base/models/res_company.py @@ -296,3 +296,12 @@ class Company(models.Model): def action_save_onboarding_company_step(self): if bool(self.street): self.set_onboarding_step_done('base_onboarding_company_state') + + @api.model + def _get_main_company(self): + try: + main_company = self.sudo().env.ref('base.main_company') + except ValueError: + main_company = self.env['res.company'].sudo().search([], limit=1, order="id") + + return main_company diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index dfa5f61e25b..73eb10b9f86 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -922,8 +922,26 @@ class GroupsImplied(models.Model): if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) for group in self: - vals = {'users': list(zip(repeat(4), group.with_context(active_test=False).users.ids))} - super(GroupsImplied, group.trans_implied_ids).write(vals) + self._cr.execute(""" + WITH RECURSIVE group_imply(gid, hid) AS ( + SELECT gid, hid + FROM res_groups_implied_rel + UNION + SELECT i.gid, r.hid + FROM res_groups_implied_rel r + JOIN group_imply i ON (i.hid = r.gid) + ) + INSERT INTO res_groups_users_rel (gid, uid) + SELECT i.hid, r.uid + FROM group_imply i, res_groups_users_rel r + WHERE r.gid = i.gid + AND i.gid = %(gid)s + EXCEPT + SELECT r.gid, r.uid + FROM res_groups_users_rel r + JOIN group_imply i ON (r.gid = i.hid) + WHERE i.gid = %(gid)s + """, dict(gid=group.id)) return res class UsersImplied(models.Model): diff --git a/odoo/addons/base/static/src/css/modules.css b/odoo/addons/base/static/src/css/modules.css index 0ac3e5cd939..f1156464ed1 100644 --- a/odoo/addons/base/static/src/css/modules.css +++ b/odoo/addons/base/static/src/css/modules.css @@ -13,10 +13,6 @@ padding-left: 80px; } -.o_modules_kanban.o_kanban_view h4.o_kanban_record_title { - line-height: 1; -} - .o_modules_kanban.o_kanban_view .o_kanban_record .o_dropdown_kanban { visibility: visible; opacity: 0.5; diff --git a/odoo/addons/base/tests/test_cache.py b/odoo/addons/base/tests/test_cache.py index 5ae2e8abaea..baa4bc2ab70 100644 --- a/odoo/addons/base/tests/test_cache.py +++ b/odoo/addons/base/tests/test_cache.py @@ -2,7 +2,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import os +import platform import psutil +import unittest from odoo.exceptions import CacheMiss from odoo.tests.common import TransactionCase @@ -51,33 +53,35 @@ class TestRecordCache(TransactionCase): self.assertCountEqual(cache.get_missing_ids(foo2 + bar2, name), [1, 2]) # set values in one environment only - for rec in [foo1, bar1]: - cache.set(rec, name, 'NAME1') - cache.set(rec, ref, 'REF1') - check(foo1, 'NAME1', 'REF1') + cache.set(foo1, name, 'FOO1_NAME') + cache.set(foo1, ref, 'FOO1_REF') + cache.set(bar1, name, 'BAR1_NAME') + cache.set(bar1, ref, 'BAR1_REF') + check(foo1, 'FOO1_NAME', 'FOO1_REF') check(foo2, None, None) - check(bar1, 'NAME1', 'REF1') + check(bar1, 'BAR1_NAME', 'BAR1_REF') check(bar2, None, None) self.assertCountEqual(cache.get_missing_ids(foo1 + bar1, name), []) self.assertCountEqual(cache.get_missing_ids(foo2 + bar2, name), [1, 2]) # set values in both environments - for rec in [foo2, bar2]: - cache.set(rec, name, 'NAME2') - cache.set(rec, ref, 'REF2') - check(foo1, 'NAME1', 'REF1') - check(foo2, 'NAME2', 'REF2') - check(bar1, 'NAME1', 'REF1') - check(bar2, 'NAME2', 'REF2') + cache.set(foo2, name, 'FOO2_NAME') + cache.set(foo2, ref, 'FOO2_REF') + cache.set(bar2, name, 'BAR2_NAME') + cache.set(bar2, ref, 'BAR2_REF') + check(foo1, 'FOO1_NAME', 'FOO1_REF') + check(foo2, 'FOO2_NAME', 'FOO2_REF') + check(bar1, 'BAR1_NAME', 'BAR1_REF') + check(bar2, 'BAR2_NAME', 'BAR2_REF') self.assertCountEqual(cache.get_missing_ids(foo1 + bar1, name), []) self.assertCountEqual(cache.get_missing_ids(foo2 + bar2, name), []) # remove value in one environment cache.remove(foo1, name) - check(foo1, None, 'REF1') - check(foo2, 'NAME2', 'REF2') - check(bar1, 'NAME1', 'REF1') - check(bar2, 'NAME2', 'REF2') + check(foo1, None, 'FOO1_REF') + check(foo2, 'FOO2_NAME', 'FOO2_REF') + check(bar1, 'BAR1_NAME', 'BAR1_REF') + check(bar2, 'BAR2_NAME', 'BAR2_REF') self.assertCountEqual(cache.get_missing_ids(foo1 + bar1, name), [1]) self.assertCountEqual(cache.get_missing_ids(foo2 + bar2, name), []) @@ -85,8 +89,8 @@ class TestRecordCache(TransactionCase): cache.invalidate([(name, None), (ref, foo1.ids)]) check(foo1, None, None) check(foo2, None, None) - check(bar1, None, 'REF1') - check(bar2, None, 'REF2') + check(bar1, None, 'BAR1_REF') + check(bar2, None, 'BAR2_REF') # total invalidation cache.invalidate() @@ -96,28 +100,34 @@ class TestRecordCache(TransactionCase): check(bar2, None, None) # set a special value - cache.set_special(foo1, name, lambda: '42') + cache.set_special(foo1, name, lambda: 'FOO1_SPECIAL_NAME') self.assertTrue(cache.contains(foo1, name)) self.assertFalse(cache.contains_value(foo1, name)) - self.assertEqual(cache.get(foo1, name), '42') + self.assertEqual(cache.get(foo1, name), 'FOO1_SPECIAL_NAME') self.assertIsNone(cache.get_value(foo1, name)) self.assertIsNotNone(cache.get_special(foo1, name)) # copy cache - for rec in [foo1, bar1]: - cache.set(rec, name, 'NAME1') - cache.set(rec, ref, 'REF1') - check(foo1, 'NAME1', 'REF1') - check(foo2, None, None) - check(bar1, 'NAME1', 'REF1') + cache.set(foo1, name, 'FOO1_NAME') + cache.set(foo1, ref, 'FOO1_REF') + cache.set(bar1, name, 'BAR1_NAME') + cache.set(bar1, ref, 'BAR1_REF') + cache.set(foo2, name, 'FOO2_NAME') + check(foo1, 'FOO1_NAME', 'FOO1_REF') + check(foo2, 'FOO2_NAME', None) + check(bar1, 'BAR1_NAME', 'BAR1_REF') check(bar2, None, None) cache.copy(foo1 + bar1, foo2.env) - check(foo1, 'NAME1', 'REF1') - check(foo2, 'NAME1', 'REF1') - check(bar1, 'NAME1', 'REF1') - check(bar2, 'NAME1', 'REF1') - + check(foo1, 'FOO1_NAME', 'FOO1_REF') + check(foo2, 'FOO1_NAME', 'FOO1_REF') + check(bar1, 'BAR1_NAME', 'BAR1_REF') + check(bar2, 'BAR1_NAME', 'BAR1_REF') + + @unittest.skipIf( + not(platform.system() == 'Linux' and platform.machine() == 'x86_64'), + "This test only makes sense on 64-bit Linux-like systems", + ) def test_memory(self): """ Check memory consumption of the cache. """ NB_RECORDS = 100000 diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index 4094fc4b829..676b0418794 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -405,3 +405,31 @@ class TestXMLTranslation(TransactionCase): self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + + def test_sync_update(self): + """ Check translations after minor change in source terms. """ + archf = '<form string="X"><div>%s</div><div>%s</div></form>' + terms_src = ('Subtotal', 'Subtotal:') + terms_en = ('Subtotal', 'Sub total:') + view = self.create_view(archf, terms_src, en_US=terms_en) + + translations = self.env['ir.translation'].search([ + ('type', '=', 'model_terms'), + ('name', '=', "ir.ui.view,arch_db"), + ('res_id', '=', view.id), + ]) + self.assertEqual(len(translations), 2) + + # modifying the arch should sync existing translations without errors + view.write({ + "arch": archf % ('Subtotal', 'Subtotal:<br/>') + }) + + translations = self.env['ir.translation'].search([ + ('type', '=', 'model_terms'), + ('name', '=', "ir.ui.view,arch_db"), + ('res_id', '=', view.id), + ]) + # 'Subtotal' being src==value, it will be discared + # 'Subtotal:' will be discarded as it match 'Subtotal' instead of 'Subtotal:<br/>' + self.assertEqual(len(translations), 0) diff --git a/odoo/addons/base/wizard/base_update_translations.py b/odoo/addons/base/wizard/base_update_translations.py index f1dc7a10ca4..0bbfc17ab24 100644 --- a/odoo/addons/base/wizard/base_update_translations.py +++ b/odoo/addons/base/wizard/base_update_translations.py @@ -32,5 +32,6 @@ class BaseUpdateTranslations(models.TransientModel): lang_name = self._get_lang_name(this.lang) with tempfile.NamedTemporaryFile() as buf: tools.trans_export(this.lang, ['all'], buf, 'po', self._cr) - tools.trans_load_data(self._cr, buf, 'po', this.lang, lang_name=lang_name) + context = {'create_empty_translation': True} + tools.trans_load_data(self._cr, buf, 'po', this.lang, lang_name=lang_name, context=context) return {'type': 'ir.actions.act_window_close'} diff --git a/odoo/addons/test_inherits/tests/test_inherits.py b/odoo/addons/test_inherits/tests/test_inherits.py index 246222b14ff..85396de5441 100644 --- a/odoo/addons/test_inherits/tests/test_inherits.py +++ b/odoo/addons/test_inherits/tests/test_inherits.py @@ -26,3 +26,14 @@ class test_inherits(common.TransactionCase): pallet = self.env.ref('test_inherits.pallet_a') pallet.write({'name': 'C'}) self.assertEqual(pallet.name, 'C') + + def test_ir_model_data_inherits(self): + """ Check the existence of the correct ir.model.data """ + IrModelData = self.env['ir.model.data'] + field = IrModelData.search([('name', '=', 'field_test_unit__name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits') + + field = IrModelData.search([('name', '=', 'field_test_box__name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits') diff --git a/odoo/addons/test_inherits_depends/__init__.py b/odoo/addons/test_inherits_depends/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/odoo/addons/test_inherits_depends/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/odoo/addons/test_inherits_depends/__manifest__.py b/odoo/addons/test_inherits_depends/__manifest__.py new file mode 100644 index 00000000000..335fdee1362 --- /dev/null +++ b/odoo/addons/test_inherits_depends/__manifest__.py @@ -0,0 +1,12 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'test-inherits-depends', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to verify the inheritance using _inherits in non-original modules.""", + 'depends': ['test_inherits'], + 'data': [], + 'installable': True, + 'auto_install': False, +} diff --git a/odoo/addons/test_inherits_depends/models.py b/odoo/addons/test_inherits_depends/models.py new file mode 100644 index 00000000000..77ad3ad1206 --- /dev/null +++ b/odoo/addons/test_inherits_depends/models.py @@ -0,0 +1,10 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +# We add a field on this model +class Unit(models.Model): + _inherit = 'test.unit' + + second_name = fields.Char() diff --git a/odoo/addons/test_inherits_depends/tests/__init__.py b/odoo/addons/test_inherits_depends/tests/__init__.py new file mode 100644 index 00000000000..5a5bb8a95f3 --- /dev/null +++ b/odoo/addons/test_inherits_depends/tests/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_inherits diff --git a/odoo/addons/test_inherits_depends/tests/test_inherits.py b/odoo/addons/test_inherits_depends/tests/test_inherits.py new file mode 100644 index 00000000000..c758b47f8de --- /dev/null +++ b/odoo/addons/test_inherits_depends/tests/test_inherits.py @@ -0,0 +1,29 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import common + + +class test_inherits(common.TransactionCase): + + def test_ir_model_data_inherits_again(self): + """ Re-run test_inherits test to make sure another imd hasn't been created """ + IrModelData = self.env['ir.model.data'] + field = IrModelData.search([('name', '=', 'field_test_unit__name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits') + + field = IrModelData.search([('name', '=', 'field_test_box__name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits') + + + def test_ir_model_data_inherits_depends(self): + """ Check the existence of the correct ir.model.data """ + IrModelData = self.env['ir.model.data'] + field = IrModelData.search([('name', '=', 'field_test_unit__second_name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits_depends') + + field = IrModelData.search([('name', '=', 'field_test_box__second_name')]) + self.assertEqual(len(field), 1) + self.assertEqual(field.module, 'test_inherits_depends') diff --git a/odoo/addons/test_translation_import/tests/test_term_count.py b/odoo/addons/test_translation_import/tests/test_term_count.py index 8a4976e1771..bde7bceac4f 100644 --- a/odoo/addons/test_translation_import/tests/test_term_count.py +++ b/odoo/addons/test_translation_import/tests/test_term_count.py @@ -92,17 +92,23 @@ class TestTermCount(common.TransactionCase): def test_export_empty_string(self): """When the string and the translation is equal the translation is empty""" # Export the translations - def update_translations(): + def update_translations(context=None): + context = dict(context or {}, overwrite=True) with closing(io.BytesIO()) as bufferobj: odoo.tools.trans_export('fr_FR', ['test_translation_import'], bufferobj, 'po', self.cr) bufferobj.name = 'test_translation_import/i18n/fr.po' - odoo.tools.trans_load_data(self.cr, bufferobj, 'po', 'fr_FR', verbose=False, context={'overwrite': True}) + odoo.tools.trans_load_data(self.cr, bufferobj, 'po', 'fr_FR', verbose=False, context=context) - # Check if the not translated key is empty string + # Check that the not translated key is not created update_translations() translation = self.env['ir.translation'].search_count([('src', '=', 'Efgh'), ('value', '=', '')]) self.assertFalse(translation, 'An empty translation is not imported') + # Check that "Generate Missing Terms" create empty string for not translated key + update_translations({'create_empty_translation': True}) + translation = self.env['ir.translation'].search_count([('src', '=', 'Efgh'), ('value', '=', '')]) + self.assertTrue(translation, 'The translation of "Efgh" should be empty') + # Modify the value translated for the equal value of the key menu = self.env.ref('test_translation_import.menu_test_translation_import') menu.name = "New Name" diff --git a/odoo/fields.py b/odoo/fields.py index 04c70e930be..a0a08631c52 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -560,6 +560,9 @@ class Field(MetaField('DummyField', (object,), {})): if self.inherited and field.required: self.required = True + if self.inherited: + self._modules.update(field._modules) + def traverse_related(self, record): """ Traverse the fields of the related field `self` except for the last one, and return it as a pair `(last_record, last_field)`. """ diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index d458d779c27..e20ebf9fed7 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -1098,8 +1098,8 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, dic['lang'] = lang dic.update(zip(fields, row)) - # ignore empty value that may be set from base language - if not dic['value']: + # do not import empty values + if not env.context.get('create_empty_translation', False) and not dic['value']: return if use_pot_reference: |
