[IMP] ir_model: add table and columns fields for many2many fields
The fields are properly reflected for base many2many fields. Moreover, the code has been improved to remove a custom many2many field whose table is still used by another field. Onchange methods have been added: - When name, model and comodel are chosen, propose table and column names. - When table is modified, propose column names if the table is already used by another many2many field.
This commit is contained in:
committed by
Raphael Collet
parent
eadc08f531
commit
9979939cd1
@@ -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)
|
||||
);
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
<field name="ttype"/>
|
||||
<field name="relation" attrs="{'required': [('ttype','in',['many2one','one2many','many2many'])],'readonly': [('ttype','!=','one2many'), ('ttype','!=','many2one'), ('ttype','!=','many2many')]}"/>
|
||||
<field name="relation_field" attrs="{'required': [('ttype','=','one2many')], 'readonly': [('ttype','!=','one2many')]}"/>
|
||||
<field name="relation_table" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="column1" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="column2" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
@@ -171,6 +174,9 @@
|
||||
<field name="relation" attrs="{'required': [('ttype','in', ['many2one','one2many','many2many'])],
|
||||
'readonly': [('ttype','not in', ['many2one','one2many','many2many'])]}"/>
|
||||
<field name="relation_field" attrs="{'required': [('ttype','=','one2many')], 'readonly': [('ttype','!=','one2many')]}"/>
|
||||
<field name="relation_table" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="column1" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="column2" attrs="{'readonly': ['|', ('ttype', '!=', 'many2many'), ('state', '!=', 'manual')]}"/>
|
||||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'invisible': [('ttype','not in',['char','text','reference'])]}"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
|
||||
+7
-5
@@ -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))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user