summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-10-19 18:16:14 +0200
committerChristophe Simonis <chs@odoo.com>2016-10-19 18:16:14 +0200
commit938ffb92da1ea898f661aec04745f7630e2f378c (patch)
tree7faeed8433101836cd0239cf6c23629ef1f1289d /odoo
parent1f9e92dfef325b6727e1bee3de55bff095ec6cf2 (diff)
parent6466aa499fc37bba5f4feeeecac99448ef88e62e (diff)
[MERGE] forward port branch 10.0 up to 6466aa4
Diffstat (limited to 'odoo')
-rw-r--r--odoo/addons/base/ir/ir_model.py14
-rw-r--r--odoo/addons/base/ir/ir_ui_view.py17
-rw-r--r--odoo/addons/base/tests/test_views.py8
-rw-r--r--odoo/addons/test_impex/tests/test_export.py4
-rw-r--r--odoo/addons/test_new_api/tests/__init__.py1
-rw-r--r--odoo/addons/test_new_api/tests/test_onchange.py2
-rw-r--r--odoo/addons/test_new_api/tests/test_one2many.py65
-rw-r--r--odoo/addons/test_pylint/tests/test_pylint.py1
-rw-r--r--odoo/fields.py16
-rw-r--r--odoo/models.py12
-rw-r--r--odoo/tools/config.py2
11 files changed, 119 insertions, 23 deletions
diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py
index b4376c99467..1e23c311da2 100644
--- a/odoo/addons/base/ir/ir_model.py
+++ b/odoo/addons/base/ir/ir_model.py
@@ -1208,6 +1208,7 @@ class IrModelData(models.Model):
datas = self.search([('module', 'in', modules_to_remove)])
wkf_todo = []
to_unlink = tools.OrderedSet()
+ undeletable = self.browse([])
for data in datas.sorted(key='id', reverse=True):
model = data.model
@@ -1232,6 +1233,7 @@ class IrModelData(models.Model):
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model, exc_info=True)
def unlink_if_refcount(to_unlink):
+ undeletable = self.browse()
for model, res_id in to_unlink:
external_ids = self.search([('model', '=', model), ('res_id', '=', res_id)])
if external_ids - datas:
@@ -1255,28 +1257,30 @@ class IrModelData(models.Model):
self.env[model].browse(res_id).unlink()
except Exception:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
+ undeletable += external_ids
self._cr.execute('ROLLBACK TO SAVEPOINT record_unlink_save')
else:
self._cr.execute('RELEASE SAVEPOINT record_unlink_save')
+ return undeletable
# Remove non-model records first, then model fields, and finish with models
- unlink_if_refcount(item for item in to_unlink if item[0] not in ('ir.model', 'ir.model.fields', 'ir.model.constraint'))
- unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.constraint')
+ undeletable += unlink_if_refcount(item for item in to_unlink if item[0] not in ('ir.model', 'ir.model.fields', 'ir.model.constraint'))
+ undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.constraint')
modules = self.env['ir.module.module'].search([('name', 'in', modules_to_remove)])
constraints = self.env['ir.model.constraint'].search([('module', 'in', modules.ids)])
constraints._module_data_uninstall()
- unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.fields')
+ undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model.fields')
relations = self.env['ir.model.relation'].search([('module', 'in', modules.ids)])
relations._module_data_uninstall()
- unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model')
+ undeletable += unlink_if_refcount(item for item in to_unlink if item[0] == 'ir.model')
self._cr.commit()
- datas.unlink()
+ (datas - undeletable).unlink()
@api.model
def _process_end(self, modules):
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index 67c755581d3..10bf7c627b4 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -229,10 +229,11 @@ actual arch.
for view, view_wo_lang in zip(self, self.with_context(lang=None)):
view_wo_lang.arch = view.arch_base
+ @api.depends('write_date')
def _compute_model_data_id(self):
- # get the last ir_model_data record corresponding to self
+ # get the first ir_model_data record corresponding to self
domain = [('model', '=', 'ir.ui.view'), ('res_id', 'in', self.ids)]
- for data in self.env['ir.model.data'].search_read(domain, ['res_id']):
+ for data in self.env['ir.model.data'].search_read(domain, ['res_id'], order='id desc'):
view = self.browse(data['res_id'])
view.model_data_id = data['id']
@@ -343,7 +344,15 @@ actual arch.
values['name'] = "%s %s" % (values.get('model'), values['type'])
self.clear_caches()
- return super(View, self).create(self._compute_defaults(values))
+
+ if 'install_mode_data' in self._context:
+ # the view is created from a data file by installing a module; delay
+ # the recomputation of field 'model_data_id' until its xmlid record
+ # is created
+ with self.env.norecompute():
+ return super(View, self).create(self._compute_defaults(values))
+ else:
+ return super(View, self).create(self._compute_defaults(values))
@api.multi
def write(self, vals):
@@ -1168,4 +1177,4 @@ actual arch.
try:
self.browse(vid)._check_xml()
except Exception as e:
- self.raise_view_error("Can't validate view: %s" % e.message, vid)
+ self.raise_view_error("Can't validate view:\n%s" % (e.message or repr(e)), vid)
diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py
index 38f30308fab..adbabdc48ad 100644
--- a/odoo/addons/base/tests/test_views.py
+++ b/odoo/addons/base/tests/test_views.py
@@ -12,6 +12,14 @@ from odoo.tests import common
from odoo.tools import mute_logger
+class ViewXMLID(common.TransactionCase):
+ def test_model_data_id(self):
+ """ Check whether views know their xmlid record. """
+ view = self.env.ref('base.view_company_form')
+ self.assertTrue(view)
+ self.assertTrue(view.model_data_id)
+ self.assertEqual(view.model_data_id.complete_name, 'base.view_company_form')
+
class ViewCase(common.TransactionCase):
def setUp(self):
super(ViewCase, self).setUp()
diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py
index e80f6df0866..226ba9943f6 100644
--- a/odoo/addons/test_impex/tests/test_export.py
+++ b/odoo/addons/test_impex/tests/test_export.py
@@ -23,7 +23,7 @@ class CreatorCase(common.TransactionCase):
def export(self, value, fields=('value',), context=None):
record = self.make(value)
record.invalidate_cache()
- return record._BaseModel__export_rows([f.split('/') for f in fields])
+ return record._export_rows([f.split('/') for f in fields])
class test_boolean_field(CreatorCase):
@@ -449,7 +449,7 @@ class test_o2m_multiple(CreatorCase):
def export(self, value=None, fields=('child1', 'child2',), context=None, **values):
record = self.make(value, **values)
- return record._BaseModel__export_rows([f.split('/') for f in fields])
+ return record._export_rows([f.split('/') for f in fields])
def test_empty(self):
self.assertEqual(
diff --git a/odoo/addons/test_new_api/tests/__init__.py b/odoo/addons/test_new_api/tests/__init__.py
index 2bbcfe40990..b90ccc96825 100644
--- a/odoo/addons/test_new_api/tests/__init__.py
+++ b/odoo/addons/test_new_api/tests/__init__.py
@@ -3,5 +3,6 @@
from . import test_new_fields
from . import test_onchange
from . import test_attributes
+from . import test_one2many
from . import test_ui
from . import test_domain
diff --git a/odoo/addons/test_new_api/tests/test_onchange.py b/odoo/addons/test_new_api/tests/test_onchange.py
index 87fc8229b7c..a6e2bff1b35 100644
--- a/odoo/addons/test_new_api/tests/test_onchange.py
+++ b/odoo/addons/test_new_api/tests/test_onchange.py
@@ -176,7 +176,7 @@ class TestOnChange(common.TransactionCase):
self.assertEqual(values, {
'name': partner.name,
'partner': partner.id,
- 'lines': [(5,), (4, line.id)],
+ 'lines': [(6, 0, [line.id])],
})
# modify 'partner'
diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py
new file mode 100644
index 00000000000..38dee13ab54
--- /dev/null
+++ b/odoo/addons/test_new_api/tests/test_one2many.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+from openerp.tests.common import TransactionCase
+
+
+class One2manyCase(TransactionCase):
+ def setUp(self):
+ super(One2manyCase, self).setUp()
+ self.Line = self.env["test_new_api.multi.line"]
+ self.multi = self.env["test_new_api.multi"].create({
+ "name": "What is up?"
+ })
+
+ def operations(self):
+ """Run operations on o2m fields to check all works fine."""
+ # Check the lines first
+ self.assertItemsEqual(
+ self.multi.lines.mapped('name'),
+ map(str, range(10)))
+ # Modify the first line and drop the last one
+ self.multi.lines[0].name = "hello"
+ self.multi.lines = self.multi.lines[:-1]
+ self.assertEqual(len(self.multi.lines), 9)
+ self.assertIn("hello", self.multi.lines.mapped('name'))
+ # Invalidate the cache and check again; this crashes if the value
+ # of self.multi.lines in cache contains new records
+ self.multi.invalidate_cache()
+ self.assertEqual(len(self.multi.lines), 9)
+ self.assertIn("hello", self.multi.lines.mapped('name'))
+
+ def test_new_one_by_one(self):
+ """Check lines created with ``new()`` and appended one by one."""
+ for name in range(10):
+ self.multi.lines |= self.Line.new({"name": str(name)})
+ self.operations()
+
+ def test_new_single(self):
+ """Check lines created with ``new()`` and added in one step."""
+ self.multi.lines = self.Line.browse(
+ [self.Line.new({"name": str(name)}).id for name in range(10)]
+ )
+ self.operations()
+
+ def test_create_one_by_one(self):
+ """Check lines created with ``create()`` and appended one by one."""
+ for name in range(10):
+ self.multi.lines |= self.Line.create({"name": str(name)})
+ self.operations()
+
+ def test_create_single(self):
+ """Check lines created with ``create()`` and added in one step."""
+ self.multi.lines = self.Line.browse(
+ [self.Line.create({"name": str(name)}).id for name in range(10)]
+ )
+ self.operations()
+
+ def test_rpcstyle_one_by_one(self):
+ """Check lines created with RPC style and appended one by one."""
+ for name in range(10):
+ self.multi.lines = [(0, 0, {"name": str(name)})]
+ self.operations()
+
+ def test_rpcstyle_single(self):
+ """Check lines created with RPC style and added in one step"""
+ self.multi.lines = [(0, 0, {'name': str(name)}) for name in range(10)]
+ self.operations()
diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py
index 251db1f6eba..38fa4c3d51a 100644
--- a/odoo/addons/test_pylint/tests/test_pylint.py
+++ b/odoo/addons/test_pylint/tests/test_pylint.py
@@ -22,6 +22,7 @@ _logger = logging.getLogger(__name__)
class TestPyLint(TransactionCase):
ENABLED_CODES = [
+ 'E0601', # using variable before assignment
'W0123', # eval used
'W0101', # unreachable code
]
diff --git a/odoo/fields.py b/odoo/fields.py
index ee732b5b62f..c542945a781 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -894,10 +894,12 @@ class Field(object):
env.invalidate(spec)
else:
- # simply write to the database, and update cache
+ # Write to database
write_value = self.convert_to_write(self.convert_to_record(value, record), record)
record.write({self.name: write_value})
- record._cache[self] = value
+ # Update the cache unless value contains a new record
+ if not (self.relational and not all(value)):
+ record._cache[self] = value
############################################################################
#
@@ -1987,7 +1989,7 @@ class _RelationalMulti(_Relational):
def convert_to_write(self, value, record):
# make result with new and existing records
- result = [(5,)]
+ result = [(6, 0, [])]
for record in value:
if not record.id:
values = {name: record[name] for name in record._cache}
@@ -1998,7 +2000,7 @@ class _RelationalMulti(_Relational):
values = record._convert_to_write(values)
result.append((1, record.id, values))
else:
- result.append((4, record.id))
+ result[0][2].append(record.id)
return result
def convert_to_onchange(self, value, record, fnames=()):
@@ -2171,7 +2173,11 @@ class One2many(_RelationalMulti):
query = "SELECT id FROM %s WHERE %s=%%s AND id <> ALL(%%s)" % (comodel._table, inverse)
comodel._cr.execute(query, (record.id, act[2] or [0]))
lines = comodel.browse([row[0] for row in comodel._cr.fetchall()])
- lines.write({inverse: False})
+ inverse_field = comodel._fields[inverse]
+ if inverse_field.ondelete == 'cascade':
+ lines.unlink()
+ else:
+ lines.write({inverse: False})
class Many2many(_RelationalMulti):
diff --git a/odoo/models.py b/odoo/models.py
index 9270782eadb..8f15a59d7cd 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -738,7 +738,7 @@ class BaseModel(object):
return '__export__.' + name
@api.multi
- def __export_rows(self, fields):
+ def _export_rows(self, fields):
""" Export fields of the records in ``self``.
:param fields: list of lists of fields to traverse
@@ -785,7 +785,7 @@ class BaseModel(object):
# recursively export the fields that follow name
fields2 = [(p[1:] if p and p[0] == name else []) for p in fields]
- lines2 = value.__export_rows(fields2)
+ lines2 = value._export_rows(fields2)
if lines2:
# merge first line with record's main line
for j, val in enumerate(lines2[0]):
@@ -804,6 +804,9 @@ class BaseModel(object):
return lines
+ # backward compatibility
+ __export_rows = _export_rows
+
@api.multi
def export_data(self, fields_to_export, raw_data=False):
""" Export fields for selected objects
@@ -817,7 +820,7 @@ class BaseModel(object):
fields_to_export = map(fix_import_export_id_paths, fields_to_export)
if raw_data:
self = self.with_context(export_raw_data=True)
- return {'datas': self.__export_rows(fields_to_export)}
+ return {'datas': self._export_rows(fields_to_export)}
@api.model
def load(self, fields, data):
@@ -3505,8 +3508,7 @@ class BaseModel(object):
``(6, _, ids)``
replaces all existing records in the set by the ``ids`` list,
equivalent to using the command ``5`` followed by a command
- ``4`` for each ``id`` in ``ids``. Can not be used on
- :class:`~odoo.fields.One2many`.
+ ``4`` for each ``id`` in ``ids``.
.. note:: Values marked as ``_`` in the list above are ignored and
can be anything, generally ``0`` or ``False``.
diff --git a/odoo/tools/config.py b/odoo/tools/config.py
index 5584002c520..ea030a80f10 100644
--- a/odoo/tools/config.py
+++ b/odoo/tools/config.py
@@ -363,7 +363,7 @@ class configmanager(object):
rcfilepath = old_rcfilepath
self.rcfile = os.path.abspath(
- self.config_file or opt.config or os.environ.get('OPENERP_SERVER') or rcfilepath)
+ self.config_file or opt.config or os.environ.get('ODOO_RC') or os.environ.get('OPENERP_SERVER') or rcfilepath)
self.load()
# Verify that we want to log or not, if not the output will go to stdout