changeset e19329d25a6f in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=e19329d25a6f
description:
        Add option to remove leading and trailing white spaces from char

        issue7914
        review340511008
diffstat:

 CHANGELOG                        |    1 +
 doc/ref/fields.rst               |    9 +++-
 trytond/ir/configuration.py      |    2 +-
 trytond/ir/lang.py               |   18 +++---
 trytond/ir/sequence.py           |    4 +-
 trytond/ir/session.py            |    2 +-
 trytond/model/fields/char.py     |   46 +++++++++++++++++-
 trytond/model/fields/text.py     |    3 +
 trytond/model/modelsql.py        |    6 +-
 trytond/res/user.py              |   10 +-
 trytond/tests/field_char.py      |    6 ++
 trytond/tests/test_field_char.py |  100 +++++++++++++++++++++++++++++++++++++++
 12 files changed, 186 insertions(+), 21 deletions(-)

diffs (426 lines):

diff -r befb3e825e20 -r e19329d25a6f CHANGELOG
--- a/CHANGELOG Mon Sep 19 09:03:03 2022 +0200
+++ b/CHANGELOG Mon Sep 19 21:25:55 2022 +0200
@@ -1,3 +1,4 @@
+* Add strip option to Char fields
 * Strip only one wildcard
 * Manage web extension origin as null
 * Remove same types restriction on PYSON If
diff -r befb3e825e20 -r e19329d25a6f doc/ref/fields.rst
--- a/doc/ref/fields.rst        Mon Sep 19 09:03:03 2022 +0200
+++ b/doc/ref/fields.rst        Mon Sep 19 21:25:55 2022 +0200
@@ -264,7 +264,7 @@
 Char
 ----
 
-.. class:: Char(string[, size[, translate[, \**options]]])
+.. class:: Char(string[, size[, translate[, strip[, \**options]]]])
 
    A single line :py:class:`string <str>` field.
 
@@ -291,6 +291,13 @@
    The value readed and stored will depend on the ``language`` defined in the
    context.
 
+.. attribute:: Char.strip
+
+   If ``True``, leading and trailing whitespace are removed.
+   If ``leading``, leading whitespace are removed.
+   If ``trailing``, trailing whitespace are removed.
+   The default value is ``True``.
+
 .. attribute:: Char.autocomplete
 
    A set of field names.
diff -r befb3e825e20 -r e19329d25a6f trytond/ir/configuration.py
--- a/trytond/ir/configuration.py       Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/ir/configuration.py       Mon Sep 19 21:25:55 2022 +0200
@@ -9,7 +9,7 @@
     'Configuration'
     __name__ = 'ir.configuration'
     language = fields.Char('language')
-    hostname = fields.Char("Hostname")
+    hostname = fields.Char("Hostname", strip=False)
     _get_language_cache = Cache('ir_configuration.get_language')
 
     @staticmethod
diff -r befb3e825e20 -r e19329d25a6f trytond/ir/lang.py
--- a/trytond/ir/lang.py        Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/ir/lang.py        Mon Sep 19 21:25:55 2022 +0200
@@ -52,24 +52,26 @@
             ], 'Direction', required=True)
 
     # date
-    date = fields.Char('Date', required=True)
+    date = fields.Char("Date", required=True, strip=False)
 
-    am = fields.Char("AM")
-    pm = fields.Char("PM")
+    am = fields.Char("AM", strip=False)
+    pm = fields.Char("PM", strip=False)
 
     # number
     grouping = fields.Char('Grouping', required=True)
-    decimal_point = fields.Char('Decimal Separator', required=True)
-    thousands_sep = fields.Char('Thousands Separator')
+    decimal_point = fields.Char(
+        "Decimal Separator", required=True, strip=False)
+    thousands_sep = fields.Char("Thousands Separator", strip=False)
 
     # monetary formatting
     mon_grouping = fields.Char('Grouping', required=True)
-    mon_decimal_point = fields.Char('Decimal Separator', required=True)
+    mon_decimal_point = fields.Char(
+        "Decimal Separator", required=True, strip=False)
     mon_thousands_sep = fields.Char('Thousands Separator')
     p_sign_posn = fields.Integer('Positive Sign Position', required=True)
     n_sign_posn = fields.Integer('Negative Sign Position', required=True)
-    positive_sign = fields.Char('Positive Sign')
-    negative_sign = fields.Char('Negative Sign')
+    positive_sign = fields.Char("Positive Sign", strip=False)
+    negative_sign = fields.Char("Negative Sign", strip=False)
     p_cs_precedes = fields.Boolean('Positive Currency Symbol Precedes')
     n_cs_precedes = fields.Boolean('Negative Currency Symbol Precedes')
     p_sep_by_space = fields.Boolean('Positive Separate by Space')
diff -r befb3e825e20 -r e19329d25a6f trytond/ir/sequence.py
--- a/trytond/ir/sequence.py    Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/ir/sequence.py    Mon Sep 19 21:25:55 2022 +0200
@@ -60,8 +60,8 @@
             'readonly': Eval('id', -1) >= 0,
             },
         depends=['id'])
-    prefix = fields.Char('Prefix')
-    suffix = fields.Char('Suffix')
+    prefix = fields.Char('Prefix', strip='leading')
+    suffix = fields.Char('Suffix', strip='trailing')
     type = fields.Selection([
         ('incremental', 'Incremental'),
         ('decimal timestamp', 'Decimal Timestamp'),
diff -r befb3e825e20 -r e19329d25a6f trytond/ir/session.py
--- a/trytond/ir/session.py     Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/ir/session.py     Mon Sep 19 21:25:55 2022 +0200
@@ -13,7 +13,7 @@
     __name__ = 'ir.session'
     _rec_name = 'key'
 
-    key = fields.Char('Key', required=True, select=True)
+    key = fields.Char('Key', required=True, select=True, strip=False)
 
     @classmethod
     def __setup__(cls):
diff -r befb3e825e20 -r e19329d25a6f trytond/model/fields/char.py
--- a/trytond/model/fields/char.py      Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/model/fields/char.py      Mon Sep 19 21:25:55 2022 +0200
@@ -1,8 +1,11 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+import string
 import warnings
 
+from sql import Expression, Query
 from sql.conditionals import Coalesce, NullIf
+from sql.functions import Trim
 from sql.operators import Not
 
 from trytond.rpc import RPC
@@ -25,7 +28,7 @@
     def __init__(self, string='', size=None, help='', required=False,
             readonly=False, domain=None, states=None, translate=False,
             select=False, on_change=None, on_change_with=None, depends=None,
-            context=None, loading=None, autocomplete=None):
+            context=None, loading=None, autocomplete=None, strip=True):
         '''
         :param translate: A boolean. If ``True`` the field is translatable.
         :param size: A integer. If set defines the maximum size of the values.
@@ -41,6 +44,7 @@
             warnings.warn('autocomplete argument is deprecated, use the '
                 'depends decorator', DeprecationWarning, stacklevel=2)
             self.autocomplete.update(autocomplete)
+        self.strip = strip
         self.translate = translate
         self.__size = None
         self.size = size
@@ -56,12 +60,51 @@
     size = property(_get_size, _set_size)
 
     @property
+    def strip(self):
+        return self.__strip
+
+    @strip.setter
+    def strip(self, value):
+        assert value in {False, True, 'leading', 'trailing'}
+        self.__strip = value
+
+    @property
     def _sql_type(self):
         if isinstance(self.size, int):
             return 'VARCHAR(%s)' % self.size
         else:
             return 'VARCHAR'
 
+    def __set__(self, inst, value):
+        if isinstance(value, str) and self.strip:
+            if self.strip == 'leading':
+                value = value.lstrip()
+            elif self.strip == 'trailing':
+                value = value.rstrip()
+            else:
+                value = value.strip()
+        super().__set__(inst, value)
+
+    def sql_format(self, value):
+        if value is not None and self.strip:
+            if isinstance(value, (Query, Expression)):
+                if self.strip == 'leading':
+                    position = 'LEADING'
+                elif self.strip == 'trailing':
+                    position = 'TRAILING'
+                else:
+                    position = 'BOTH'
+                value = Trim(
+                    value, position=position, characters=string.whitespace)
+            else:
+                if self.strip == 'leading':
+                    value = value.lstrip()
+                elif self.strip == 'trailing':
+                    value = value.rstrip()
+                else:
+                    value = value.strip()
+        return super().sql_format(value)
+
     def set_rpc(self, model):
         super(Char, self).set_rpc(model)
         if self.autocomplete:
@@ -188,6 +231,7 @@
     def definition(self, model, language):
         definition = super().definition(model, language)
         definition['autocomplete'] = list(self.autocomplete)
+        definition['strip'] = self.strip
         if self.size is not None:
             definition['size'] = self.size
         return definition
diff -r befb3e825e20 -r e19329d25a6f trytond/model/fields/text.py
--- a/trytond/model/fields/text.py      Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/model/fields/text.py      Mon Sep 19 21:25:55 2022 +0200
@@ -18,6 +18,9 @@
     forbidden_chars = ''
     search_full_text = True
 
+    def __init__(self, *args, strip=False, **kwarg):
+        super().__init__(*args, strip=strip, **kwarg)
+
 
 class FullText(Field):
     '''
diff -r befb3e825e20 -r e19329d25a6f trytond/model/modelsql.py
--- a/trytond/model/modelsql.py Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/model/modelsql.py Mon Sep 19 21:25:55 2022 +0200
@@ -698,7 +698,8 @@
                 if (getattr(field, 'translate', False)
                         and not hasattr(field, 'set')):
                     translation_values.setdefault(
-                        '%s,%s' % (cls.__name__, fname), {})[new_id] = value
+                        '%s,%s' % (cls.__name__, fname), {})[new_id] = (
+                            field.sql_format(value))
                 if hasattr(field, 'set'):
                     args = fields_to_set.setdefault(fname, [])
                     actions = iter(args)
@@ -1113,7 +1114,8 @@
                         and not hasattr(field, 'set')):
                     Translation.set_ids(
                         '%s,%s' % (cls.__name__, fname), 'model',
-                        transaction.language, ids, [value] * len(ids))
+                        transaction.language, ids,
+                        [field.sql_format(value)] * len(ids))
                 if hasattr(field, 'set'):
                     fields_to_set.setdefault(fname, []).extend((ids, value))
 
diff -r befb3e825e20 -r e19329d25a6f trytond/res/user.py
--- a/trytond/res/user.py       Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/res/user.py       Mon Sep 19 21:25:55 2022 +0200
@@ -112,7 +112,7 @@
     __name__ = "res.user"
     name = fields.Char('Name', select=True)
     login = fields.Char('Login', required=True)
-    password_hash = fields.Char('Password Hash')
+    password_hash = fields.Char('Password Hash', strip=False)
     password = fields.Function(fields.Char(
             "Password",
             states={
@@ -120,7 +120,7 @@
                 }),
         getter='get_password', setter='set_password')
     password_reset = fields.Char(
-        "Reset Password",
+        "Reset Password", strip=False,
         states={
             'invisible': not _has_password,
             })
@@ -812,7 +812,7 @@
     """
     __name__ = 'res.user.login.attempt'
     login = fields.Char('Login', size=512)
-    device_cookie = fields.Char("Device Cookie")
+    device_cookie = fields.Char("Device Cookie", strip=False)
     ip_address = fields.Char("IP Address")
     ip_network = fields.Char("IP Network")
 
@@ -896,7 +896,7 @@
     __name__ = 'res.user.device'
 
     login = fields.Char("Login", required=True)
-    cookie = fields.Char("Cookie", readonly=True, required=True)
+    cookie = fields.Char("Cookie", readonly=True, required=True, strip=False)
 
     @classmethod
     def __setup__(cls):
@@ -1082,7 +1082,7 @@
     __name__ = 'res.user.application'
     _rec_name = 'key'
 
-    key = fields.Char("Key", required=True, select=True)
+    key = fields.Char("Key", required=True, select=True, strip=False)
     user = fields.Many2One('res.user', "User", select=True)
     application = fields.Selection([], "Application")
     state = fields.Selection([
diff -r befb3e825e20 -r e19329d25a6f trytond/tests/field_char.py
--- a/trytond/tests/field_char.py       Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/tests/field_char.py       Mon Sep 19 21:25:55 2022 +0200
@@ -10,6 +10,9 @@
     'Char'
     __name__ = 'test.char'
     char = fields.Char("Char")
+    char_lstripped = fields.Char("Char", strip='leading')
+    char_rstripped = fields.Char("Char", strip='trailing')
+    char_unstripped = fields.Char("Char", strip=False)
 
 
 class CharDefault(ModelSQL):
@@ -47,6 +50,9 @@
     'Char Translate'
     __name__ = 'test.char_translate'
     char = fields.Char("Char", translate=True)
+    char_lstripped = fields.Char("Char", strip='leading', translate=True)
+    char_rstripped = fields.Char("Char", strip='trailing', translate=True)
+    char_unstripped = fields.Char("Char", strip=False, translate=True)
 
 
 class CharUnaccentedOn(ModelSQL):
diff -r befb3e825e20 -r e19329d25a6f trytond/tests/test_field_char.py
--- a/trytond/tests/test_field_char.py  Mon Sep 19 09:03:03 2022 +0200
+++ b/trytond/tests/test_field_char.py  Mon Sep 19 21:25:55 2022 +0200
@@ -323,6 +323,64 @@
 
         self.assertEqual(char.char, "é")
 
+    @with_transaction()
+    def test_create_strip(self):
+        "Test create with stripping"
+        Char = self.Char()
+
+        char, = Char.create([{
+                    'char': " Foo ",
+                    'char_lstripped': " Foo ",
+                    'char_rstripped': " Foo ",
+                    'char_unstripped': " Foo ",
+                    }])
+
+        read_record = Char(char.id)
+        self.assertEqual(read_record.char, "Foo")
+        self.assertEqual(read_record.char_lstripped, "Foo ")
+        self.assertEqual(read_record.char_rstripped, " Foo")
+        self.assertEqual(read_record.char_unstripped, " Foo ")
+
+    @with_transaction()
+    def test_write_strip(self):
+        "Test write with stripping"
+        Char = self.Char()
+
+        char, = Char.create([{
+                    'char': "Foo",
+                    'char_lstripped': "Foo",
+                    'char_rstripped': "Foo",
+                    'char_unstripped': "Foo",
+                    }])
+
+        Char.write([char], {
+                'char': " Bar ",
+                'char_lstripped': " Bar ",
+                'char_rstripped': " Bar ",
+                'char_unstripped': " Bar ",
+                })
+        read_record = Char(char.id)
+        self.assertEqual(read_record.char, "Bar")
+        self.assertEqual(read_record.char_lstripped, "Bar ")
+        self.assertEqual(read_record.char_rstripped, " Bar")
+        self.assertEqual(read_record.char_unstripped, " Bar ")
+
+    @with_transaction()
+    def test_set_strip(self):
+        "Test set with stripping"
+        Char = self.Char()
+
+        char = Char()
+
+        char.char = " Foo "
+        char.char_lstripped = " Foo "
+        char.char_rstripped = " Foo "
+        char.char_unstripped = " Foo "
+        self.assertEqual(char.char, "Foo")
+        self.assertEqual(char.char_lstripped, "Foo ")
+        self.assertEqual(char.char_rstripped, " Foo")
+        self.assertEqual(char.char_unstripped, " Foo ")
+
 
 class FieldCharTestCase(unittest.TestCase, CommonTestCaseMixin):
     "Test Field Char"
@@ -507,6 +565,48 @@
                     'char': 'foobar',
                     })
 
+    @with_transaction()
+    def test_create_strip_with_sql_value(self):
+        "Test create with stripping with SQL value"
+        Char = self.Char()
+
+        char, = Char.create([{
+                    'char': Literal(" Foo "),
+                    'char_lstripped': Literal(" Foo "),
+                    'char_rstripped': Literal(" Foo "),
+                    'char_unstripped': Literal(" Foo "),
+                    }])
+
+        read_record = Char(char.id)
+        self.assertEqual(read_record.char, "Foo")
+        self.assertEqual(read_record.char_lstripped, "Foo ")
+        self.assertEqual(read_record.char_rstripped, " Foo")
+        self.assertEqual(read_record.char_unstripped, " Foo ")
+
+    @with_transaction()
+    def test_write_strip_with_sql_value(self):
+        "Test write with stripping with SQL value"
+        Char = self.Char()
+
+        char, = Char.create([{
+                    'char': "Foo",
+                    'char_lstripped': "Foo",
+                    'char_rstripped': "Foo",
+                    'char_unstripped': "Foo",
+                    }])
+
+        Char.write([char], {
+                'char': Literal(" Bar "),
+                'char_lstripped': Literal(" Bar "),
+                'char_rstripped': Literal(" Bar "),
+                'char_unstripped': Literal(" Bar "),
+                })
+        read_record = Char(char.id)
+        self.assertEqual(read_record.char, "Bar")
+        self.assertEqual(read_record.char_lstripped, "Bar ")
+        self.assertEqual(read_record.char_rstripped, " Bar")
+        self.assertEqual(read_record.char_unstripped, " Bar ")
+
 
 class FieldCharTranslatedTestCase(unittest.TestCase, CommonTestCaseMixin):
     "Test Field Char Translated"

Reply via email to