From a130816cbdea0b44c3ad52336d0feec4e7355c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 15 Mar 2014 10:44:45 +0100 Subject: [PATCH] Add support of domain for non-relation field issue3743 review4181002 --- CHANGELOG | 1 + doc/ref/models/fields.rst | 6 -- trytond/model/model.py | 2 +- trytond/model/modelstorage.py | 106 +++++++++++++++------------------- trytond/tests/__init__.py | 1 + trytond/tests/test.py | 8 ++- trytond/tests/test_fields.py | 11 ++++ 7 files changed, 68 insertions(+), 67 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4f6ef2cb07..9afcfbc8aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add support of domain for non-relation field * Manage microseconds in JSON-RPC and XML-RPC * Remove Sha field * Add password widget diff --git a/doc/ref/models/fields.rst b/doc/ref/models/fields.rst index 62af938749..8316415a5b 100644 --- a/doc/ref/models/fields.rst +++ b/doc/ref/models/fields.rst @@ -49,12 +49,6 @@ If ``True``, the field is not editable in the client. Default is ``False``. A :ref:`domain ` constraint that will be applied on the field value. -.. warning:: - - For now it only works on relational fields like :class:`Many2One`, - :class:`One2Many` and :class:`Many2Many`. -.. - ``states`` ---------- diff --git a/trytond/model/model.py b/trytond/model/model.py index d7fabb339c..8563f4bfa0 100644 --- a/trytond/model/model.py +++ b/trytond/model/model.py @@ -276,6 +276,7 @@ class Model(WarningErrorMixin, URLMixin, PoolBase): 'loading', 'filename', 'selection_change_with', + 'domain', ): if getattr(cls._fields[field], arg, None) is not None: value = getattr(cls._fields[field], arg) @@ -352,7 +353,6 @@ class Model(WarningErrorMixin, URLMixin, PoolBase): relation = copy.copy( cls._fields[field].get_target().__name__) res[field]['relation'] = relation - res[field]['domain'] = copy.copy(cls._fields[field].domain) res[field]['context'] = copy.copy(cls._fields[field].context) res[field]['create'] = accesses.get(field, {}).get('create', True) diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index dc62378a72..5fb41b3e3d 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -920,6 +920,51 @@ class ModelStorage(Model): return True return False + def validate_domain(field): + if not field.domain: + return + if field._type in ('many2one', 'one2many'): + Relation = pool.get(field.model_name) + elif field._type in ('many2many', 'one2one'): + Relation = field.get_target() + else: + Relation = cls + if is_pyson(field.domain): + pyson_domain = PYSONEncoder().encode(field.domain) + for record in records: + env = EvalEnvironment(record, cls) + env.update(Transaction().context) + env['current_date'] = datetime.datetime.today() + env['time'] = time + env['context'] = Transaction().context + env['active_id'] = record.id + domain = PYSONDecoder(env).decode(pyson_domain) + validate_relation_domain( + field, [record], Relation, domain) + else: + validate_relation_domain( + field, records, Relation, field.domain) + + def validate_relation_domain(field, records, Relation, domain): + if Relation != cls: + relations = [] + for record in records: + if getattr(record, field.name): + if field._type in ('many2one', 'one2one'): + relations.append(getattr(record, field.name)) + else: + relations.extend(getattr(record, field.name)) + else: + relations = records + if relations: + finds = Relation.search(['AND', + [('id', 'in', [r.id for r in relations])], + domain, + ]) + if set(relations) != set(finds): + cls.raise_user_error('domain_validation_record', + error_args=cls._get_error_args(field.name)) + field_names = set(field_names or []) ctx_pref['active_test'] = False with Transaction().set_context(ctx_pref): @@ -931,65 +976,8 @@ class ModelStorage(Model): if isinstance(field, fields.Function) and \ not field.setter: continue - # validate domain - if (field._type in - ('many2one', 'many2many', 'one2many', 'one2one') - and field.domain): - if field._type in ('many2one', 'one2many'): - Relation = pool.get(field.model_name) - else: - Relation = field.get_target() - if is_pyson(field.domain): - pyson_domain = PYSONEncoder().encode(field.domain) - for record in records: - env = EvalEnvironment(record, cls) - env.update(Transaction().context) - env['current_date'] = datetime.datetime.today() - env['time'] = time - env['context'] = Transaction().context - env['active_id'] = record.id - domain = PYSONDecoder(env).decode(pyson_domain) - relation_ids = [] - if getattr(record, field_name): - if field._type in ('many2one', 'one2one'): - relation_ids.append( - getattr(record, field_name).id) - else: - relation_ids.extend( - [x.id for x in getattr(record, - field_name)]) - if relation_ids and not Relation.search([ - 'AND', - [('id', 'in', relation_ids)], - domain, - ]): - cls.raise_user_error( - 'domain_validation_record', - error_args=cls._get_error_args( - field_name)) - else: - relation_ids = [] - for record in records: - if getattr(record, field_name): - if field._type in ('many2one', 'one2one'): - relation_ids.append( - getattr(record, field_name).id) - else: - relation_ids.extend( - [x.id for x in getattr(record, - field_name)]) - if relation_ids: - finds = Relation.search([ - 'AND', - [('id', 'in', relation_ids)], - field.domain, - ]) - find_ids = map(int, finds) - if not set(relation_ids) == set(find_ids): - cls.raise_user_error( - 'domain_validation_record', - error_args=cls._get_error_args( - field_name)) + + validate_domain(field) def required_test(value, field_name): if (isinstance(value, (type(None), type(False), list, diff --git a/trytond/tests/__init__.py b/trytond/tests/__init__.py index 31b4ef8326..8da0e0a5b9 100644 --- a/trytond/tests/__init__.py +++ b/trytond/tests/__init__.py @@ -22,6 +22,7 @@ def register(): Integer, IntegerDefault, IntegerRequired, + IntegerDomain, Float, FloatDefault, FloatRequired, diff --git a/trytond/tests/test.py b/trytond/tests/test.py index d8637fd1a0..f6cbaf7530 100644 --- a/trytond/tests/test.py +++ b/trytond/tests/test.py @@ -8,7 +8,7 @@ from trytond.pyson import Eval __all__ = [ 'Boolean', 'BooleanDefault', - 'Integer', 'IntegerDefault', 'IntegerRequired', + 'Integer', 'IntegerDefault', 'IntegerRequired', 'IntegerDomain', 'Float', 'FloatDefault', 'FloatRequired', 'FloatDigits', 'Numeric', 'NumericDefault', 'NumericRequired', 'NumericDigits', 'Char', 'CharDefault', 'CharRequired', 'CharSize', 'CharTranslate', @@ -79,6 +79,12 @@ class IntegerRequired(ModelSQL): required=True) +class IntegerDomain(ModelSQL): + 'Integer Domain' + __name__ = 'test.integer_domain' + integer = fields.Integer('Integer', domain=[('integer', '>', 42)]) + + class Float(ModelSQL): 'Float' __name__ = 'test.float' diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py index 975adde1bb..b06b1a9e5b 100644 --- a/trytond/tests/test_fields.py +++ b/trytond/tests/test_fields.py @@ -29,6 +29,7 @@ class FieldsTestCase(unittest.TestCase): self.integer = POOL.get('test.integer') self.integer_default = POOL.get('test.integer_default') self.integer_required = POOL.get('test.integer_required') + self.integer_domain = POOL.get('test.integer_domain') self.float = POOL.get('test.float') self.float_default = POOL.get('test.float_default') @@ -389,6 +390,16 @@ class FieldsTestCase(unittest.TestCase): transaction.cursor.rollback() + def test0021integer(self): + 'Test Integer with domain' + with Transaction().start(DB_NAME, USER, context=CONTEXT): + self.integer_domain.create([{ + 'integer': 100, + }]) + self.assertRaises(UserError, self.integer_domain.create, [{ + 'integer': 10, + }]) + def test0030float(self): 'Test Float' with Transaction().start(DB_NAME, USER,