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"