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()