Add option to remove leading and trailing white spaces from char
issue7914 review340511008
This commit is contained in:
@@ -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
|
||||
|
||||
+8
-1
@@ -264,7 +264,7 @@ BigInteger
|
||||
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 @@ Char
|
||||
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.
|
||||
|
||||
@@ -9,7 +9,7 @@ class Configuration(ModelSingleton, ModelSQL):
|
||||
'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
|
||||
|
||||
+10
-8
@@ -52,24 +52,26 @@ class Lang(DeactivableMixin, ModelSQL, ModelView):
|
||||
], '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')
|
||||
|
||||
@@ -60,8 +60,8 @@ class Sequence(DeactivableMixin, ModelSQL, ModelView):
|
||||
'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'),
|
||||
|
||||
@@ -13,7 +13,7 @@ class Session(ModelSQL):
|
||||
__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):
|
||||
|
||||
@@ -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 @@ class Char(FieldTranslate):
|
||||
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 @@ class Char(FieldTranslate):
|
||||
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
|
||||
@@ -55,6 +59,15 @@ class Char(FieldTranslate):
|
||||
|
||||
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):
|
||||
@@ -62,6 +75,36 @@ class Char(FieldTranslate):
|
||||
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 @@ class Char(FieldTranslate):
|
||||
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
|
||||
|
||||
@@ -18,6 +18,9 @@ class Text(Char):
|
||||
forbidden_chars = ''
|
||||
search_full_text = True
|
||||
|
||||
def __init__(self, *args, strip=False, **kwarg):
|
||||
super().__init__(*args, strip=strip, **kwarg)
|
||||
|
||||
|
||||
class FullText(Field):
|
||||
'''
|
||||
|
||||
@@ -698,7 +698,8 @@ class ModelSQL(ModelStorage):
|
||||
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 @@ class ModelSQL(ModelStorage):
|
||||
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))
|
||||
|
||||
|
||||
+5
-5
@@ -112,7 +112,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
|
||||
__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 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
|
||||
}),
|
||||
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 @@ class LoginAttempt(ModelSQL):
|
||||
"""
|
||||
__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 @@ class UserDevice(ModelSQL):
|
||||
__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 @@ class UserApplication(Workflow, ModelSQL, ModelView):
|
||||
__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([
|
||||
|
||||
@@ -10,6 +10,9 @@ class Char(ModelSQL):
|
||||
'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 @@ class CharTranslate(ModelSQL):
|
||||
'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):
|
||||
|
||||
@@ -323,6 +323,64 @@ class CommonTestCaseMixin:
|
||||
|
||||
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 @@ class FieldCharTestCase(unittest.TestCase, CommonTestCaseMixin):
|
||||
'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"
|
||||
|
||||
Reference in New Issue
Block a user