diff --git a/CHANGELOG b/CHANGELOG index 4e35c91b5e..6241a1dcda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Allow to use Reference field in One2Many & Many2Many * Remove hexmd5 from ModelView.fields_view_get * Allow client to manage model access * Add time format validation diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index 82695ec1b3..7a196f3a42 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -418,7 +418,8 @@ One2Many .. class:: One2Many(model_name, field, string[, add_remove[, order[, datetime_field[, \**options]]]]) A one-to-many relation field. It requires to have the opposite -:class:`Many2One` field defined on the target model. +:class:`Many2One` field or a :class:`Reference` field defined on the target +model. This field accepts as written value a list of tuples like this: @@ -449,7 +450,8 @@ This field accepts as written value a list of tuples like this: .. attribute:: One2Many.field - The name of the field that handles the opposite :class:`Many2One` + The name of the field that handles the opposite :class:`Many2One` or + :class:`Reference`. :class:`One2Many` has some extra optional arguments: @@ -483,7 +485,8 @@ A many-to-many relation field. .. attribute:: Many2Many.origin - The name of the field that has the :class:`Many2One` to the record. + The name of the field that has the :class:`Many2One` or :class:`Reference` + to the record. .. attribute:: Many2Many.target diff --git a/trytond/model/fields/many2many.py b/trytond/model/fields/many2many.py index df968f8b81..b65fadafed 100644 --- a/trytond/model/fields/many2many.py +++ b/trytond/model/fields/many2many.py @@ -68,18 +68,30 @@ class Many2Many(Field): order = self.order relation_obj = Pool().get(self.relation_name) + if self.origin in relation_obj._columns: + origin_field = relation_obj._columns[self.origin] + else: + origin_field = relation_obj._inherit_fields[self.origin][2] relation_ids = [] for i in range(0, len(ids), Transaction().cursor.IN_MAX): sub_ids = ids[i:i + Transaction().cursor.IN_MAX] - relation_ids.append(relation_obj.search([ - (self.origin, 'in', sub_ids), - (self.target + '.id', '!=', False), - ], order=order)) + if origin_field._type == 'reference': + references = ['%s,%s' % (model._name, x) for x in sub_ids] + clause = [(self.origin, 'in', references)] + else: + clause = [(self.origin, 'in', sub_ids)] + clause += [(self.target + '.id', '!=', None)] + relation_ids.append(relation_obj.search(clause, order=order)) relation_ids = list(chain(*relation_ids)) for relation in relation_obj.browse(relation_ids): - res[relation[self.origin].id].append(relation[self.target].id) + if origin_field._type == 'reference': + _, origin_id = relation[self.origin].split(',') + origin_id = int(origin_id) + else: + origin_id = relation[self.origin].id + res[origin_id].append(relation[self.target].id) return res def set(self, ids, model, name, values): @@ -93,26 +105,53 @@ class Many2Many(Field): (``create``, ``{: value}``), (``write``, ````, ``{: value}``), (``delete``, ````), + (``delete_all``), (``unlink``, ````), (``add``, ````), (``unlink_all``), (``set``, ````) ''' + pool = Pool() if not values: return - relation_obj = Pool().get(self.relation_name) + relation_obj = pool.get(self.relation_name) target_obj = self.get_target() + + if self.origin in relation_obj._columns: + origin_field = relation_obj._columns[self.origin] + else: + origin_field = relation_obj._inherit_fields[self.origin][2] + + def search_clause(ids): + if origin_field._type == 'reference': + references = ['%s,%s' % (model._name, x) for x in ids] + return (self.origin, 'in', references) + else: + return (self.origin, 'in', ids) + + def field_value(record_id): + if origin_field._type == 'reference': + return '%s,%s' % (model._name, record_id) + else: + return record_id + for act in values: if act[0] == 'create': for record_id in ids: relation_obj.create({ - self.origin: record_id, - self.target: target_obj.create(act[1]), - }) + self.origin: field_value(record_id), + self.target: target_obj.create(act[1]), + }) elif act[0] == 'write': target_obj.write(act[1], act[2]) elif act[0] == 'delete': target_obj.delete(act[1]) + elif act[0] == 'delete_all': + relations = relation_obj.browse(relation_obj.search([ + search_clause(ids), + ])) + target_obj.delete([r[self.target].id for r in relations + if r[self.target].id]) elif act[0] == 'unlink': if isinstance(act[1], (int, long)): target_ids = [act[1]] @@ -125,9 +164,9 @@ class Many2Many(Field): Transaction().cursor.IN_MAX): sub_ids = target_ids[i:i + Transaction().cursor.IN_MAX] relation_ids += relation_obj.search([ - (self.origin, 'in', ids), - (self.target, 'in', sub_ids), - ]) + search_clause(ids), + (self.target, 'in', sub_ids), + ]) relation_obj.delete(relation_ids) elif act[0] == 'add': if isinstance(act[1], (int, long)): @@ -141,22 +180,22 @@ class Many2Many(Field): Transaction().cursor.IN_MAX): sub_ids = target_ids[i:i + Transaction().cursor.IN_MAX] relation_ids = relation_obj.search([ - (self.origin, 'in', ids), - (self.target, 'in', sub_ids), - ]) + search_clause(ids), + (self.target, 'in', sub_ids), + ]) for relation in relation_obj.browse(relation_ids): existing_ids.append(relation[self.target].id) for new_id in (x for x in target_ids if x not in existing_ids): for record_id in ids: relation_obj.create({ - self.origin: record_id, - self.target: new_id, - }) + self.origin: field_value(record_id), + self.target: new_id, + }) elif act[0] == 'unlink_all': target_ids = relation_obj.search([ - (self.origin, 'in', ids), - (self.target + '.id', '!=', False), - ]) + search_clause(ids), + (self.target + '.id', '!=', None), + ]) relation_obj.delete(target_ids) elif act[0] == 'set': if not act[1]: @@ -164,17 +203,17 @@ class Many2Many(Field): else: target_ids = list(act[1]) target_ids2 = relation_obj.search([ - (self.origin, 'in', ids), - (self.target + '.id', '!=', False), - ]) + search_clause(ids), + (self.target + '.id', '!=', None), + ]) relation_obj.delete(target_ids2) for new_id in target_ids: for record_id in ids: relation_obj.create({ - self.origin: record_id, - self.target: new_id, - }) + self.origin: field_value(record_id), + self.target: new_id, + }) else: raise Exception('Bad arguments') diff --git a/trytond/model/fields/one2many.py b/trytond/model/fields/one2many.py index 5ac1c6e74d..9318842e23 100644 --- a/trytond/model/fields/one2many.py +++ b/trytond/model/fields/one2many.py @@ -24,7 +24,8 @@ class One2Many(Field): loading='lazy'): ''' :param model_name: The name of the target model. - :param field: The name of the field that handle the reverse many2one. + :param field: The name of the field that handle the reverse many2one or + reference. :param add_remove: A list that defines a domain on add/remove. See domain on ModelStorage.search. :param order: a list of tuples that are constructed like this: @@ -66,21 +67,29 @@ class One2Many(Field): Return target records ordered. :param ids: a list of ids - :param model: a string with the name of the model + :param model: the model :param name: a string with the name of the field :param values: a dictionary with the read values :return: a dictionary with ids as key and values as value ''' pool = Pool() + relation_obj = pool.get(self.model_name) + if self.field in relation_obj._columns: + field = relation_obj._columns[self.field] + else: + field = relation_obj._inherit_fields[self.field][2] res = {} for i in ids: res[i] = [] ids2 = [] for i in range(0, len(ids), Transaction().cursor.IN_MAX): sub_ids = ids[i:i + Transaction().cursor.IN_MAX] - ids2.append(pool.get(self.model_name).search([ - (self.field, 'in', sub_ids), - ], order=self.order)) + if field._type == 'reference': + references = ['%s,%s' % (model._name, x) for x in sub_ids] + clause = [(self.field, 'in', references)] + else: + clause = [(self.field, 'in', sub_ids)] + ids2.append(relation_obj.search(clause, order=self.order)) cache = Transaction().cursor.get_cache(Transaction().context) cache.setdefault(self.model_name, {}) @@ -93,8 +102,13 @@ class One2Many(Field): ids3.append(i) if ids3: - for i in pool.get(self.model_name).read(ids3, [self.field]): - res[i[self.field]].append(i['id']) + for i in relation_obj.read(ids3, [self.field]): + if field._type == 'reference': + _, id_ = i[self.field].split(',') + id_ = int(id_) + else: + id_ = i[self.field] + res[id_].append(i['id']) index_of_ids2 = dict((i, index) for index, i in enumerate(chain(*ids2))) @@ -107,7 +121,7 @@ class One2Many(Field): Set the values. :param ids: A list of ids - :param model: A string with the name of the model + :param model: the model :param name: A string with the name of the field :param values: A list of tuples: (``create``, ``{: value}``), @@ -122,21 +136,38 @@ class One2Many(Field): pool = Pool() if not values: return - model = pool.get(self.model_name) + target_obj = pool.get(self.model_name) + if self.field in target_obj._columns: + field = target_obj._columns[self.field] + else: + field = target_obj._inherit_fields[self.field][2] + + def search_clause(ids): + if field._type == 'reference': + references = ['%s,%s' % (model._name, x) for x in ids] + return (self.field, 'in', references) + else: + return (self.field, 'in', ids) + + def field_value(record_id): + if field._type == 'reference': + return '%s,%s' % (model._name, record_id) + else: + return record_id + for act in values: if act[0] == 'create': for record_id in ids: - act[1][self.field] = record_id - model.create(act[1]) + act[1][self.field] = field_value(record_id) + target_obj.create(act[1]) elif act[0] == 'write': - model.write(act[1], act[2]) + target_obj.write(act[1], act[2]) elif act[0] == 'delete': - model.delete(act[1]) + target_obj.delete(act[1]) elif act[0] == 'delete_all': - target_ids = model.search([ - (self.field, 'in', ids), - ]) - model.delete(target_ids) + target_obj.delete(target_obj.search([ + search_clause(ids), + ])) elif act[0] == 'unlink': if isinstance(act[1], (int, long)): target_ids = [act[1]] @@ -144,13 +175,13 @@ class One2Many(Field): target_ids = list(act[1]) if not target_ids: continue - target_ids = model.search([ - (self.field, 'in', ids), - ('id', 'in', target_ids), - ]) - model.write(target_ids, { - self.field: False, - }) + target_ids = target_obj.search([ + search_clause(ids), + ('id', 'in', target_ids), + ]) + target_obj.write(target_ids, { + self.field: None, + }) elif act[0] == 'add': if isinstance(act[1], (int, long)): target_ids = [act[1]] @@ -159,32 +190,32 @@ class One2Many(Field): if not target_ids: continue for record_id in ids: - model.write(target_ids, { - self.field: record_id, - }) + target_obj.write(target_ids, { + self.field: field_value(record_id), + }) elif act[0] == 'unlink_all': - target_ids = model.search([ - (self.field, 'in', ids), - ]) - model.write(target_ids, { - self.field: False, - }) + target_ids = target_obj.search([ + search_clause(ids), + ]) + target_obj.write(target_ids, { + self.field: None, + }) elif act[0] == 'set': if not act[1]: target_ids = [0] else: target_ids = list(act[1]) for record_id in ids: - target_ids2 = model.search([ - (self.field, '=', record_id), - ('id', 'not in', target_ids), - ]) - model.write(target_ids2, { - self.field: False, - }) - if act[1]: - model.write(target_ids, { - self.field: record_id, + target_ids2 = target_obj.search([ + search_clause([record_id]), + ('id', 'not in', target_ids), + ]) + target_obj.write(target_ids2, { + self.field: None, }) + if act[1]: + target_obj.write(target_ids, { + self.field: field_value(record_id), + }) else: raise Exception('Bad arguments') diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index ab319e6ad3..8b9125118f 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -1427,6 +1427,17 @@ class ModelSQL(ModelStorage): else: relation_obj = target_obj origin, target = field.field, 'id' + if origin in relation_obj._columns: + rev_field = relation_obj._columns[origin] + else: + rev_field = relation_obj._inherit_fields[origin][2] + if rev_field._type == 'reference': + sql_type = FIELDS[ + self.id._type].sql_type(self.id)[0] + origin = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)' + % (origin, sql_type)) + else: + origin = '"%s"' % origin if hasattr(field, 'search'): domain.extend([(fargs[0], 'in', target_obj.search([ (fargs[1], domain[i][1], domain[i][2]), @@ -1436,7 +1447,7 @@ class ModelSQL(ModelStorage): query1, query2 = target_obj.search([ (fargs[1], domain[i][1], domain[i][2]), ], order=[], query_string=True) - query1 = ('SELECT "%s" FROM "%s" WHERE "%s" IN (%s)' % + query1 = ('SELECT %s FROM "%s" WHERE "%s" IN (%s)' % (origin, relation_obj._table, target, query1)) domain[i] = ('id', 'inselect', (query1, query2)) i += 1 @@ -1450,57 +1461,46 @@ class ModelSQL(ModelStorage): domain.extend(field.search(table, clause[0], clause)) elif field._type == 'one2many': field_obj = pool.get(field.model_name) - - if isinstance(domain[i][2], basestring): - # get the ids of the records of the "distant" resource - ids2 = [x[0] for x in field_obj.search([ - ('rec_name', domain[i][1], domain[i][2]), - ], order=[])] - else: - ids2 = domain[i][2] - table_query = '' table_args = [] if field_obj.table_query(): table_query, table_args = field_obj.table_query() table_query = '(' + table_query + ') AS ' + if field.field in field_obj._columns: + rev_field = field_obj._columns[field.field] + else: + rev_field = field_obj._inherit_fields[field.field][2] + if rev_field._type == 'reference': + sql_type = FIELDS[self.id._type].sql_type(self.id)[0] + select = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)' + % (field.field, sql_type)) + else: + select = '"' + field.field + '"' - if ids2 == True or ids2 == False: - query1 = 'SELECT "' + field.field + '" ' \ + if isinstance(domain[i][2], bool) or domain[i][2] is None: + query1 = 'SELECT ' + select + ' ' \ 'FROM ' + table_query + \ '"' + field_obj._table + '" ' \ 'WHERE "' + field.field + '" IS NOT NULL' query2 = table_args clause = 'inselect' - if ids2 == False: + if not domain[i][2]: clause = 'notinselect' domain[i] = ('id', clause, (query1, query2)) - elif not ids2: - domain[i] = ('id', '=', '0') else: - if len(ids2) < cursor.IN_MAX: - red_sql, red_ids = reduce_ids('id', ids2) - query1 = 'SELECT "' + field.field + '" ' \ - 'FROM ' + table_query + \ - '"' + field_obj._table + '" ' \ - 'WHERE ' + red_sql - query2 = table_args + red_ids - domain[i] = ('id', 'inselect', (query1, query2)) + if isinstance(domain[i][2], basestring): + target_field = 'rec_name' else: - ids3 = [] - for i in range(0, len(ids2), cursor.IN_MAX): - sub_ids2 = ids2[i:i + cursor.IN_MAX] - red_sql, red_ids = reduce_ids('id', sub_ids2) - cursor.execute( - 'SELECT "' + field.field + '" ' \ - 'FROM ' + table_query + \ - '"' + field_obj._table + '" ' \ - 'WHERE ' + red_sql, - table_args + red_ids) - - ids3.extend([x[0] for x in cursor.fetchall()]) - - domain[i] = ('id', 'in', ids3) + target_field = 'id' + query1, query2 = field_obj.search([ + (target_field, domain[i][1], domain[i][2]), + ], order=[], query_string=True) + query1 = ('SELECT ' + select + ' ' + 'FROM ' + table_query + + '"' + field_obj._table + '" ' + 'WHERE id IN (' + query1 + ')') + query2 = table_args + query2 + domain[i] = ('id', 'inselect', (query1, query2)) i += 1 elif field._type in ('many2many', 'one2one'): # XXX must find a solution for long id list @@ -1555,14 +1555,31 @@ class ModelSQL(ModelStorage): domain[i] = ('id', 'not in', ids2 + _rec_get(ids2, table, domain[i][0])) else: + relation_obj = pool.get(field.relation_name) + table_query = '' + table_args = [] + if relation_obj.table_query(): + table_query, table_args = relation_obj.table_query() + table_query = '(' + table_query + ') AS ' + if field.origin in relation_obj._columns: + origin_field = relation_obj._columns[field.origin] + else: + origin_field = \ + relation_obj._inherit_fields[field.origin][2] + if origin_field._type == 'reference': + sql_type = FIELDS[self.id._type].sql_type(self.id)[0] + select = ('CAST(SPLIT_PART("%s", \',\', 2) AS %s)' + % (field.origin, sql_type)) + else: + select = '"' + field.origin + '"' if isinstance(domain[i][2], bool) or domain[i][2] is None: - relation_obj = pool.get(field.relation_name) - query1 = 'SELECT "' + field.origin + '" ' \ - 'FROM "' + relation_obj._table + '" '\ + query1 = 'SELECT ' + select + ' ' \ + 'FROM ' + table_query + ' '\ + '"' + relation_obj._table + '" ' \ 'WHERE "' + field.origin + '" IS NOT NULL' - query2 = [] + query2 = table_args clause = 'inselect' - if domain[i][2] == False: + if not domain[i][2]: clause = 'notinselect' domain[i] = ('id', clause, (query1, query2)) else: @@ -1575,9 +1592,11 @@ class ModelSQL(ModelStorage): query1, query2 = target_obj.search([ (target_field, domain[i][1], domain[i][2]), ], order=[], query_string=True) - query1 = ('SELECT "%s" FROM "%s" WHERE "%s" IN (%s)' % - (field.origin, relation_obj._table, - field.target, query1)) + query1 = ('SELECT ' + select + ' ' + 'FROM ' + table_query + + '"' + relation_obj._table + '" ' + 'WHERE "' + field.target + '" IN (' + query1 + ')') + query2 = table_args + query2 domain[i] = ('id', 'inselect', (query1, query2)) i += 1 diff --git a/trytond/test/test.py b/trytond/test/test.py index b7961247ee..62d259fa49 100644 --- a/trytond/test/test.py +++ b/trytond/test/test.py @@ -472,6 +472,114 @@ class One2OneRequiredRelation(ModelSQL): One2OneRequiredRelation() +class One2Many(ModelSQL): + 'One2Many Relation' + _name = 'test.one2many' + _description = __doc__ + name = fields.Char('Name', required=True) + targets = fields.One2Many('test.one2many.target', 'origin', 'Targets') + +One2Many() + + +class One2ManyTarget(ModelSQL): + 'One2Many Target' + _name = 'test.one2many.target' + _description = __doc__ + name = fields.Char('Name', required=True) + origin = fields.Many2One('test.one2many', 'Origin') + +One2ManyTarget() + + +class One2ManyReference(ModelSQL): + 'One2Many Reference Relation' + _name = 'test.one2many_reference' + _description = __doc__ + name = fields.Char('Name', required=True) + targets = fields.One2Many('test.one2many_reference.target', 'origin', + 'Targets') + +One2ManyReference() + + +class One2ManyReferenceTarget(ModelSQL): + 'One2Many Reference Target' + _name = 'test.one2many_reference.target' + _description = __doc__ + name = fields.Char('Name', required=True) + origin = fields.Reference('Origin', [ + ('test.one2many_reference', 'One2Many Reference'), + ]) + +One2ManyReferenceTarget() + + +class Many2Many(ModelSQL): + 'Many2Many' + _name = 'test.many2many' + _description = __doc__ + name = fields.Char('Name', required=True) + targets = fields.Many2Many('test.many2many.relation', 'origin', 'target', + 'Targets') + +Many2Many() + + +class Many2ManyTarget(ModelSQL): + 'Many2Many Target' + _name = 'test.many2many.target' + _description = __doc__ + name = fields.Char('Name', required=True) + + +Many2ManyTarget() + + +class Many2ManyRelation(ModelSQL): + 'Many2Many Relation' + _name = 'test.many2many.relation' + _description = __doc__ + origin = fields.Many2One('test.many2many', 'Origin') + target = fields.Many2One('test.many2many.target', 'Target') + +Many2ManyRelation() + + +class Many2ManyReference(ModelSQL): + 'Many2Many Reference' + _name = 'test.many2many_reference' + _description = __doc__ + name = fields.Char('Name', required=True) + targets = fields.Many2Many('test.many2many_reference.relation', 'origin', + 'target', 'Targets') + +Many2ManyReference() + + +class Many2ManyReferenceTarget(ModelSQL): + 'Many2Many Reference Target' + _name = 'test.many2many_reference.target' + _description = __doc__ + name = fields.Char('Name', required=True) + + +Many2ManyReferenceTarget() + + +class Many2ManyReferenceRelation(ModelSQL): + 'Many2Many Relation' + _name = 'test.many2many_reference.relation' + _description = __doc__ + origin = fields.Reference('Origin', [ + ('test.many2many_reference', 'Many2Many Reference'), + ]) + target = fields.Many2One('test.many2many_reference.target', + 'Reference Target') + +Many2ManyReferenceRelation() + + class Property(ModelSQL): 'Property' _name = 'test.property' diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py index 9fb25511c1..3e2cba930b 100644 --- a/trytond/tests/test_fields.py +++ b/trytond/tests/test_fields.py @@ -76,6 +76,18 @@ class FieldsTestCase(unittest.TestCase): self.one2one_target = POOL.get('test.one2one.target') self.one2one_required = POOL.get('test.one2one_required') + self.one2many = POOL.get('test.one2many') + self.one2many_target = POOL.get('test.one2many.target') + self.one2many_reference = POOL.get('test.one2many_reference') + self.one2many_reference_target = POOL.get( + 'test.one2many_reference.target') + + self.many2many = POOL.get('test.many2many') + self.many2many_target = POOL.get('test.many2many.target') + self.many2many_reference = POOL.get('test.many2many_reference') + self.many2many_reference_target = POOL.get( + 'test.many2many_reference.target') + self.property_ = POOL.get('test.property') def test0010boolean(self): @@ -2457,7 +2469,324 @@ class FieldsTestCase(unittest.TestCase): transaction.cursor.rollback() - def test0110property(self): + def test0120one2many(self): + ''' + Test One2Many. + ''' + with Transaction().start(DB_NAME, USER, + context=CONTEXT) as transaction: + for one2many, one2many_target in ( + (self.one2many, self.one2many_target), + (self.one2many_reference, self.one2many_reference_target), + ): + one2many1_id = one2many.create({ + 'name': 'origin1', + 'targets': [ + ('create', { + 'name': 'target1', + }), + ], + }) + self.assert_(one2many1_id) + + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(len(one2many1['targets']), 1) + target1_id, = one2many1['targets'] + + one2many_ids = one2many.search([ + ('targets', '=', 'target1'), + ]) + self.assertEqual(one2many_ids, [one2many1_id]) + + one2many_ids = one2many.search([ + ('targets', '!=', 'target1'), + ]) + self.assertEqual(one2many_ids, []) + + one2many_ids = one2many.search([ + ('targets', 'in', [target1_id]), + ]) + self.assertEqual(one2many_ids, [one2many1_id]) + + one2many_ids = one2many.search([ + ('targets', 'in', [0]), + ]) + self.assertEqual(one2many_ids, []) + + one2many_ids = one2many.search([ + ('targets', 'not in', [target1_id]), + ]) + self.assertEqual(one2many_ids, []) + + one2many_ids = one2many.search([ + ('targets', 'not in', [0]), + ]) + self.assertEqual(one2many_ids, [one2many1_id]) + + one2many_ids = one2many.search([ + ('targets.name', '=', 'target1'), + ]) + self.assertEqual(one2many_ids, [one2many1_id]) + + one2many_ids = one2many.search([ + ('targets.name', '!=', 'target1'), + ]) + self.assertEqual(one2many_ids, []) + + one2many2_id = one2many.create({ + 'name': 'origin2', + }) + self.assert_(one2many2_id) + + one2many2 = one2many.read(one2many2_id, ['targets']) + self.assertEqual(one2many2['targets'], []) + + one2many_ids = one2many.search([ + ('targets', '=', None), + ]) + self.assertEqual(one2many_ids, [one2many2_id]) + + one2many.write(one2many1_id, { + 'targets': [ + ('write', [target1_id], { + 'name': 'target1bis', + }), + ], + }) + target1 = one2many_target.read(target1_id, ['name']) + self.assertEqual(target1['name'], 'target1bis') + + target2_id = one2many_target.create({ + 'name': 'target2', + }) + one2many.write(one2many1_id, { + 'targets': [ + ('add', [target2_id]), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], + [target1_id, target2_id]) + + one2many.write(one2many1_id, { + 'targets': [ + ('unlink', [target2_id]), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], [target1_id]) + target2_id, = one2many_target.search([ + ('id', '=', target2_id), + ]) + self.assert_(target2_id) + + one2many.write(one2many1_id, { + 'targets': [ + ('unlink_all',), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], []) + target_ids = one2many_target.search([ + ('id', 'in', [target1_id, target2_id]), + ]) + self.assertEqual(target_ids, [target1_id, target2_id]) + + one2many.write(one2many1_id, { + 'targets': [ + ('set', [target1_id, target2_id]), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], + [target1_id, target2_id]) + + one2many.write(one2many1_id, { + 'targets': [ + ('delete', [target2_id]), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], [target1_id]) + target_ids = one2many_target.search([ + ('id', '=', target2_id), + ]) + self.assertEqual(target_ids, []) + + one2many.write(one2many1_id, { + 'targets': [ + ('delete_all',), + ], + }) + one2many1 = one2many.read(one2many1_id, ['targets']) + self.assertEqual(one2many1['targets'], []) + target_ids = one2many_target.search([ + ('id', '=', target1_id), + ]) + self.assertEqual(target_ids, []) + + transaction.cursor.rollback() + + def test0130many2many(self): + ''' + Test Many2Many. + ''' + with Transaction().start(DB_NAME, USER, + context=CONTEXT) as transaction: + for many2many, many2many_target in ( + (self.many2many, self.many2many_target), + (self.many2many_reference, + self.many2many_reference_target), + ): + many2many1_id = many2many.create({ + 'name': 'origin1', + 'targets': [ + ('create', { + 'name': 'target1', + }), + ], + }) + self.assert_(many2many1_id) + + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(len(many2many1['targets']), 1) + target1_id, = many2many1['targets'] + + many2many_ids = many2many.search([ + ('targets', '=', 'target1'), + ]) + self.assertEqual(many2many_ids, [many2many1_id]) + + many2many_ids = many2many.search([ + ('targets', '!=', 'target1'), + ]) + self.assertEqual(many2many_ids, []) + + many2many_ids = many2many.search([ + ('targets', 'in', [target1_id]), + ]) + self.assertEqual(many2many_ids, [many2many1_id]) + + many2many_ids = many2many.search([ + ('targets', 'in', [0]), + ]) + self.assertEqual(many2many_ids, []) + + many2many_ids = many2many.search([ + ('targets', 'not in', [target1_id]), + ]) + self.assertEqual(many2many_ids, []) + + many2many_ids = many2many.search([ + ('targets', 'not in', [0]), + ]) + self.assertEqual(many2many_ids, [many2many1_id]) + + many2many_ids = many2many.search([ + ('targets.name', '=', 'target1'), + ]) + self.assertEqual(many2many_ids, [many2many1_id]) + + many2many_ids = many2many.search([ + ('targets.name', '!=', 'target1'), + ]) + self.assertEqual(many2many_ids, []) + + many2many2_id = many2many.create({ + 'name': 'origin2', + }) + self.assert_(many2many2_id) + + many2many2 = many2many.read(many2many2_id, ['targets']) + self.assertEqual(many2many2['targets'], []) + + many2many_ids = many2many.search([ + ('targets', '=', None), + ]) + self.assertEqual(many2many_ids, [many2many2_id]) + + many2many.write(many2many1_id, { + 'targets': [ + ('write', [target1_id], { + 'name': 'target1bis', + }), + ], + }) + target1 = many2many_target.read(target1_id, ['name']) + self.assertEqual(target1['name'], 'target1bis') + + target2_id = many2many_target.create({ + 'name': 'target2', + }) + many2many.write(many2many1_id, { + 'targets': [ + ('add', [target2_id]), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], + [target1_id, target2_id]) + + many2many.write(many2many1_id, { + 'targets': [ + ('unlink', [target2_id]), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], [target1_id]) + target2_id, = many2many_target.search([ + ('id', '=', target2_id), + ]) + self.assert_(target2_id) + + many2many.write(many2many1_id, { + 'targets': [ + ('unlink_all',), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], []) + target_ids = many2many_target.search([ + ('id', 'in', [target1_id, target2_id]), + ]) + self.assertEqual(target_ids, [target1_id, target2_id]) + + many2many.write(many2many1_id, { + 'targets': [ + ('set', [target1_id, target2_id]), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], + [target1_id, target2_id]) + + many2many.write(many2many1_id, { + 'targets': [ + ('delete', [target2_id]), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], [target1_id]) + target_ids = many2many_target.search([ + ('id', '=', target2_id), + ]) + self.assertEqual(target_ids, []) + + many2many.write(many2many1_id, { + 'targets': [ + ('delete_all',), + ], + }) + many2many1 = many2many.read(many2many1_id, ['targets']) + self.assertEqual(many2many1['targets'], []) + target_ids = many2many_target.search([ + ('id', '=', target1_id), + ]) + self.assertEqual(target_ids, []) + + transaction.cursor.rollback() + + def test0140property(self): ''' Test Property with supported field types. '''