[REF] models: delegate o2m and m2m schema update to the field
This commit is contained in:
+59
-8
@@ -20,13 +20,14 @@ from openerp.tools import float_precision, float_repr, float_round, frozendict,
|
||||
html_sanitize, human_size, pg_varchar, ustr, OrderedSet
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
|
||||
from openerp.tools.translate import html_translate
|
||||
from openerp.tools.translate import html_translate, _
|
||||
|
||||
DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
|
||||
DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
|
||||
EMPTY_DICT = frozendict()
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_schema = logging.getLogger(__name__[:-7] + '.schema')
|
||||
|
||||
Default = object() # default value for __init__() methods
|
||||
|
||||
@@ -866,6 +867,12 @@ class Field(object):
|
||||
# Read from/write to database
|
||||
#
|
||||
|
||||
def check_schema(self, model):
|
||||
""" Make sure the database contains everything for this field. Return
|
||||
``True`` if the schema was altered and the field must be recomputed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def read(self, records):
|
||||
""" Read the value of ``self`` on ``records``, and store it in cache. """
|
||||
return NotImplementedError("Method read() undefined on %s" % self)
|
||||
@@ -2172,6 +2179,12 @@ class One2many(_RelationalMulti):
|
||||
fnames = [name for name in fnames if name != self.inverse_name]
|
||||
return super(One2many, self).convert_to_onchange(value, record, fnames)
|
||||
|
||||
def check_schema(self, model):
|
||||
if self.comodel_name in model.env:
|
||||
comodel = model.env[self.comodel_name]
|
||||
if self.inverse_name not in comodel._fields:
|
||||
raise UserError(_("No inverse field %r found for %r") % (self.inverse_name, self.comodel_name))
|
||||
|
||||
def read(self, records):
|
||||
# retrieve the lines in the comodel
|
||||
comodel = records.env[self.comodel_name].with_context(**self.context)
|
||||
@@ -2285,12 +2298,21 @@ class Many2many(_RelationalMulti):
|
||||
|
||||
def _setup_regular_base(self, model):
|
||||
super(Many2many, self)._setup_regular_base(model)
|
||||
if not self.relation and self.store:
|
||||
# retrieve self.relation from the corresponding column
|
||||
column = self.to_column()
|
||||
if isinstance(column, fields.many2many):
|
||||
self.relation, self.column1, self.column2 = column._sql_names(model)
|
||||
elif self.store:
|
||||
if self.store:
|
||||
if not (self.relation and self.column1 and self.column2):
|
||||
# table name is based on the stable alphabetical order of tables
|
||||
comodel = model.env[self.comodel_name]
|
||||
if not self.relation:
|
||||
tables = sorted([model._table, comodel._table])
|
||||
assert tables[0] != tables[1], \
|
||||
"%s: Implicit/canonical naming of many2many relationship " \
|
||||
"table is not possible when source and destination models " \
|
||||
"are the same" % self
|
||||
self.relation = '%s_%s_rel' % tuple(tables)
|
||||
if not self.column1:
|
||||
self.column1 = '%s_id' % model._table
|
||||
if not self.column2:
|
||||
self.column2 = '%s_id' % comodel._table
|
||||
# check validity of table name
|
||||
check_pg_name(self.relation)
|
||||
|
||||
@@ -2314,6 +2336,35 @@ class Many2many(_RelationalMulti):
|
||||
_column_auto_join = property(attrgetter('auto_join'))
|
||||
_column_limit = property(attrgetter('limit'))
|
||||
|
||||
def check_schema(self, model):
|
||||
cr = model._cr
|
||||
rel, id1, id2 = self.relation, self.column1, self.column2
|
||||
# do not create relations for custom fields as they do not belong to a module
|
||||
# they will be automatically removed when dropping the corresponding ir.model.field
|
||||
# table name for custom relation all starts with x_, see __init__
|
||||
if not rel.startswith('x_'):
|
||||
model._save_relation_table(rel, self._module)
|
||||
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (rel,))
|
||||
if not cr.dictfetchall():
|
||||
if self.comodel_name not in model.env:
|
||||
raise UserError(_('Many2many comodel does not exist: %r') % (self.comodel_name,))
|
||||
comodel = model.env[self.comodel_name]
|
||||
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s"))' % (rel, id1, id2, id1, id2))
|
||||
# create foreign key references with ondelete=cascade, unless the targets are SQL views
|
||||
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (comodel._table,))
|
||||
if not cr.fetchall():
|
||||
model._m2o_add_foreign_key_unchecked(rel, id2, comodel, 'cascade', self._module)
|
||||
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (model._table,))
|
||||
if not cr.fetchall():
|
||||
model._m2o_add_foreign_key_unchecked(rel, id1, model, 'cascade', self._module)
|
||||
|
||||
cr.execute('CREATE INDEX ON "%s" ("%s")' % (rel, id1))
|
||||
cr.execute('CREATE INDEX ON "%s" ("%s")' % (rel, id2))
|
||||
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (rel, model._table, comodel._table))
|
||||
cr.commit()
|
||||
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", rel, model._table, comodel._table)
|
||||
return True
|
||||
|
||||
def read(self, records):
|
||||
comodel = records.env[self.comodel_name]
|
||||
|
||||
@@ -2434,6 +2485,6 @@ class Id(Field):
|
||||
|
||||
# imported here to avoid dependency cycle issues
|
||||
from openerp import SUPERUSER_ID
|
||||
from .exceptions import AccessError, MissingError
|
||||
from .exceptions import AccessError, MissingError, UserError
|
||||
from .models import check_pg_name, BaseModel, IdType
|
||||
from .osv import fields
|
||||
|
||||
+4
-49
@@ -2399,12 +2399,9 @@ class BaseModel(object):
|
||||
# Don't update custom (also called manual) fields
|
||||
continue
|
||||
|
||||
if field.type == 'one2many':
|
||||
self._o2m_raise_on_missing_reference(field.column)
|
||||
|
||||
elif field.type == 'many2many':
|
||||
res = self._m2m_raise_or_create_relation(field.column)
|
||||
if res and field.compute:
|
||||
if not field.column_type:
|
||||
# the field is not stored as a column
|
||||
if field.check_schema(self) and field.compute:
|
||||
stored_fields.append(field)
|
||||
|
||||
else:
|
||||
@@ -2537,7 +2534,7 @@ class BaseModel(object):
|
||||
if comodel._auto and comodel._table != 'ir_actions':
|
||||
self._m2o_fix_foreign_key(self._table, name, comodel, field.ondelete)
|
||||
|
||||
elif field.column_type:
|
||||
else:
|
||||
# the column doesn't exist in database, create it
|
||||
cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, name, field.column_type[1]))
|
||||
cr.execute("COMMENT ON COLUMN %s.\"%s\" IS %%s" % (self._table, name), (field.string,))
|
||||
@@ -2673,48 +2670,6 @@ class BaseModel(object):
|
||||
self._cr.execute(query, (self._table,))
|
||||
return {row['attname']: row for row in self._cr.dictfetchall()}
|
||||
|
||||
@api.model_cr
|
||||
def _o2m_raise_on_missing_reference(self, column):
|
||||
# TODO this check should be a method on fields.one2many.
|
||||
if column._obj in self.env:
|
||||
other = self.env[column._obj]
|
||||
# TODO the condition could use fields_get_keys().
|
||||
if column._fields_id not in other._fields:
|
||||
raise UserError(_("There is no reference field '%s' found for '%s'") % (column._fields_id, column._obj))
|
||||
|
||||
@api.model_cr
|
||||
def _m2m_raise_or_create_relation(self, column):
|
||||
""" Create the table for the relation if necessary.
|
||||
Return ``True`` if the relation had to be created.
|
||||
"""
|
||||
cr = self._cr
|
||||
m2m_tbl, col1, col2 = column._sql_names(self)
|
||||
# do not create relations for custom fields as they do not belong to a module
|
||||
# they will be automatically removed when dropping the corresponding ir.model.field
|
||||
# table name for custom relation all starts with x_, see __init__
|
||||
if not m2m_tbl.startswith('x_'):
|
||||
self._save_relation_table(m2m_tbl, column._module)
|
||||
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
|
||||
if not cr.dictfetchall():
|
||||
if column._obj not in self.env:
|
||||
raise UserError(_('Many2Many destination model does not exist: `%s`') % (column._obj,))
|
||||
dest_model = self.env[column._obj]
|
||||
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s"))' % (m2m_tbl, col1, col2, col1, col2))
|
||||
# create foreign key references with ondelete=cascade, unless the targets are SQL views
|
||||
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (dest_model._table,))
|
||||
if not cr.fetchall():
|
||||
self._m2o_add_foreign_key_unchecked(m2m_tbl, col2, dest_model, 'cascade', column._module)
|
||||
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (self._table,))
|
||||
if not cr.fetchall():
|
||||
self._m2o_add_foreign_key_unchecked(m2m_tbl, col1, self, 'cascade', column._module)
|
||||
|
||||
cr.execute('CREATE INDEX ON "%s" ("%s")' % (m2m_tbl, col1))
|
||||
cr.execute('CREATE INDEX ON "%s" ("%s")' % (m2m_tbl, col2))
|
||||
cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (m2m_tbl, self._table, dest_model._table))
|
||||
cr.commit()
|
||||
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, dest_model._table)
|
||||
return True
|
||||
|
||||
@api.model_cr
|
||||
def _add_sql_constraints(self):
|
||||
"""
|
||||
|
||||
@@ -966,7 +966,7 @@ class expression(object):
|
||||
push(create_substitution_leaf(leaf, ('id', o2m_op, select_distinct_from_where_not_null(cr, column._fields_id, comodel._table)), model))
|
||||
|
||||
elif column._type == 'many2many':
|
||||
rel_table, rel_id1, rel_id2 = column._sql_names(model)
|
||||
rel_table, rel_id1, rel_id2 = field.relation, field.column1, field.column2
|
||||
|
||||
if operator in HIERARCHY_FUNCS:
|
||||
def _rec_convert(ids):
|
||||
|
||||
Reference in New Issue
Block a user