summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-01-14 10:34:50 +0100
committerChristophe Simonis <chs@odoo.com>2019-01-14 10:34:50 +0100
commit3cbc9dd6c034f8bbba8a75def07d063e3f625bdb (patch)
tree55d7e9b940e27a1dad5415b6b721ab464d0bc418 /odoo
parent976429c8c69b2225e96ccb0e254ee4849832ed27 (diff)
parent6a0675d36dd888daa4183f3d609ce334ed3cb493 (diff)
[MERGE] forward port branch 12.0 up to 6a0675d36dd
Diffstat (limited to 'odoo')
-rw-r--r--odoo/addons/base/models/ir_actions_report.py7
-rw-r--r--odoo/addons/base/models/ir_model.py13
-rw-r--r--odoo/addons/base/models/ir_module.py17
-rw-r--r--odoo/addons/base/models/ir_translation.py14
-rw-r--r--odoo/addons/base/models/ir_ui_view.py3
-rw-r--r--odoo/addons/base/models/res_company.py9
-rw-r--r--odoo/addons/base/models/res_users.py22
-rw-r--r--odoo/addons/base/static/src/css/modules.css4
-rw-r--r--odoo/addons/base/tests/test_cache.py72
-rw-r--r--odoo/addons/base/tests/test_translate.py28
-rw-r--r--odoo/addons/base/wizard/base_update_translations.py3
-rw-r--r--odoo/addons/test_inherits/tests/test_inherits.py11
-rw-r--r--odoo/addons/test_inherits_depends/__init__.py3
-rw-r--r--odoo/addons/test_inherits_depends/__manifest__.py12
-rw-r--r--odoo/addons/test_inherits_depends/models.py10
-rw-r--r--odoo/addons/test_inherits_depends/tests/__init__.py3
-rw-r--r--odoo/addons/test_inherits_depends/tests/test_inherits.py29
-rw-r--r--odoo/addons/test_translation_import/tests/test_term_count.py12
-rw-r--r--odoo/fields.py3
-rw-r--r--odoo/tools/translate.py4
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: