Store model name on ir.model.field

This commit is contained in:
Cédric Krier
2024-02-23 18:40:52 +01:00
parent 0dffe1087c
commit b60f0b7044
14 changed files with 116 additions and 118 deletions
+3 -3
View File
@@ -34,7 +34,7 @@ class Email(ModelSQL, ModelView):
recipients = fields.Many2One(
'ir.model.field', "Recipients",
domain=[
('model.model', '=', Eval('model')),
('model', '=', Eval('model')),
],
help="The field that contains the recipient(s).")
fallback_recipients = fields.Many2One(
@@ -49,7 +49,7 @@ class Email(ModelSQL, ModelView):
recipients_secondary = fields.Many2One(
'ir.model.field', "Secondary Recipients",
domain=[
('model.model', '=', Eval('model')),
('model', '=', Eval('model')),
],
help="The field that contains the secondary recipient(s).")
fallback_recipients_secondary = fields.Many2One(
@@ -64,7 +64,7 @@ class Email(ModelSQL, ModelView):
recipients_hidden = fields.Many2One(
'ir.model.field', "Hidden Recipients",
domain=[
('model.model', '=', Eval('model')),
('model', '=', Eval('model')),
],
help="The field that contains the hidden recipient(s).")
fallback_recipients_hidden = fields.Many2One(
@@ -34,17 +34,12 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
def _setup_notification(self, recipient_field='create_uid'):
pool = Pool()
Model = pool.get('ir.model')
ModelField = pool.get('ir.model.field')
Action = pool.get('ir.action')
Report = pool.get('ir.action.report')
User = pool.get('res.user')
NotificationEmail = pool.get('notification.email')
model, = Model.search([
('model', '=', User.__name__),
])
action = Action(name="Notification Email", type='ir.action.report')
action.save()
report = Report()
@@ -52,7 +47,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
report.action = action
report.template_extension = 'txt'
report.report_content = b'Hello ${records[0].name}'
report.model = model.model
report.model = User.__name__
report.save()
user = User(Transaction().user)
@@ -61,7 +56,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
notification_email = NotificationEmail()
notification_email.recipients, = ModelField.search([
('model.model', '=', model.model),
('model', '=', User.__name__),
('name', '=', recipient_field),
])
notification_email.content = report
+1
View File
@@ -1,3 +1,4 @@
* Store model name on ir.model.field
* Add fmany2one field
* Allow creation of foreign key on non primary key
* Include context information in access error
+6 -2
View File
@@ -16,10 +16,12 @@ CREATE TABLE ir_model (
PRIMARY KEY(id)
);
ALTER TABLE ir_model ADD CONSTRAINT ir_model_model_uniq UNIQUE (model);
CREATE TABLE ir_model_field (
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL
CONSTRAINT ir_model_field_id_positive CHECK(id >= 0),
model INTEGER,
model VARCHAR NOT NULL,
name VARCHAR NOT NULL,
relation VARCHAR,
field_description VARCHAR,
@@ -28,9 +30,11 @@ CREATE TABLE ir_model_field (
module VARCHAR,
"access" BOOL,
PRIMARY KEY(id),
FOREIGN KEY (model) REFERENCES ir_model(id) ON DELETE CASCADE
FOREIGN KEY (model) REFERENCES ir_model(model) ON DELETE CASCADE
);
ALTER TABLE ir_model_field ADD CONSTRAINT ir_model_field_name_model_uniq UNIQUE (name, model);
CREATE TABLE ir_ui_view (
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL
CONSTRAINT ir_ui_view_id_positive CHECK(id >= 0),
+1 -1
View File
@@ -22,7 +22,7 @@ CREATE TABLE ir_model (
CREATE TABLE ir_model_field (
id INTEGER PRIMARY KEY AUTOINCREMENT,
model INTEGER,
model VARCHAR,
name VARCHAR,
relation VARCHAR,
field_description VARCHAR,
+1 -1
View File
@@ -12,6 +12,7 @@ __all__ = ['register', 'routes']
def register():
Pool.register(
model.ModelField, # register first for model char migration
configuration.Configuration,
translation.Translation,
translation.TranslationSetStart,
@@ -42,7 +43,6 @@ def register():
action.ActionWizard,
action.ActionURL,
model.Model,
model.ModelField,
model.ModelAccess,
model.ModelFieldAccess,
model.ModelButton,
+2 -2
View File
@@ -314,11 +314,11 @@ class EmailTemplate(ModelSQL, ModelView):
]:
field = getattr(cls, field)
field.domain = [
('model', '=', Eval('model')),
('model_ref.id', '=', Eval('model', -1)),
['OR',
('relation', 'in', cls.email_models()),
[
('model.model', 'in', cls.email_models()),
('model', 'in', cls.email_models()),
('name', '=', 'id'),
],
]
+76 -68
View File
@@ -61,8 +61,7 @@ class Model(ModelSQL, ModelView):
module = fields.Char('Module',
help="Module in which this model is defined.", readonly=True)
global_search_p = fields.Boolean('Global Search')
fields = fields.One2Many('ir.model.field', 'model', 'Fields',
required=True)
fields = fields.One2Many('ir.model.field', 'model_ref', "Fields")
_get_names_cache = Cache('ir.model.get_names')
@classmethod
@@ -221,7 +220,11 @@ class Model(ModelSQL, ModelView):
return cls.get_names().get(model, model)
class ModelField(ModelSQL, ModelView):
class ModelField(
fields.fmany2one(
'model_ref', 'model', 'ir.model,model', "Model",
required=True, ondelete='CASCADE'),
ModelSQL, ModelView):
"Model field"
__name__ = 'ir.model.field'
name = fields.Char('Name', required=True,
@@ -234,12 +237,11 @@ class ModelField(ModelSQL, ModelView):
'readonly': Bool(Eval('module')),
},
depends=['module'])
model = fields.Many2One('ir.model', 'Model', required=True,
ondelete='CASCADE',
model = fields.Char(
"Model", required=True,
states={
'readonly': Bool(Eval('module')),
},
depends=['module'])
})
field_description = fields.Char('Field Description', translate=True,
loading='lazy',
states={
@@ -272,7 +274,7 @@ class ModelField(ModelSQL, ModelView):
@classmethod
def __setup__(cls):
super(ModelField, cls).__setup__()
cls.__access__.add('model')
cls.__access__.add('model_ref')
table = cls.__table__()
cls._sql_constraints += [
('name_model_uniq', Unique(table, table.name, table.model),
@@ -281,17 +283,37 @@ class ModelField(ModelSQL, ModelView):
cls._order.insert(0, ('name', 'ASC'))
@classmethod
def register(cls, model, module_name, model_id):
def __register__(cls, module):
pool = Pool()
Model = pool.get('ir.model')
transaction = Transaction()
cursor = transaction.connection.cursor()
table_h = cls.__table_handler__(module)
table = cls.__table__()
model = Model.__table__()
# Migration from 7.0: model as char
if (table_h.column_exist('model')
and table_h.column_is_type('model', 'INTEGER')):
table_h.column_rename('model', '_temp_model')
table_h.add_column('model', 'VARCHAR')
cursor.execute(*table.update(
[table.model], [model.model],
from_=[model],
where=table._temp_model == model.id))
table_h.drop_column('_temp_model')
super().__register__(module)
@classmethod
def register(cls, model, module_name, model_id):
cursor = Transaction().connection.cursor()
ir_model_field = cls.__table__()
ir_model = Model.__table__()
cursor.execute(*ir_model_field.join(ir_model,
condition=ir_model_field.model == ir_model.id
).select(ir_model_field.id.as_('id'),
cursor.execute(*ir_model_field
.select(
ir_model_field.id.as_('id'),
ir_model_field.name.as_('name'),
ir_model_field.field_description.as_('field_description'),
ir_model_field.ttype.as_('ttype'),
@@ -299,7 +321,7 @@ class ModelField(ModelSQL, ModelView):
ir_model_field.module.as_('module'),
ir_model_field.help.as_('help'),
ir_model_field.access.as_('access'),
where=ir_model.model == model.__name__))
where=ir_model_field.model == model.__name__))
model_fields = {f['name']: f for f in cursor_dict(cursor)}
for field_name, field in model._fields.items():
@@ -312,28 +334,41 @@ class ModelField(ModelSQL, ModelView):
access = field_name in model.__access__
if field_name not in model_fields:
cursor.execute(*ir_model_field.insert(
[ir_model_field.model, ir_model_field.name,
cursor.execute(*ir_model_field.insert([
ir_model_field.model,
ir_model_field.name,
ir_model_field.field_description,
ir_model_field.ttype, ir_model_field.relation,
ir_model_field.help, ir_model_field.module,
ir_model_field.access],
[[
model_id, field_name,
ir_model_field.ttype,
ir_model_field.relation,
ir_model_field.help,
ir_model_field.module,
ir_model_field.access,
], [[
model.__name__,
field_name,
field.string,
field._type, relation,
field.help, module_name,
access]]))
field._type,
relation,
field.help,
module_name,
access,
]]))
elif (model_fields[field_name]['field_description'] != field.string
or model_fields[field_name]['ttype'] != field._type
or model_fields[field_name]['relation'] != relation
or model_fields[field_name]['help'] != field.help
or model_fields[field_name]['access'] != access):
cursor.execute(*ir_model_field.update(
[ir_model_field.field_description,
ir_model_field.ttype, ir_model_field.relation,
ir_model_field.help, ir_model_field.access],
[field.string, field._type, relation, field.help,
cursor.execute(*ir_model_field.update([
ir_model_field.field_description,
ir_model_field.ttype,
ir_model_field.relation,
ir_model_field.help,
ir_model_field.access,
], [
field.string,
field._type,
relation,
field.help,
access],
where=(ir_model_field.id
== model_fields[field_name]['id'])))
@@ -341,14 +376,11 @@ class ModelField(ModelSQL, ModelView):
@classmethod
def clean(cls):
pool = Pool()
IrModel = pool.get('ir.model')
transaction = Transaction()
cursor = transaction.connection.cursor()
ir_model = IrModel.__table__()
ir_model_field = cls.__table__()
cursor.execute(*ir_model_field
.join(ir_model, condition=ir_model_field.model == ir_model.id)
.select(ir_model.model, ir_model_field.name, ir_model_field.id))
cursor.execute(*ir_model_field.select(
ir_model_field.model, ir_model_field.name, ir_model_field.id))
for model, field, id_ in cursor:
Model = pool.get(model)
if field not in Model._fields:
@@ -407,7 +439,6 @@ class ModelField(ModelSQL, ModelView):
def read(cls, ids, fields_names):
pool = Pool()
Translation = pool.get('ir.translation')
Model = pool.get('ir.model')
to_delete = []
if Transaction().context.get('language'):
@@ -425,46 +456,27 @@ class ModelField(ModelSQL, ModelView):
if (Transaction().context.get('language')
and ('field_description' in fields_names
or 'help' in fields_names)):
model_ids = set()
for rec in res:
if isinstance(rec['model'], (list, tuple)):
model_ids.add(rec['model'][0])
else:
model_ids.add(rec['model'])
model_ids = list(model_ids)
cursor = Transaction().connection.cursor()
model = Model.__table__()
cursor.execute(*model.select(model.id, model.model,
where=model.id.in_(model_ids)))
id2model = dict(cursor)
trans_args = []
for rec in res:
if isinstance(rec['model'], (list, tuple)):
model_id = rec['model'][0]
else:
model_id = rec['model']
if 'field_description' in fields_names:
trans_args.append((id2model[model_id] + ',' + rec['name'],
'field', Transaction().language, None))
trans_args.append((
rec['model'] + ',' + rec['name'],
'field', Transaction().language, None))
if 'help' in fields_names:
trans_args.append((id2model[model_id] + ',' + rec['name'],
trans_args.append((
rec['model'] + ',' + rec['name'],
'help', Transaction().language, None))
Translation.get_sources(trans_args)
for rec in res:
if isinstance(rec['model'], (list, tuple)):
model_id = rec['model'][0]
else:
model_id = rec['model']
if 'field_description' in fields_names:
res_trans = Translation.get_source(
id2model[model_id] + ',' + rec['name'],
rec['model'] + ',' + rec['name'],
'field', Transaction().language)
if res_trans:
rec['field_description'] = res_trans
if 'help' in fields_names:
res_trans = Translation.get_source(
id2model[model_id] + ',' + rec['name'],
rec['model'] + ',' + rec['name'],
'help', Transaction().language)
if res_trans:
rec['help'] = res_trans
@@ -760,11 +772,9 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
lambda: defaultdict(lambda: True)))
pool = Pool()
Model = pool.get('ir.model')
ModelField = pool.get('ir.model.field')
User = pool.get('res.user')
field_access = cls.__table__()
ir_model = Model.__table__()
model_field = ModelField.__table__()
groups = User.get_groups()
@@ -783,10 +793,8 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
cursor = Transaction().connection.cursor()
cursor.execute(*field_access.join(model_field,
condition=field_access.field == model_field.id
).join(ir_model,
condition=model_field.model == ir_model.id
).select(
ir_model.model,
model_field.model,
model_field.name,
Max(Case(
(field_access.perm_read == Literal(True), 1),
@@ -800,11 +808,11 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
Max(Case(
(field_access.perm_delete == Literal(True), 1),
else_=0)),
where=ir_model.model.in_(models)
where=model_field.model.in_(models)
& (field_access.active == Literal(True))
& (field_access.group.in_(groups or [-1])
| (field_access.group == Null)),
group_by=[ir_model.model, model_field.name]))
group_by=[model_field.model, model_field.name]))
for m, f, r, w, c, d in cursor:
accesses[m][f] = {'read': r, 'write': w, 'create': c, 'delete': d}
for model, maccesses in accesses.items():
+4 -4
View File
@@ -341,7 +341,7 @@ class Translation(ModelSQL, ModelView):
trans_args = []
for record in records:
if ttype in ('field', 'help'):
name = record.model.model + ',' + record.name
name = record.model + ',' + record.name
else:
name = record.model + ',' + field_name
trans_args.append((name, ttype, lang, None))
@@ -349,7 +349,7 @@ class Translation(ModelSQL, ModelView):
for record in records:
if ttype in ('field', 'help'):
name = record.model.model + ',' + record.name
name = record.model + ',' + record.name
else:
name = record.model + ',' + field_name
translations[record.id] = cls.get_source(name, ttype, lang)
@@ -358,7 +358,7 @@ class Translation(ModelSQL, ModelView):
if ttype in {'field', 'help'}:
try:
field = getattr(
pool.get(record.model.model), record.name)
pool.get(record.model), record.name)
except KeyError:
continue
translations[record.id] = ''
@@ -458,7 +458,7 @@ class Translation(ModelSQL, ModelView):
def get_name(record):
if ttype in ('field', 'help'):
return record.model.model + ',' + record.name
return record.model + ',' + record.name
else:
return record.model + ',' + field_name
+2 -2
View File
@@ -4,8 +4,8 @@ this repository contains the full copyright notices and license terms. -->
<form>
<label name="name"/>
<field name="name"/>
<label name="model"/>
<field name="model" readonly="1"/>
<label name="model_ref"/>
<field name="model_ref"/>
<label name="ttype"/>
<field name="ttype"/>
<label name="relation"/>
+1 -1
View File
@@ -3,7 +3,7 @@
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="2"/>
<field name="model" expand="1"/>
<field name="model_ref" expand="1"/>
<field name="ttype" optional="0"/>
<field name="relation" expand="1" optional="1"/>
<field name="field_description" expand="1" optional="0"/>
+6 -16
View File
@@ -559,15 +559,13 @@ class Field(object):
class FieldTranslate(Field):
def _get_translation_join(self, Model, name,
translation, model, table, from_, language):
def _get_translation_join(
self, Model, name, translation, table, from_, language):
if Model.__name__ == 'ir.model.field':
pool = Pool()
IrModel = pool.get('ir.model')
ModelData = pool.get('ir.model.data')
ModelField = pool.get('ir.model.field')
Translation = pool.get('ir.translation')
model = IrModel.__table__()
model_data = ModelData.__table__()
model_field = ModelField.__table__()
msg_trans = Translation.__table__()
@@ -593,14 +591,12 @@ class FieldTranslate(Field):
condition=Concat(
Concat(model_data.module, '.'),
model_data.fs_id) == getattr(model_field, name))
.join(model,
condition=model_field.model == model.id)
.select(
msg_trans.id.as_('id'),
Literal(-1).as_('res_id'),
msg_trans.value.as_('value'),
Concat(
Concat(model.model, ','),
Concat(model_field.model, ','),
model_field.name).as_('name'),
msg_trans.lang.as_('lang'),
Literal(type_).as_('type'),
@@ -628,9 +624,7 @@ class FieldTranslate(Field):
type_ = 'model'
res_id = -1
elif Model.__name__ == 'ir.model.field':
from_ = from_.join(model, 'LEFT',
condition=model.id == table.model)
name_ = Concat(Concat(model.model, ','), table.name)
name_ = Concat(Concat(table.model, ','), table.name)
if name == 'field_description':
type_ = 'field'
else:
@@ -652,16 +646,14 @@ class FieldTranslate(Field):
from trytond.ir.lang import get_parent_language
pool = Pool()
Translation = pool.get('ir.translation')
IrModel = pool.get('ir.model')
table = join = Model.__table__()
model = IrModel.__table__()
language = Transaction().language
column = None
while language:
translation = Translation.__table__()
translation, join = self._get_translation_join(
Model, name, translation, model, table, join, language)
Model, name, translation, table, join, language)
column = Coalesce(NullIf(column, ''), translation.value)
language = get_parent_language(language)
return table, join, column
@@ -690,7 +682,6 @@ class FieldTranslate(Field):
from trytond.ir.lang import get_parent_language
pool = Pool()
Translation = pool.get('ir.translation')
IrModel = pool.get('ir.model')
table, _ = tables[None]
join = table
language = Transaction().language
@@ -699,9 +690,8 @@ class FieldTranslate(Field):
key = name + '.translation-' + language
if key not in tables:
translation = Translation.__table__()
model = IrModel.__table__()
translation, join = self._get_translation_join(
Model, name, translation, model, table, table, language)
Model, name, translation, table, table, language)
if join.left == table:
tables[key] = {
None: (join.right, join.condition),
+5 -5
View File
@@ -430,7 +430,7 @@ class _ModelFieldAccessTestCase(unittest.TestCase):
pool = Pool()
Field = pool.get('ir.model.field')
field1, = Field.search([
('model.model', '=', 'test.access'),
('model', '=', 'test.access'),
('name', '=', name),
])
return field1
@@ -925,7 +925,7 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
Field = pool.get('ir.model.field')
FieldAccess = pool.get('ir.model.field.access')
field, = Field.search([
('model.model', '=', 'test.access.relate'),
('model', '=', 'test.access.relate'),
('name', '=', 'value'),
])
FieldAccess.create([{
@@ -947,7 +947,7 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
Field = pool.get('ir.model.field')
FieldAccess = pool.get('ir.model.field.access')
field, = Field.search([
('model.model', '=', 'test.access.relate'),
('model', '=', 'test.access.relate'),
('name', '=', 'value'),
])
FieldAccess.create([{
@@ -974,7 +974,7 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
Field = pool.get('ir.model.field')
FieldAccess = pool.get('ir.model.field.access')
field, = Field.search([
('model.model', '=', 'test.access.relate'),
('model', '=', 'test.access.relate'),
('name', '=', 'parent'),
])
FieldAccess.create([{
@@ -992,7 +992,7 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
Field = pool.get('ir.model.field')
FieldAccess = pool.get('ir.model.field.access')
field, = Field.search([
('model.model', '=', 'test.access.relate'),
('model', '=', 'test.access.relate'),
('name', '=', 'parent'),
])
FieldAccess.create([{
+6 -6
View File
@@ -66,7 +66,7 @@ class IrTestCase(ModuleTestCase):
field, = ModelField.search([
('field_description', '=', "Name"),
('model.model', '=', 'ir.lang'),
('model', '=', 'ir.lang'),
('module', '=', 'ir'),
])
self.assertEqual(field.field_description, "Name")
@@ -79,7 +79,7 @@ class IrTestCase(ModuleTestCase):
fields = ModelField.search([
('field_description', 'in', ["Name", "Code"]),
('model.model', '=', 'ir.lang'),
('model', '=', 'ir.lang'),
('module', '=', 'ir'),
])
self.assertEqual(
@@ -93,7 +93,7 @@ class IrTestCase(ModuleTestCase):
field, = ModelField.search([
('field_description', '=', "ID"),
('model.model', '=', 'ir.lang'),
('model', '=', 'ir.lang'),
('module', '=', 'ir'),
])
self.assertEqual(field.field_description, "ID")
@@ -392,7 +392,7 @@ class IrTestCase(ModuleTestCase):
admin.save()
model, = IrModel.search([('model', '=', 'res.user')])
field, = IrModelField.search([
('model', '=', model.id),
('model', '=', 'res.user'),
('name', '=', 'id'),
])
@@ -427,7 +427,7 @@ class IrTestCase(ModuleTestCase):
admin.save()
model, = IrModel.search([('model', '=', 'res.user')])
field, = IrModelField.search([
('model', '=', model.id),
('model', '=', 'res.user'),
('name', '=', 'id'),
])
@@ -453,7 +453,7 @@ class IrTestCase(ModuleTestCase):
admin.save()
model, = IrModel.search([('model', '=', 'res.user')])
field, = IrModelField.search([
('model', '=', model.id),
('model', '=', 'res.user'),
('name', '=', 'id'),
])