diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql
index c0fd67ea56c..6b0812a4b40 100644
--- a/openerp/addons/base/base.sql
+++ b/openerp/addons/base/base.sql
@@ -39,7 +39,10 @@ CREATE TABLE ir_model_fields (
required boolean default False,
selectable boolean default False,
translate boolean default False,
- serialization_field_id integer references ir_model_fields on delete cascade,
+ serialization_field_id integer references ir_model_fields on delete cascade,
+ relation_table varchar,
+ column1 varchar,
+ column2 varchar,
primary key(id)
);
diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py
index 8fafc60c1c9..ab30836feb9 100644
--- a/openerp/addons/base/ir/ir_model.py
+++ b/openerp/addons/base/ir/ir_model.py
@@ -245,6 +245,9 @@ class ir_model_fields(osv.osv):
"structure of the serialization field, instead "
"of having its own database column. This cannot be "
"changed after creation."),
+ 'relation_table': fields.char("Relation Table", help="Used for custom many2many fields to define a custom relation table name"),
+ 'column1': fields.char("Column 1", help="Column referring to the record in the model table"),
+ 'column2': fields.char("Column 2", help="Column referring to the record in the comodel table"),
}
_rec_name='field_description'
_defaults = {
@@ -287,26 +290,75 @@ class ir_model_fields(osv.osv):
('size_gt_zero', 'CHECK (size>=0)',_size_gt_zero_msg ),
]
+ @api.model
+ def _custom_many2many_names(self, model_name, comodel_name):
+ """ Return default names for the table and columns of a custom many2many field. """
+ rel1 = self.env[model_name]._table
+ rel2 = self.env[comodel_name]._table
+ table = 'x_%s_%s_rel' % tuple(sorted([rel1, rel2]))
+ if rel1 == rel2:
+ return (table, 'id1', 'id2')
+ else:
+ return (table, '%s_id' % rel1, '%s_id' % rel2)
+
+ @api.onchange('ttype', 'model_id', 'relation')
+ def _onchange_ttype(self):
+ if self.ttype == 'many2many' and self.model_id and self.relation:
+ names = self._custom_many2many_names(self.model_id.model, self.relation)
+ self.relation_table, self.column1, self.column2 = names
+ else:
+ self.relation_table = False
+ self.column1 = False
+ self.column2 = False
+
+ @api.onchange('relation_table')
+ def _onchange_relation_table(self):
+ if self.relation_table:
+ # check whether other fields use the same table
+ others = self.search([('ttype', '=', 'many2many'),
+ ('relation_table', '=', self.relation_table),
+ ('id', 'not in', self._origin.ids)])
+ if others:
+ for other in others:
+ if (other.model, other.relation) == (self.relation, self.model):
+ # other is a candidate inverse field
+ self.column1 = other.column2
+ self.column2 = other.column1
+ return
+ return {'warning':{
+ 'title': _("Warning"),
+ 'message': _("The table %r if used for other, possibly incompatible fields.") % self.relation_table,
+ }}
+
def _drop_column(self, cr, uid, ids, context=None):
+ tables_to_drop = set()
+
for field in self.browse(cr, uid, ids, context):
if field.name in MAGIC_COLUMNS:
continue
model = self.pool[field.model]
- cr.execute('select relkind from pg_class where relname=%s', (model._table,))
+ cr.execute('SELECT relkind FROM pg_class WHERE relname=%s', (model._table,))
result = cr.fetchone()
- cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
+ cr.execute("""SELECT column_name FROM information_schema.columns
+ WHERE table_name=%s AND column_name=%s""",
+ (model._table, field.name))
column_name = cr.fetchone()
if column_name and (result and result[0] == 'r'):
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
- # remove m2m relation table for custom fields
- # we consider the m2m relation is only one way as it's not possible
- # to specify the relation table in the interface for custom fields
- # TODO master: maybe use ir.model.relations for custom fields
if field.state == 'manual' and field.ttype == 'many2many':
- rel_name = model._fields[field.name].relation
- cr.execute('DROP table "%s"' % (rel_name))
+ rel_name = field.relation_table or model._fields[field.name].relation
+ tables_to_drop.add(rel_name)
model._pop_field(cr, uid, field.name, context=context)
+ if tables_to_drop:
+ # drop the relation tables that are not used by other fields
+ cr.execute("""SELECT relation_table FROM ir_model_fields
+ WHERE relation_table IN %s AND id NOT IN %s""",
+ (tuple(tables_to_drop), tuple(ids)))
+ tables_to_keep = set(row[0] for row in cr.fetchall())
+ for rel_name in tables_to_drop - tables_to_keep:
+ cr.execute('DROP TABLE "%s"' % rel_name)
+
return True
def unlink(self, cr, user, ids, context=None):
diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml
index 4ae1dd85b34..44a44fe66bf 100644
--- a/openerp/addons/base/ir/ir_model_view.xml
+++ b/openerp/addons/base/ir/ir_model_view.xml
@@ -68,6 +68,9 @@
+
+
+
@@ -171,6 +174,9 @@
+
+
+
diff --git a/openerp/models.py b/openerp/models.py
index 12fa508341b..73fbc47aae7 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -391,6 +391,9 @@ class BaseModel(object):
'translate': bool(getattr(f, 'translate', False)),
'relation_field': f.type == 'one2many' and f.inverse_name or None,
'serialization_field_id': None,
+ 'relation_table': f.type == 'many2many' and f.relation or None,
+ 'column1': f.type == 'many2many' and f.column1 or None,
+ 'column2': f.type == 'many2many' and f.column2 or None,
}
if getattr(f, 'serialization_field', None):
# resolve link to serialization_field if specified by name
@@ -696,11 +699,10 @@ class BaseModel(object):
if partial and field['relation'] not in self.pool:
continue
attrs['comodel_name'] = field['relation']
- _rel1 = field['relation'].replace('.', '_')
- _rel2 = field['model'].replace('.', '_')
- attrs['relation'] = 'x_%s_%s_%s_rel' % (_rel1, _rel2, name)
- attrs['column1'] = 'id1'
- attrs['column2'] = 'id2'
+ rel, col1, col2 = self.env['ir.model.fields']._custom_many2many_names(field['model'], field['relation'])
+ attrs['relation'] = field['relation_table'] or rel
+ attrs['column1'] = field['column1'] or col1
+ attrs['column2'] = field['column2'] or col2
attrs['domain'] = eval(field['domain']) if field['domain'] else None
self._add_field(name, Field.by_type[field['ttype']](**attrs))