[IMP] fields: allow shared fields

Given the following variables:
    * Module A
    * Module B
    * Model M
    * Field X of model M

If module A defines M.X and is installed, an xmlid for this field is
generated in the form of A.field_M_X

Before this commit:

If module B defines M.X as well and is installed after module A, no
xmlid is generated.

This means that if module A is uninstalled, the single xmlid pointing to
M.X will be deleted and thus, the field itself will be deleted as well,
therefore any views from module B referencing M.X will crash.

After this commit:

If module B (or any subsequent modules) define M.X, an xmlid will be
generated in the form of B.field_M_X.

If module A is uninstalled, the xmlid A.field_M_X will be deleted, but
B.field_M_X will remain and thus the field itself won't be deleted.

This system means that for a single actual field, there can be multiple
xmlids, each xmlid sharing the same field, thus the name "shared
fields".

Task ID 38016
This commit is contained in:
Adrian Torres
2018-03-09 15:44:39 +01:00
committed by GitHub
parent ed4cbf57dc
commit bbfb098a54
2 changed files with 31 additions and 20 deletions
+25 -19
View File
@@ -746,39 +746,45 @@ class IrModelFields(models.Model):
fields_data = self._existing_field_data(field.model_name)
field_data = fields_data.get(field.name)
params = self._reflect_field_params(field)
cr = self.env.cr
created = False
if field_data is None:
cr = self.env.cr
# create an entry in this table
# does not exist, create an entry in this table
query_insert(cr, self._table, params)
record = self.browse(cr.fetchone())
self.pool.post_init(record.modified, list(params))
# create a corresponding xml id
module = field._module or self._context.get('module')
if module:
model_name = field.model_name.replace('.', '_')
xmlid = 'field_%s__%s' % (model_name, field.name)
cr.execute(""" INSERT INTO ir_model_data (module, name, model, res_id, date_init, date_update)
VALUES (%s, %s, %s, %s, (now() at time zone 'UTC'), (now() at time zone 'UTC')) """,
(module, xmlid, record._name, record.id))
# update fields_data (for recursive calls)
fields_data[field.name] = dict(params, id=record.id)
return record
created = True
diff = {key for key, val in params.items() if field_data[key] != val}
if diff:
cr = self.env.cr
# update the entry in this table
elif any(field_data[key] != val for key, val in params.items()):
# exists, update the entry in this table
query_update(cr, self._table, params, ['model', 'name'])
record = self.browse(cr.fetchone())
self.pool.post_init(record.modified, diff)
names = [key for key, val in params.items() if field_data[key] != val]
self.pool.post_init(record.modified, names)
# update fields_data (for recursive calls)
field_data.update(params)
return record
else:
# nothing to update, simply return the corresponding record
return self.browse(field_data['id'])
# exists, but nothing to update
record = self.browse(field_data['id'])
# generate xmlids if necessary, one per module defining the same field
module = self._context.get('module')
if module and (created or module in field._modules):
model_name = field.model_name.replace('.', '_')
xmlid = 'field_%s__%s' % (model_name, field.name)
cr.execute(
"""
INSERT INTO ir_model_data (module, name, model, res_id, date_init, date_update)
SELECT %s, %s, %s, %s, (now() at time zone 'UTC'), (now() at time zone 'UTC')
WHERE NOT EXISTS (SELECT id FROM ir_model_data WHERE module=%s AND name=%s)
""", (module, xmlid, record._name, record.id, module, xmlid)
)
return record
def _reflect_model(self, model):
""" Reflect the given model's fields. """
+6 -1
View File
@@ -272,8 +272,9 @@ class Field(MetaField('DummyField', (object,), {})):
'args': EMPTY_DICT, # the parameters given to __init__()
'_attrs': EMPTY_DICT, # the field's non-slot attributes
'_module': None, # the field's module name
'_modules': None, # modules that define this field
'_setup_done': None, # the field's setup state: None, 'base' or 'full'
'_sequence': None, # absolute ordering of the field
'_sequence': None, # absolute ordering of the field
'automatic': False, # whether the field is automatically created ("magic" field)
'inherited': False, # whether the field is inherited (_inherits)
@@ -391,16 +392,20 @@ class Field(MetaField('DummyField', (object,), {})):
def _get_attrs(self, model, name):
""" Return the field parameter attributes as a dictionary. """
# determine all inherited field attributes
modules = set()
attrs = {}
if not (self.args.get('automatic') or self.args.get('manual')):
# magic and custom fields do not inherit from parent classes
for field in reversed(resolve_mro(model, name, self._can_setup_from)):
attrs.update(field.args)
if '_module' in field.args:
modules.add(field.args['_module'])
attrs.update(self.args) # necessary in case self is not in class
attrs['args'] = self.args
attrs['model_name'] = model._name
attrs['name'] = name
attrs['_modules'] = modules
# initialize ``self`` with ``attrs``
if attrs.get('compute'):