[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:
@@ -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
@@ -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'):
|
||||
|
||||
Reference in New Issue
Block a user