summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-04-13 13:22:27 +0200
committerChristophe Simonis <chs@odoo.com>2017-04-13 13:22:27 +0200
commit13ad16104afa58fd7d3d70f502ada972727084eb (patch)
tree85cf1db6e9bd45960cc76e2b04122c460d945337 /odoo
parentbb5e0a3cfa08845dc935b1bc4881807f56e116c8 (diff)
parent00e169c05a270a0943a74fbeb0a884009946c7cd (diff)
[MERGE] forward port branch 10.0 up to 00e169c05a2
Diffstat (limited to 'odoo')
-rw-r--r--odoo/addons/base/ir/ir_filters.xml2
-rw-r--r--odoo/addons/base/ir/ir_model.py50
-rw-r--r--odoo/addons/base/ir/ir_qweb/fields.py2
-rw-r--r--odoo/addons/base/res/res_company_view.xml2
-rw-r--r--odoo/addons/base/res/res_users.py2
-rw-r--r--odoo/addons/base/tests/test_expression.py20
-rw-r--r--odoo/addons/base/tests/test_ir_actions.py111
-rw-r--r--odoo/addons/test_new_api/ir.model.access.csv1
-rw-r--r--odoo/addons/test_new_api/models.py16
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py23
-rw-r--r--odoo/fields.py1
-rw-r--r--odoo/models.py5
-rw-r--r--odoo/modules/graph.py7
-rw-r--r--odoo/modules/module.py2
-rw-r--r--odoo/osv/expression.py23
15 files changed, 225 insertions, 42 deletions
diff --git a/odoo/addons/base/ir/ir_filters.xml b/odoo/addons/base/ir/ir_filters.xml
index d21c6ad25d5..f46ec82c7f6 100644
--- a/odoo/addons/base/ir/ir_filters.xml
+++ b/odoo/addons/base/ir/ir_filters.xml
@@ -24,7 +24,7 @@
<record id="ir_filters_delete_own_rule" model="ir.rule">
<field name="name">ir.filters.own.rule.delete</field>
<field name="model_id" ref="model_ir_filters"/>
- <field name="domain_force">[('create_uid', '=', user.id)]</field>
+ <field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py
index 98410ba110b..f67d9c1dc5f 100644
--- a/odoo/addons/base/ir/ir_model.py
+++ b/odoo/addons/base/ir/ir_model.py
@@ -9,6 +9,7 @@ from collections import defaultdict
from odoo import api, fields, models, SUPERUSER_ID, tools, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.modules.registry import Registry
+from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -292,6 +293,25 @@ class IrModelFields(models.Model):
raise UserError(_("The Selection Options expression is not a valid Pythonic expression."
"Please provide an expression in the [('key','Label'), ...] format."))
+ @api.constrains('name', 'state')
+ def _check_name(self):
+ for field in self:
+ if field.state == 'manual' and not field.name.startswith('x_'):
+ raise ValidationError(_("Custom fields must have a name that starts with 'x_' !"))
+ try:
+ models.check_pg_name(field.name)
+ except ValidationError:
+ msg = _("Field names can only contain characters, digits and underscores (up to 63).")
+ raise ValidationError(msg)
+
+ @api.constrains('model', 'name')
+ def _unique_name(self):
+ # fix on stable branch (to be converted into an SQL constraint)
+ for field in self:
+ count = self.search_count([('model', '=', field.model), ('name', '=', field.name)])
+ if count > 1:
+ raise ValidationError(_("Field names must be unique per model."))
+
_sql_constraints = [
('size_gt_zero', 'CHECK (size>=0)', 'Size of the field cannot be negative.'),
]
@@ -449,6 +469,10 @@ class IrModelFields(models.Model):
This method prevents the modification/deletion of many2one fields
that have an inverse one2many, for instance.
"""
+ self = self.filtered(lambda record: record.state == 'manual')
+ if not self:
+ return
+
for record in self:
model = self.env[record.model]
field = model._fields[record.name]
@@ -462,6 +486,23 @@ class IrModelFields(models.Model):
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
raise UserError(msg % (field, model._field_inverses[field][0]))
+ # remove fields from registry, and check that views are not broken
+ fields = [self.env[record.model]._pop_field(record.name) for record in self]
+ domain = expression.OR([('arch_db', 'like', record.name)] for record in self)
+ views = self.env['ir.ui.view'].search(domain)
+ try:
+ for view in views:
+ view._check_xml()
+ except Exception:
+ raise UserError("\n".join([
+ _("Cannot rename/delete fields that are still present in views:"),
+ _("Fields:") + " " + ", ".join(map(str, fields)),
+ _("View:") + " " + view.name,
+ ]))
+ finally:
+ # the registry has been modified, restore it
+ self.pool.setup_models(self._cr)
+
@api.multi
def unlink(self):
if not self:
@@ -504,9 +545,6 @@ class IrModelFields(models.Model):
res = super(IrModelFields, self).create(vals)
if vals.get('state', 'manual') == 'manual':
- if not vals['name'].startswith('x_'):
- raise UserError(_("Custom fields must have a name that starts with 'x_' !"))
-
if vals.get('relation') and not self.env['ir.model'].search([('model', '=', vals['relation'])]):
raise UserError(_("Model %s does not exist!") % vals['relation'])
@@ -569,12 +607,6 @@ class IrModelFields(models.Model):
item._prepare_update()
if column_rename:
raise UserError(_('Can only rename one field at a time!'))
- if vals['name'] in obj._fields:
- raise UserError(_('Cannot rename field to %s, because that field already exists!') % vals['name'])
- if vals.get('state', 'manual') == 'manual' and not vals['name'].startswith('x_'):
- raise UserError(_('New field name must still start with x_ , because it is a custom field!'))
- if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
- raise ValueError('Invalid character in column name')
column_rename = (obj._table, item.name, vals['name'], item.index)
# We don't check the 'state', because it might come from the context
diff --git a/odoo/addons/base/ir/ir_qweb/fields.py b/odoo/addons/base/ir/ir_qweb/fields.py
index f0c7d576672..4beec41db7c 100644
--- a/odoo/addons/base/ir/ir_qweb/fields.py
+++ b/odoo/addons/base/ir/ir_qweb/fields.py
@@ -340,7 +340,7 @@ class MonetaryConverter(models.AbstractModel):
if not options.get('display_currency') and field.type == 'monetary' and field.currency_field:
options['display_currency'] = record[field.currency_field]
- return self.value_to_html(record[field_name], options)
+ return super(MonetaryConverter, self).record_to_html(record, field_name, options)
TIMEDELTA_UNITS = (
('year', 3600 * 24 * 365),
diff --git a/odoo/addons/base/res/res_company_view.xml b/odoo/addons/base/res/res_company_view.xml
index 95bafe4f334..4746deeb1b1 100644
--- a/odoo/addons/base/res/res_company_view.xml
+++ b/odoo/addons/base/res/res_company_view.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
- <report id="preview_rml_report" model="res.company" name="preview.report" menu="False" rml="base/report/preview_report.rml" string="Preview RML Report"/>
+ <report id="preview_rml_report" model="res.config.settings" name="preview.report" menu="False" rml="base/report/preview_report.rml" string="Preview RML Report"/>
<record id="view_company_form" model="ir.ui.view">
<field name="name">res.company.form</field>
diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py
index e92aaf5315e..da025eeca78 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -744,7 +744,7 @@ class GroupsView(models.Model):
xml = E.field(E.group(*(xml1), col="2"), E.group(*(xml2), col="4"), name="groups_id", position="replace")
xml.addprevious(etree.Comment("GENERATED AUTOMATICALLY BY GROUPS"))
xml_content = etree.tostring(xml, pretty_print=True, xml_declaration=True, encoding="utf-8")
- view.with_context(lang=None).write({'arch': xml_content})
+ view.with_context(lang=None).write({'arch': xml_content, 'arch_fs': False})
def get_application_groups(self, domain):
""" Return the non-share groups that satisfy ``domain``. """
diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py
index 932268d717c..104c4a6dfe1 100644
--- a/odoo/addons/base/tests/test_expression.py
+++ b/odoo/addons/base/tests/test_expression.py
@@ -683,8 +683,8 @@ class TestAutoJoin(TransactionCase):
self.assertEqual(partners, p_aa,
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..'): incorrect result")
# Test produced queries
- self.assertEqual(len(self.query_list), 3,
- "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') should produce 3 queries (1 in res_partner_bank, 2 on res_partner)")
+ self.assertEqual(len(self.query_list), 2,
+ "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') should produce 2 queries (1 in res_partner_bank, 1 on res_partner)")
sql_query = self.query_list[0].get_sql()
self.assertIn('res_partner_bank', sql_query[0],
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') first query incorrect main table")
@@ -692,16 +692,16 @@ class TestAutoJoin(TransactionCase):
expected = "%s::text like %s" % (unaccent('"res_partner_bank"."sanitized_acc_number"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') first query incorrect where condition")
-
+
self.assertEqual(['%' + name_test + '%'], sql_query[2],
"_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') first query incorrect parameter")
- sql_query = self.query_list[2].get_sql()
+ sql_query = self.query_list[1].get_sql()
self.assertIn('res_partner', sql_query[0],
- "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') third query incorrect main table")
+ "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') second query incorrect main table")
self.assertIn('"res_partner"."id" in (%s)', sql_query[1],
- "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') third query incorrect where condition")
+ "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') second query incorrect where condition")
self.assertIn(p_aa.id, sql_query[2],
- "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') third query incorrect parameter")
+ "_auto_join off: ('bank_ids.sanitized_acc_number', 'like', '..') second query incorrect parameter")
# Do: cascaded one2many without _auto_join
self._reinit_mock()
@@ -710,8 +710,8 @@ class TestAutoJoin(TransactionCase):
self.assertEqual(partners, p_a + p_b,
"_auto_join off: ('child_ids.bank_ids.id', 'in', [..]): incorrect result")
# Test produced queries
- self.assertEqual(len(self.query_list), 5,
- "_auto_join off: ('child_ids.bank_ids.id', 'in', [..]) should produce 5 queries (1 in res_partner_bank, 4 on res_partner)")
+ self.assertEqual(len(self.query_list), 3,
+ "_auto_join off: ('child_ids.bank_ids.id', 'in', [..]) should produce 3 queries (1 in res_partner_bank, 2 on res_partner)")
# Do: one2many with _auto_join
patch_auto_join(partner_obj, 'bank_ids', True)
@@ -943,7 +943,7 @@ class TestAutoJoin(TransactionCase):
self.assertLessEqual(p_a + p_b, partners,
"_auto_join off: ('child_ids.state_id.country_id.code', 'like', '..') incorrect result")
# Test produced queries
- self.assertEqual(len(self.query_list), 5,
+ self.assertEqual(len(self.query_list), 4,
"_auto_join off: ('child_ids.state_id.country_id.code', 'like', '..') number of queries incorrect")
# Do: ('child_ids.state_id.country_id.code', 'like', '..') with _auto_join
diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py
index 74d220b05bb..62711a8eab5 100644
--- a/odoo/addons/base/tests/test_ir_actions.py
+++ b/odoo/addons/base/tests/test_ir_actions.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo.exceptions import ValidationError
+from odoo.exceptions import UserError, ValidationError
from odoo.tools import mute_logger
import odoo.tests.common as common
@@ -181,3 +181,112 @@ class TestServerActions(TestServerActionsBase):
self.action.write({
'child_ids': [(6, 0, [self.action.id])]
})
+
+
+class TestCustomFields(common.TransactionCase):
+ MODEL = 'res.partner'
+
+ def setUp(self):
+ # use a test cursor instead of a real cursor
+ registry = odoo.registry()
+ registry.enter_test_mode()
+ fnames = set(registry[self.MODEL]._fields)
+
+ @self.addCleanup
+ def callback():
+ registry.leave_test_mode()
+ # the tests may have modified the registry, reset it
+ with registry.cursor() as cr:
+ registry.clear_manual_fields()
+ registry.setup_models(cr)
+ assert set(registry[self.MODEL]._fields) == fnames
+
+ super(TestCustomFields, self).setUp()
+
+ # do not reload the registry after removing a field
+ self.env = self.env(context={'_force_unlink': True})
+
+ def create_field(self, name):
+ """ create a custom field and return it """
+ model = self.env['ir.model'].search([('model', '=', self.MODEL)])
+ field = self.env['ir.model.fields'].create({
+ 'model_id': model.id,
+ 'name': name,
+ 'field_description': name,
+ 'ttype': 'char',
+ })
+ self.assertIn(name, self.env[self.MODEL]._fields)
+ return field
+
+ def create_view(self, name):
+ """ create a view with the given field name """
+ return self.env['ir.ui.view'].create({
+ 'name': 'yet another view',
+ 'model': self.MODEL,
+ 'arch': '<tree string="X"><field name="%s"/></tree>' % name,
+ })
+
+ def test_create_custom(self):
+ """ custom field names must be start with 'x_' """
+ with self.assertRaises(ValidationError):
+ self.create_field('foo')
+
+ def test_rename_custom(self):
+ """ custom field names must be start with 'x_' """
+ field = self.create_field('x_foo')
+ with self.assertRaises(ValidationError):
+ field.name = 'foo'
+
+ def test_create_valid(self):
+ """ field names must be valid pg identifiers """
+ with self.assertRaises(ValidationError):
+ self.create_field('x_foo bar')
+
+ def test_rename_valid(self):
+ """ field names must be valid pg identifiers """
+ field = self.create_field('x_foo')
+ with self.assertRaises(ValidationError):
+ field.name = 'x_foo bar'
+
+ def test_create_unique(self):
+ """ one cannot create two fields with the same name on a given model """
+ self.create_field('x_foo')
+ with self.assertRaises(ValidationError):
+ self.create_field('x_foo')
+
+ def test_rename_unique(self):
+ """ one cannot create two fields with the same name on a given model """
+ field1 = self.create_field('x_foo')
+ field2 = self.create_field('x_bar')
+ with self.assertRaises(ValidationError):
+ field2.name = field1.name
+
+ def test_remove_without_view(self):
+ """ try removing a custom field that does not occur in views """
+ field = self.create_field('x_foo')
+ field.unlink()
+
+ def test_rename_without_view(self):
+ """ try renaming a custom field that does not occur in views """
+ field = self.create_field('x_foo')
+ field.name = 'x_bar'
+
+ def test_remove_with_view(self):
+ """ try removing a custom field that occurs in a view """
+ field = self.create_field('x_foo')
+ self.create_view('x_foo')
+
+ # try to delete the field, this should fail but not modify the registry
+ with self.assertRaises(UserError):
+ field.unlink()
+ self.assertIn('x_foo', self.env[self.MODEL]._fields)
+
+ def test_rename_with_view(self):
+ """ try renaming a custom field that occurs in a view """
+ field = self.create_field('x_foo')
+ self.create_view('x_foo')
+
+ # try to delete the field, this should fail but not modify the registry
+ with self.assertRaises(UserError):
+ field.name = 'x_bar'
+ self.assertIn('x_foo', self.env[self.MODEL]._fields)
diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv
index 03ec40061ce..4e136481512 100644
--- a/odoo/addons/test_new_api/ir.model.access.csv
+++ b/odoo/addons/test_new_api/ir.model.access.csv
@@ -17,3 +17,4 @@ access_test_new_api_company,access_test_new_api_company,model_test_new_api_compa
access_test_new_api_company_attr,access_test_new_api_company_attr,model_test_new_api_company_attr,,1,1,1,1
access_test_new_api_sparse,access_test_new_api_sparse,model_test_new_api_sparse,,1,1,1,1
access_test_new_api_compute_inverse,access_test_new_api_compute_inverse,model_test_new_api_compute_inverse,,1,1,1,1
+access_test_new_api_recursive,access_test_new_api_recursive,model_test_new_api_recursive,,1,1,1,1
diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py
index f719c696494..ad7092159a4 100644
--- a/odoo/addons/test_new_api/models.py
+++ b/odoo/addons/test_new_api/models.py
@@ -360,3 +360,19 @@ class Sparse(models.Model):
char = fields.Char(sparse='data')
selection = fields.Selection([('one', 'One'), ('two', 'Two')], sparse='data')
partner = fields.Many2one('res.partner', sparse='data')
+
+
+class ComputeRecursive(models.Model):
+ _name = 'test_new_api.recursive'
+
+ name = fields.Char(required=True)
+ parent = fields.Many2one('test_new_api.recursive')
+ display_name = fields.Char(compute='_compute_display_name', store=True)
+
+ @api.depends('name', 'parent.display_name')
+ def _compute_display_name(self):
+ for rec in self:
+ if rec.parent:
+ rec.display_name = rec.parent.display_name + " / " + rec.name
+ else:
+ rec.display_name = rec.name
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index 1326eb3da39..9deaee1a5f0 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -179,6 +179,29 @@ class TestFields(common.TransactionCase):
cath.parent = finn
self.assertEqual(ewan.display_name, "Gabriel / Finnley / Catherine / Ewan")
+ def test_12_recursive_recompute(self):
+ """ test recomputation on recursively dependent field """
+ a = self.env['test_new_api.recursive'].create({'name': 'A'})
+ b = self.env['test_new_api.recursive'].create({'name': 'B', 'parent': a.id})
+ c = self.env['test_new_api.recursive'].create({'name': 'C', 'parent': b.id})
+ d = self.env['test_new_api.recursive'].create({'name': 'D', 'parent': c.id})
+ self.assertEqual(a.display_name, 'A')
+ self.assertEqual(b.display_name, 'A / B')
+ self.assertEqual(c.display_name, 'A / B / C')
+ self.assertEqual(d.display_name, 'A / B / C / D')
+
+ b.parent = False
+ self.assertEqual(a.display_name, 'A')
+ self.assertEqual(b.display_name, 'B')
+ self.assertEqual(c.display_name, 'B / C')
+ self.assertEqual(d.display_name, 'B / C / D')
+
+ b.name = 'X'
+ self.assertEqual(a.display_name, 'A')
+ self.assertEqual(b.display_name, 'X')
+ self.assertEqual(c.display_name, 'X / C')
+ self.assertEqual(d.display_name, 'X / C / D')
+
def test_12_cascade(self):
""" test computed field depending on computed field """
message = self.env.ref('test_new_api.message_0_0')
diff --git a/odoo/fields.py b/odoo/fields.py
index a5404e3c367..b14be35ba54 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -708,6 +708,7 @@ class Field(object):
model._field_triggers.add(field, (self, path_str))
elif path:
self.recursive = True
+ model._field_triggers.add(field, (self, '.'.join(path)))
############################################################################
#
diff --git a/odoo/models.py b/odoo/models.py
index c6e265e4f86..56923cd1558 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -669,7 +669,7 @@ class BaseModel(object):
field = cls._fields.get(name)
if not field:
_logger.warning("method %s.%s: @constrains parameter %r is not a field name", cls._name, attr, name)
- if not (field.store or field.inverse):
+ elif not (field.store or field.inverse):
_logger.warning("method %s.%s: @constrains parameter %r is not writeable", cls._name, attr, name)
methods.append(func)
@@ -4342,7 +4342,8 @@ class BaseModel(object):
"""
self.ensure_one()
vals = self.copy_data(default)[0]
- new = self.create(vals)
+ # To avoid to create a translation in the lang of the user, copy_translation will do it
+ new = self.with_context(lang=None).create(vals)
self.copy_translations(new)
return new
diff --git a/odoo/modules/graph.py b/odoo/modules/graph.py
index aaf90ae8443..ff2f99188fe 100644
--- a/odoo/modules/graph.py
+++ b/odoo/modules/graph.py
@@ -80,7 +80,7 @@ class Graph(dict):
info = odoo.modules.module.load_information_from_description_file(module)
if info and info['installable']:
packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
- else:
+ elif module != 'studio_customization':
_logger.warning('module %s: not installable, skipped', module)
dependencies = dict([(p, info['depends']) for p, info in packages])
@@ -112,10 +112,7 @@ class Graph(dict):
unmet_deps = filter(lambda p: p not in self, dependencies[package])
_logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps))
- result = len(self) - len_graph
- if result != len(module_list):
- _logger.warning('Some modules were not loaded.')
- return result
+ return len(self) - len_graph
def __iter__(self):
diff --git a/odoo/modules/module.py b/odoo/modules/module.py
index ecf95be088b..d15df6dba59 100644
--- a/odoo/modules/module.py
+++ b/odoo/modules/module.py
@@ -302,7 +302,7 @@ def load_information_from_description_file(module, mod_path=None):
:param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...)
"""
if not mod_path:
- mod_path = get_module_path(module)
+ mod_path = get_module_path(module, downloaded=True)
manifest_file = module_manifest(mod_path)
if manifest_file:
# default values for descriptor
diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py
index eea14988eb7..bef1cb1c523 100644
--- a/odoo/osv/expression.py
+++ b/odoo/osv/expression.py
@@ -876,8 +876,7 @@ class expression(object):
# Making search easier when there is a left operand as one2many or many2many
elif len(path) > 1 and field.store and field.type in ('many2many', 'one2many'):
right_ids = comodel.search([('.'.join(path[1:]), operator, right)]).ids
- table_ids = model.with_context(active_test=False).search([(path[0], 'in', right_ids)]).ids
- leaf.leaf = ('id', 'in', table_ids)
+ leaf.leaf = (path[0], 'in', right_ids)
push(leaf)
elif not field.store:
@@ -980,15 +979,16 @@ class expression(object):
rel_table, rel_id1, rel_id2 = field.relation, field.column1, field.column2
if operator in HIERARCHY_FUNCS:
- def _rec_convert(ids):
- if comodel == model:
- return ids
- return select_from_where(cr, rel_id1, rel_table, rel_id2, ids, operator)
-
ids2 = to_ids(right, comodel)
dom = HIERARCHY_FUNCS[operator]('id', ids2, comodel)
ids2 = comodel.search(dom).ids
- push(create_substitution_leaf(leaf, ('id', 'in', _rec_convert(ids2)), model))
+ if comodel == model:
+ push(create_substitution_leaf(leaf, ('id', 'in', ids2), model))
+ else:
+ subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2)
+ # avoid flattening of argument in to_sql()
+ subquery = cr.mogrify(subquery, [tuple(ids2)])
+ push(create_substitution_leaf(leaf, ('id', 'inselect', (subquery, [])), internal=True))
else:
call_null_m2m = True
if right is not False:
@@ -1014,8 +1014,11 @@ class expression(object):
operator = 'in' # operator changed because ids are directly related to main object
else:
call_null_m2m = False
- m2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in'
- push(create_substitution_leaf(leaf, ('id', m2m_op, select_from_where(cr, rel_id1, rel_table, rel_id2, res_ids, operator) or [0]), model))
+ subop = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect'
+ subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2)
+ # avoid flattening of argument in to_sql()
+ subquery = cr.mogrify(subquery, [tuple(filter(None, res_ids))])
+ push(create_substitution_leaf(leaf, ('id', subop, (subquery, [])), internal=True))
if call_null_m2m:
m2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'