From 10e31bc31173e4d80737ddbcaf1ccd176494d886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 15 Mar 2019 12:27:44 +0100 Subject: [PATCH] Add Model.__names__ to retrieve model and field names issue8159 review72431002 --- CHANGELOG | 1 + doc/ref/models/models.rst | 6 +++++ trytond/ir/model.py | 42 +++++++++++++++++++++++++++++++---- trytond/ir/translation.py | 12 ++++++++++ trytond/model/model.py | 13 +++++++++++ trytond/model/modelsql.py | 6 ++--- trytond/model/modelstorage.py | 33 +++++---------------------- trytond/tests/test_model.py | 20 +++++++++++++++++ 8 files changed, 99 insertions(+), 34 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b35236f890..ac7e675edb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add Model.__names__ to retrieve model and field names * Add size attribute on image tag * Allow to use channel to synchronize Cache * Display ids and rule names on access error diff --git a/doc/ref/models/models.rst b/doc/ref/models/models.rst index 4dac9fa184..46255d7aef 100644 --- a/doc/ref/models/models.rst +++ b/doc/ref/models/models.rst @@ -63,6 +63,12 @@ Class methods: Return the definition of each field on the model. +.. classmethod:: Model.__names__([field]) + + Returns a dictionary with the name of the `model` and the `field`. + It is a convenience-method used to format messages which should include + those names. + Instance methods: .. method:: Model.on_change(fieldnames) diff --git a/trytond/ir/model.py b/trytond/ir/model.py index 54c8a5b172..6288710708 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -66,6 +66,7 @@ class Model(ModelSQL, ModelView): global_search_p = fields.Boolean('Global Search') fields = fields.One2Many('ir.model.field', 'model', 'Fields', required=True) + _get_name_cache = Cache('ir.model.get_name') @classmethod def __setup__(cls): @@ -186,6 +187,19 @@ class Model(ModelSQL, ModelView): record.id, name, icon) return heapq.nlargest(int(limit), generate()) + @classmethod + def get_name(cls, model): + name = cls._get_name_cache.get(model) + if name is None: + models = cls.search([('model', '=', model)], limit=1) + if models: + model, = models + name = model.name + cls._get_name_cache.set(model, name) + else: + name = model + return name + class ModelField(ModelSQL, ModelView): "Model field" @@ -224,6 +238,7 @@ class ModelField(ModelSQL, ModelView): depends=['module']) module = fields.Char('Module', help="Module in which this field is defined") + _get_name_cache = Cache('ir.model.field.get_name') @classmethod def __setup__(cls): @@ -333,6 +348,22 @@ class ModelField(ModelSQL, ModelView): ('name',) + tuple(clause[1:]), ] + @classmethod + def get_name(cls, model, field): + name = cls._get_name_cache.get((model, field)) + if name is None: + fields = cls.search([ + ('model.model', '=', model), + ('name', '=', field), + ], limit=1) + if fields: + field, = fields + name = field.field_description + cls._get_name_cache.set((model, field), name) + else: + name = field + return name + @classmethod def read(cls, ids, fields_names): pool = Pool() @@ -511,6 +542,8 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView): @classmethod def check(cls, model_name, mode='read', raise_exception=True): 'Check access for model_name and mode' + pool = Pool() + Model = pool.get(model_name) assert mode in ['read', 'write', 'create', 'delete'], \ 'Invalid access mode for security' if ((Transaction().user == 0) @@ -522,7 +555,7 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView): if not access and access is not None: if raise_exception: raise AccessError(gettext( - 'ir.msg_access_rule_error', model=model_name)) + 'ir.msg_access_rule_error', **Model.__names__())) else: return False return True @@ -557,7 +590,7 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView): # XXX Can not check access right on instance method selection = [] for model_name, _ in selection: - if not cls.check(model_name, mode=mode, + if model_name and not cls.check(model_name, mode=mode, raise_exception=False): return False return True @@ -692,6 +725,8 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView): ''' Check access for fields on model_name. ''' + pool = Pool() + Model = pool.get(model_name) assert mode in ('read', 'write', 'create', 'delete'), \ 'Invalid access mode' if ((Transaction().user == 0) @@ -710,8 +745,7 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView): if raise_exception: raise AccessError( gettext('ir.msg_access_rule_field_error', - model=model_name, - field=field)) + **Model.__names__(field))) else: return False return True diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 6ebb38fa8b..c4d2b83d8c 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -621,7 +621,11 @@ class Translation(ModelSQL, ModelView): def delete(cls, translations): pool = Pool() Message = pool.get('ir.message') + Model = pool.get('ir.model') + ModelField = pool.get('ir.model.field') Message._message_cache.clear() + Model._get_name_cache.clear() + ModelField._get_name_cache.clear() cls._translation_cache.clear() ModelView._fields_view_get_cache.clear() return super(Translation, cls).delete(translations) @@ -630,7 +634,11 @@ class Translation(ModelSQL, ModelView): def create(cls, vlist): pool = Pool() Message = pool.get('ir.message') + Model = pool.get('ir.model') + ModelField = pool.get('ir.model.field') Message._message_cache.clear() + Model._get_name_cache.clear() + ModelField._get_name_cache.clear() cls._translation_cache.clear() ModelView._fields_view_get_cache.clear() vlist = [x.copy() for x in vlist] @@ -645,7 +653,11 @@ class Translation(ModelSQL, ModelView): def write(cls, *args): pool = Pool() Message = pool.get('ir.message') + Model = pool.get('ir.model') + ModelField = pool.get('ir.model.field') Message._message_cache.clear() + Model._get_name_cache.clear() + ModelField._get_name_cache.clear() cls._translation_cache.clear() ModelView._fields_view_get_cache.clear() return super(Translation, cls).write(*args) diff --git a/trytond/model/model.py b/trytond/model/model.py index 15d70e1d14..0928b92367 100644 --- a/trytond/model/model.py +++ b/trytond/model/model.py @@ -203,6 +203,19 @@ class Model(URLMixin, PoolBase, metaclass=ModelMeta): def pre_validate(self): pass + @classmethod + def __names__(cls, field=None): + pool = Pool() + IrModel = pool.get('ir.model') + IrModelField = pool.get('ir.model.field') + + names = { + 'model': IrModel.get_name(cls.__name__), + } + if field: + names['field'] = IrModelField.get_name(cls.__name__, field) + return names + def __init__(self, id=None, **kwargs): super(Model, self).__init__() if id is not None: diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 04680053af..74d0b1d504 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -352,7 +352,7 @@ class ModelSQL(ModelStorage): if values.get(field_name) is None: raise RequiredValidationError( gettext('ir.msg_required_validation_record', - **cls._get_error_args(field_name))) + **cls.__names__(field_name))) for name, _, error in cls._sql_constraints: if TableHandler.convert_name(name) in str(exception): raise SQLConstraintError(gettext(error)) @@ -374,7 +374,7 @@ class ModelSQL(ModelStorage): if not ((target_records or (values[field_name] in create_records)) and (values[field_name] not in delete_records)): - error_args = cls._get_error_args(field_name) + error_args = cls.__names__(field_name) error_args['value'] = values[field_name] raise ForeignKeyError( gettext('ir.msg_foreign_model_missing', @@ -1114,7 +1114,7 @@ class ModelSQL(ModelStorage): if Model.search([ (field_name, 'in', sub_ids), ], order=[]): - error_args = Model._get_error_args(field_name) + error_args = Model.__names__(field_name) raise ForeignKeyError( gettext('ir.msg_foreign_model_exist', **error_args)) diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 7afe3762a8..650142bd67 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -928,27 +928,6 @@ class ModelStorage(Model): return False return True - @classmethod - def _get_error_args(cls, field_name): - pool = Pool() - ModelField = pool.get('ir.model.field') - error_args = { - 'field': field_name, - 'model': cls.__name__ - } - if ModelField: - model_fields = ModelField.search([ - ('name', '=', field_name), - ('model.model', '=', cls.__name__), - ], limit=1) - if model_fields: - model_field, = model_fields - error_args.update({ - 'field': model_field.field_description, - 'model': model_field.model.name, - }) - return error_args - @classmethod def validate(cls, records): pass @@ -1067,7 +1046,7 @@ class ModelStorage(Model): if sub_relations != set(finds): raise DomainValidationError( gettext('ir.msg_domain_validation_record', - **cls._get_error_args(field.name))) + **cls.__names__(field.name))) field_names = set(field_names or []) function_fields = {name for name, field in cls._fields.items() @@ -1094,7 +1073,7 @@ class ModelStorage(Model): and not isinstance(value, ModelStorage))): raise RequiredValidationError( gettext('ir.msg_required_validation_record', - **cls._get_error_args(field_name))) + **cls.__names__(field_name))) # validate states required if field.states and 'required' in field.states: if is_pyson(field.states['required']): @@ -1137,7 +1116,7 @@ class ModelStorage(Model): field_size = field.size size = len(getattr(record, field_name) or '') if (size > field_size >= 0): - error_args = cls._get_error_args(field_name) + error_args = cls.__names__(field_name) error_args['size'] = size error_args['max_size'] = field_size raise SizeValidationError( @@ -1146,7 +1125,7 @@ class ModelStorage(Model): def digits_test(value, digits, field_name): def raise_error(value): - error_args = cls._get_error_args(field_name) + error_args = cls.__names__(field_name) error_args['digits'] = digits[1] error_args['value'] = repr(value) raise DigitsValidationError( @@ -1205,7 +1184,7 @@ class ModelStorage(Model): test.add('') test.add(None) if value not in test: - error_args = cls._get_error_args(field_name) + error_args = cls.__names__(field_name) error_args['value'] = value raise SelectionValidationError( gettext('ir.msg_selection_validation_record', @@ -1218,7 +1197,7 @@ class ModelStorage(Model): value = value.time() if value != datetime.datetime.strptime( value.strftime(format), format).time(): - error_args = cls._get_error_args(field_name) + error_args = cls.__names__(field_name) error_args['value'] = value raise TimeFormatValidationError( gettext('ir.msg_time_format_validation_record', diff --git a/trytond/tests/test_model.py b/trytond/tests/test_model.py index cb1469863b..9eb1e608b9 100644 --- a/trytond/tests/test_model.py +++ b/trytond/tests/test_model.py @@ -56,6 +56,26 @@ class ModelTestCase(unittest.TestCase): self.assertEqual(record.parent.name, "Test 1") self.assertEqual(record.parent.parent.name, "Test 2") + @with_transaction() + def test_names_model(self): + "Test __names__ for model only" + pool = Pool() + Model = pool.get('test.model') + + names = Model.__names__() + + self.assertEqual(names, {'model': "Model"}) + + @with_transaction() + def test_names_field(self): + "Test __names__ with field" + pool = Pool() + Model = pool.get('test.model') + + names = Model.__names__('name') + + self.assertEqual(names, {'model': "Model", 'field': "Name"}) + def suite(): return unittest.TestLoader().loadTestsFromTestCase(ModelTestCase)