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

Reply via email to