changeset 1b42f8379b1e in trytond:default details: https://hg.tryton.org/trytond?cmd=changeset&node=1b42f8379b1e description: Include record name and value in validation error message
issue5581 review26711002 diffstat: CHANGELOG | 1 + doc/ref/models.rst | 5 ++- trytond/ir/message.xml | 20 ++++++++++----- trytond/model/model.py | 16 ++++++++++++- trytond/model/modelsql.py | 8 ++++-- trytond/model/modelstorage.py | 38 +++++++++++++------------------ trytond/tests/test_model.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 35 deletions(-) diffs (314 lines): diff -r 827a10296511 -r 1b42f8379b1e CHANGELOG --- a/CHANGELOG Mon Aug 01 10:57:00 2022 +0200 +++ b/CHANGELOG Sat Aug 06 23:05:40 2022 +0200 @@ -1,3 +1,4 @@ +* Include record name and value in validation error message * Add header parameter on export data * Enforce certificate validation for SMTP connection (issue11564) * Use singleton for TableHandler diff -r 827a10296511 -r 1b42f8379b1e doc/ref/models.rst --- a/doc/ref/models.rst Mon Aug 01 10:57:00 2022 +0200 +++ b/doc/ref/models.rst Sat Aug 06 23:05:40 2022 +0200 @@ -96,9 +96,10 @@ ``level`` defines the number of relations to include in the relation field definition. -.. classmethod:: Model.__names__([field]) +.. classmethod:: Model.__names__([field[, record]]) - Return a dictionary with the name of the ``model`` and the ``field``. + Return a dictionary with the name of the ``model``, the ``field`` and the + ``record`` and the ``value`` of the field. It is a convenience-method used to format messages which should include those names. diff -r 827a10296511 -r 1b42f8379b1e trytond/ir/message.xml --- a/trytond/ir/message.xml Mon Aug 01 10:57:00 2022 +0200 +++ b/trytond/ir/message.xml Sat Aug 06 23:05:40 2022 +0200 @@ -161,25 +161,31 @@ <field name="text">Syntax error for XML id: %(value)r in "%(field)s" of "%(model)s" (%(column)s).</field> </record> <record model="ir.message" id="msg_domain_validation_record"> - <field name="text">The value for field "%(field)s" in "%(model)s" is not valid according to its domain.</field> + <field name="text">The value "%(value)s" for field "%(field)s" in "%(record)s" of "%(model)s" is not valid according to its domain.</field> + </record> + <record model="ir.message" id="msg_required_validation"> + <field name="text">A value is required for field "%(field)s" in "%(model)s".</field> </record> <record model="ir.message" id="msg_required_validation_record"> - <field name="text">A value is required for field "%(field)s" in "%(model)s".</field> + <field name="text">A value is required for field "%(field)s" in "%(record)s" of "%(model)s".</field> + </record> + <record model="ir.message" id="msg_size_validation"> + <field name="text">The value "%(value)s" for field "%(field)s" in "%(model)s" is too long (%(size)i > %(max_size)i).</field> </record> <record model="ir.message" id="msg_size_validation_record"> - <field name="text">The value for field "%(field)s" in "%(model)s" is too long (%(size)i > %(max_size)i).</field> + <field name="text">The value "%(value)s" for field "%(field)s" in "%(record)s" of "%(model)s" is too long (%(size)i > %(max_size)i).</field> </record> <record model="ir.message" id="msg_digits_validation_record"> - <field name="text">The number of digits in the value "%(value)s" for field "%(field)s" in "%(model)s" exceeds the limit of "%(digits)i".</field> + <field name="text">The number of digits in the value "%(value)r" for field "%(field)s" in "%(record)s" of "%(model)s" exceeds the limit of "%(digits)i".</field> </record> <record model="ir.message" id="msg_forbidden_char_validation_record"> - <field name="text">The value "%(value)s" for field "%(field)s" in "%(model)s" contains some invalid chars "%(chars)s".</field> + <field name="text">The value "%(value)s" for field "%(field)s" in "%(record)s" of "%(model)s" contains some invalid chars "%(chars)s".</field> </record> <record model="ir.message" id="msg_selection_validation_record"> - <field name="text">The value "%(value)s" for field "%(field)s" in "%(model)s" is not one of the allowed options.</field> + <field name="text">The value "%(value)s" for field "%(field)s" in "%(record)s" of "%(model)s" is not one of the allowed options.</field> </record> <record model="ir.message" id="msg_time_format_validation_record"> - <field name="text">The time value "%(value)s" for field "%(field)s" in "%(model)s" is not valid.</field> + <field name="text">The time value "%(value)s" for field "%(field)s" in "%(record)s" of "%(model)s" is not valid.</field> </record> <record model="ir.message" id="msg_foreign_model_missing"> <field name="text">The value "%(value)s" for field "%(field)s" in "%(model)s" does not exist.</field> diff -r 827a10296511 -r 1b42f8379b1e trytond/model/model.py --- a/trytond/model/model.py Mon Aug 01 10:57:00 2022 +0200 +++ b/trytond/model/model.py Sat Aug 06 23:05:40 2022 +0200 @@ -234,7 +234,7 @@ pass @classmethod - def __names__(cls, field=None): + def __names__(cls, field=None, record=None): pool = Pool() IrModel = pool.get('ir.model') IrModelField = pool.get('ir.model.field') @@ -244,6 +244,20 @@ } if field: names['field'] = IrModelField.get_name(cls.__name__, field) + + if record: + try: + names['record'] = record.rec_name + except Exception: + names['record'] = record.id + if field: + value = getattr(record, field, None) + if isinstance(value, Model): + try: + value = value.rec_name + except Exception: + value = value.id + names['value'] = value return names def __init__(self, id=None, **kwargs): diff -r 827a10296511 -r 1b42f8379b1e trytond/model/modelsql.py --- a/trytond/model/modelsql.py Mon Aug 01 10:57:00 2022 +0200 +++ b/trytond/model/modelsql.py Sat Aug 06 23:05:40 2022 +0200 @@ -365,7 +365,7 @@ and field_name not in ('create_uid', 'create_date')): if values.get(field_name) is None: raise RequiredValidationError( - gettext('ir.msg_required_validation_record', + gettext('ir.msg_required_validation', **cls.__names__(field_name))) for name, _, error in cls._sql_constraints: if backend.TableHandler.convert_name(name) in str(exception): @@ -408,13 +408,15 @@ if (hasattr(field, 'size') and isinstance(field.size, int) and field.sql_type()): - size = len(values.get(field_name) or '') + value = values.get(field_name) or '' + size = len(value) if size > field.size: error_args = cls.__names__(field_name) + error_args['value'] = value error_args['size'] = size error_args['max_size'] = field.size raise SizeValidationError( - gettext('ir.msg_size_validation_record', **error_args)) + gettext('ir.msg_size_validation', **error_args)) @classmethod def history_revisions(cls, ids): diff -r 827a10296511 -r 1b42f8379b1e trytond/model/modelstorage.py --- a/trytond/model/modelstorage.py Mon Aug 01 10:57:00 2022 +0200 +++ b/trytond/model/modelstorage.py Sat Aug 06 23:05:40 2022 +0200 @@ -1313,7 +1313,7 @@ domain = domain.get(Relation.__class__, []) msg = gettext( 'ir.msg_domain_validation_record', - **cls.__names__(field.name)) + **cls.__names__(field.name, invalid_record)) fields = set() level = 0 if field not in Relation._fields.values(): @@ -1356,7 +1356,8 @@ validate_domain(field) - def required_test(value, field_name, field): + def required_test(record, field): + value = getattr(record, field.name) if (( isinstance(value, (type(None), bool, list, tuple, str, dict)) @@ -1365,7 +1366,7 @@ and not isinstance(value, ModelStorage))): raise RequiredValidationError( gettext('ir.msg_required_validation_record', - **cls.__names__(field_name))) + **cls.__names__(field_name, record))) # validate states required if field.states and 'required' in field.states: if is_pyson(field.states['required']): @@ -1375,18 +1376,15 @@ required = _record_eval_pyson( record, pyson_required, encoded=True) if required: - required_test(getattr(record, field_name), - field_name, field) + required_test(record, field) else: if field.states['required']: for record in records: - required_test(getattr(record, field_name), - field_name, field) + required_test(record, field) # validate required if field.required: for record in records: - required_test( - getattr(record, field_name), field_name, field) + required_test(record, field) # validate size if hasattr(field, 'size') and field.size is not None: for record in records: @@ -1396,7 +1394,7 @@ field_size = field.size size = len(getattr(record, field_name) or '') if (size > field_size >= 0): - error_args = cls.__names__(field_name) + error_args = cls.__names__(field_name, record) error_args['size'] = size error_args['max_size'] = field_size raise SizeValidationError( @@ -1405,9 +1403,8 @@ def digits_test(record, digits, field_name): def raise_error(value): - error_args = cls.__names__(field_name) + error_args = cls.__names__(field_name, record) error_args['digits'] = digits[1] - error_args['value'] = repr(value) raise DigitsValidationError( gettext('ir.msg_digits_validation_record', **error_args)) @@ -1447,8 +1444,7 @@ value = getattr(record, field_name) if value and any( c in value for c in field.forbidden_chars): - error_args = cls.__names__(field_name) - error_args['value'] = value + error_args = cls.__names__(field_name, record) error_args['chars'] = ','.join( repr(c) for c in field.forbidden_chars if c in value) @@ -1487,21 +1483,21 @@ values = value or [] for value in values: if value not in test: - error_args = cls.__names__(field_name) + error_args = cls.__names__(field_name, record) error_args['value'] = value raise SelectionValidationError(gettext( 'ir.msg_selection_validation_record', **error_args)) - def format_test(value, format, field_name): + def format_test(record, format, field_name): + value = getattr(record, field_name) if not value: return if not isinstance(value, datetime.time): value = value.time() if value != datetime.datetime.strptime( value.strftime(format), format).time(): - error_args = cls.__names__(field_name) - error_args['value'] = value + error_args = cls.__names__(field_name, record) raise TimeFormatValidationError( gettext('ir.msg_time_format_validation_record', **error_args)) @@ -1519,12 +1515,10 @@ env['context'] = Transaction().context env['active_id'] = record.id format = PYSONDecoder(env).decode(pyson_format) - format_test(getattr(record, field_name), format, - field_name) + format_test(record, format, field_name) else: for record in records: - format_test(getattr(record, field_name), - field.format, field_name) + format_test(record, field.format, field_name) for record in records: record.pre_validate() diff -r 827a10296511 -r 1b42f8379b1e trytond/tests/test_model.py --- a/trytond/tests/test_model.py Mon Aug 01 10:57:00 2022 +0200 +++ b/trytond/tests/test_model.py Sat Aug 06 23:05:40 2022 +0200 @@ -168,6 +168,57 @@ self.assertEqual(names, {'model': "Model", 'field': "Name"}) @with_transaction() + def test_names_record(self): + "test __names__ with record" + pool = Pool() + Model = pool.get('test.model') + + record = Model(name="Test") + record.save() + names = Model.__names__(record=record) + + self.assertEqual(names, {'model': "Model", 'record': "Test"}) + + @with_transaction() + def test_names_unsaved_record(self): + "test __names__ with unsaved record" + pool = Pool() + Model = pool.get('test.model') + + record = Model() + names = Model.__names__(record=record) + + self.assertEqual(names, {'model': "Model", 'record': record.id}) + + @with_transaction() + def test_names_value(self): + "test __names__ with value" + pool = Pool() + Model = pool.get('test.model') + + record = Model(name="Test") + names = Model.__names__(field='name', record=record) + + self.assertEqual( + names, { + 'model': "Model", 'field': "Name", + 'record': record.id, 'value': "Test"}) + + @with_transaction() + def test_names_unset_value(self): + "test __names__ with unset value" + pool = Pool() + Model = pool.get('test.model') + + record = Model() + names = Model.__names__(field='name', record=record) + + self.assertEqual( + names, { + 'model': "Model", 'field': "Name", + 'record': record.id, 'value': None}) + + @with_transaction() def test_fields_get_no_write_access(self): "Test field is readonly when no write access on it" pool = Pool()