Added domain inversion feature

This commit is contained in:
Nicolas Évrard committed 2011-01-24 11:10:20 +01:00
1 parent 6573eff501
commit 1f277dd067
10 files changed
+559 -123

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Added domain inversion feature
* Handle loading attribute on fields
* Use default format for value in wizard form
* Add One2One field
+1
View File
@@ -1,3 +1,4 @@
Copyright (C) 2010- Nicolas Évrard.
Copyright (C) 2007-2010 Cédric Krier.
Copyright (C) 2007-2010 Bertrand Chenal.
Copyright (C) 2008-2010 B2CK SPRL.
+3
View File
@@ -2,3 +2,6 @@
#this repository contains the full copyright notices and license terms.
from common import *
from datetime_strftime import *
from domain_inversion import domain_inversion, eval_domain, localize_domain, \
unlocalize_domain, merge, inverse_leaf
from environment import EvalEnvironment
+405
View File
@@ -0,0 +1,405 @@
#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 operator
import types
OPERATORS = {
'=': operator.eq,
'>': operator.gt,
'<': operator.lt,
'<=': operator.le,
'>=': operator.ge,
'!=': operator.ne,
'in': lambda a, b: operator.contains(b, a),
'not in': lambda a, b: not operator.contains(b, a),
# Those operators are not supported (yet ?)
'like': lambda a, b: True,
'ilike': lambda a, b: True,
'not like': lambda a, b: True,
'not ilike': lambda a, b: True,
'child_of': lambda a, b: True,
'not child_of': lambda a, b: True,
}
def locale_part(expression):
if '.' in expression:
fieldname, local = expression.split('.')
return local
return expression
def is_leaf(expression):
return (isinstance(expression, (list, tuple))
and len(expression) > 2
and isinstance(expression[1], basestring)
and expression[1] in OPERATORS)
def eval_leaf(part, context):
field, operand, value = part[:3]
if '.' in field:
# In the case where the leaf concerns a m2o then having a value in the
# evaluation context is deemed suffisant
return bool(context.get(field.split('.')[0]))
return OPERATORS[operand](context[field], value)
def inverse_leaf(domain):
if domain in ('AND', 'OR'):
return domain
elif is_leaf(domain):
if 'child_of' in domain[1]:
if len(domain) == 3:
return domain
else:
return [domain[3]] + domain[1:]
return domain
else:
return map(inverse_leaf, domain)
def eval_domain(domain, context, boolop=operator.and_):
"compute domain boolean value according to the context"
if is_leaf(domain):
return eval_leaf(domain, context)
elif not domain and boolop is operator.and_:
return True
elif not domain and boolop is operator.or_:
return False
elif domain[0] == 'AND':
return eval_domain(domain[1:], context)
elif domain[0] == 'OR':
return eval_domain(domain[1:], context, operator.or_)
else:
return boolop(eval_domain(domain[0], context),
eval_domain(domain[1:], context, boolop))
def localize_domain(domain):
"returns only locale part of domain. eg: langage.code -> code"
if domain in ('AND', 'OR', True, False):
return domain
elif is_leaf(domain):
return [locale_part(domain[0])] + domain[1:]
else:
return [localize_domain(part) for part in domain]
def unlocalize_domain(domain, fieldname):
if domain in ('AND', 'OR', True, False):
return domain
elif is_leaf(domain):
return ['%s.%s' % (fieldname, domain[0])] + domain[1:]
else:
return [unlocalize_domain(part, fieldname) for part in domain]
def simplify(domain):
"remove unused domain delimiter"
if is_leaf(domain):
return domain
elif domain in ('OR', 'AND'):
return domain
elif (isinstance(domain, list) and len(domain) == 1
and not is_leaf(domain[0])):
return simplify(domain[0])
elif (isinstance(domain, list) and len(domain) == 2
and domain[0] in ('AND', 'OR')):
return [simplify(domain[1])]
else:
return [simplify(branch) for branch in domain]
def merge(domain, domoperator=None):
if not domain or domain in ('AND', 'OR'):
return []
domain_type = 'OR' if domain[0] == 'OR' else 'AND'
if is_leaf(domain):
return [domain]
elif domoperator is None:
return [domain_type] + reduce(operator.add,
[merge(e, domain_type) for e in domain])
elif domain_type == domoperator:
return reduce(operator.add, [merge(e, domain_type) for e in domain])
else:
# without setting the domoperator
return [merge(domain)]
def parse(domain):
if is_leaf(domain):
return domain
elif not domain:
return And([])
elif domain[0] == 'OR':
return Or(domain[1:])
else:
return And(domain[1:] if domain[0] == 'AND' else domain)
def domain_inversion(domain, symbol, context=None):
"""compute an inversion of the domain eventually the context is used to
simplify the expression"""
if context is None:
context = {}
expression = parse(domain)
if symbol not in expression.variables:
return True
return expression.inverse(symbol, context)
class And(object):
def __init__(self, expressions):
self.branches = map(parse, expressions)
self.variables = set()
for expression in self.branches:
if is_leaf(expression):
self.variables.add(self.base(expression[0]))
elif isinstance(expression, And):
self.variables |= expression.variables
def base(self, expression):
return expression if '.' not in expression else expression.split('.')[0]
def inverse(self, symbol, context):
result = []
for part in self.branches:
if isinstance(part, And):
part_inversion = part.inverse(symbol, context)
evaluated = isinstance(part_inversion, types.BooleanType)
if not evaluated:
result.append(part_inversion)
elif part_inversion:
continue
else:
return False
elif is_leaf(part) and self.base(part[0]) == symbol:
result.append(part)
else:
field, _, _ = part
if (field not in context
or field in context and eval_leaf(part, context)):
result.append(True)
else:
return False
result = filter(lambda e: e is not True, result)
if result == []:
return True
else:
return simplify(result)
class Or(And):
def inverse(self, symbol, context):
result = []
known_variables = set(context.keys())
if (symbol not in self.variables
and not known_variables >= self.variables):
# In this case we don't know anything about this OR part, we
# consider it to be True (because people will have the constraint on
# this part later).
return True
for part in self.branches:
if isinstance(part, And):
part_inversion = part.inverse(symbol, context)
evaluated = isinstance(part_inversion, types.BooleanType)
if symbol not in part.variables:
if evaluated and part_inversion:
return True
continue
if not evaluated:
result.append(part_inversion)
elif part_inversion:
return True
else:
continue
elif is_leaf(part) and self.base(part[0]) == symbol:
result.append(part)
else:
field, _, _ = part
field = self.base(field)
if field in context and eval_leaf(part, context):
return True
elif field in context and not eval_leaf(part, context):
result.append(False)
result = filter(lambda e: e is not False, result)
if result == []:
return False
else:
return simplify(['OR'] + result)
# Test stuffs
def test_simple_inversion():
domain = [['x', '=', 3]]
assert domain_inversion(domain, 'x') == [['x', '=', 3]]
domain = []
assert domain_inversion(domain, 'x') == True
assert domain_inversion(domain, 'y') == True
assert domain_inversion(domain, 'x', {'x': 5}) == True
assert domain_inversion(domain, 'z', {'x': 7}) == True
domain = [['x.id', '>', 5]]
assert domain_inversion(domain, 'x') == [['x.id', '>', 5]]
def test_and_inversion():
domain = [['x', '=', 3], ['y', '>', 5]]
assert domain_inversion(domain, 'x') == [['x', '=', 3]]
assert domain_inversion(domain, 'x', {'y': 4}) == False
assert domain_inversion(domain, 'x', {'y': 6}) == [['x', '=', 3]]
domain = [['x', '=', 3], ['y', '=', 5]]
assert domain_inversion(domain, 'z') == True
assert domain_inversion(domain, 'z', {'x': 2, 'y': 7}) == True
domain = [['x.id', '>', 5], ['y', '<', 3]]
assert domain_inversion(domain, 'y') == [['y', '<', 3]]
assert domain_inversion(domain, 'y', {'x': 3}) == [['y', '<', 3]]
assert domain_inversion(domain, 'x') == [['x.id', '>', 5]]
def test_or_inversion():
domain = ['OR', ['x', '=', 3], ['y', '>', 5], ['z', '=', 'abc']]
assert domain_inversion(domain, 'x') == [['x', '=', 3]]
assert domain_inversion(domain, 'x', {'y': 4}) == [['x', '=', 3]]
assert domain_inversion(domain, 'x', {'y': 4, 'z': 'ab'}) == [['x', '=', 3]]
assert domain_inversion(domain, 'x', {'y': 7}) == True
assert domain_inversion(domain, 'x', {'y': 7, 'z': 'b'}) == True
assert domain_inversion(domain, 'x', {'z': 'abc'}) == True
assert domain_inversion(domain, 'x', {'y': 4, 'z': 'abc'}) == True
domain = ['OR', ['x', '=', 3], ['y', '>', 5]]
assert domain_inversion(domain, 'z') == True
domain = ['OR', ['x.id', '>', 5], ['y', '<', 3]]
assert domain_inversion(domain, 'y') == [['y', '<', 3]]
assert domain_inversion(domain, 'y', {'z': 4}) == [['y', '<', 3]]
assert domain_inversion(domain, 'y', {'x': 3}) == True
domain = [u'OR', [u'length', u'>', 5], [u'language.code', u'=', u'de_DE']]
assert domain_inversion(domain, 'length', {'length': 0, 'name': 'n'}) ==\
[['length', '>', 5]]
def test_orand_inversion():
domain = ['OR', [['x', '=', 3], ['y', '>', 5], ['z', '=', 'abc']],
[['x', '=', 4]], [['y', '>', 6]]]
assert domain_inversion(domain, 'x') == True
assert domain_inversion(domain, 'x', {'y': 4}) == [[['x', '=', 4]]]
assert domain_inversion(domain, 'x', {'z': 'abc', 'y': 7}) == True
assert domain_inversion(domain, 'x', {'y': 7}) == True
assert domain_inversion(domain, 'x', {'z': 'ab'}) == True
def test_andor_inversion():
domain = [['OR', ['x', '=', 4], ['y', '>', 6]], ['z', '=', 3]]
assert domain_inversion(domain, 'z') == [['z', '=', 3]]
assert domain_inversion(domain, 'z', {'x': 5}) == [['z', '=', 3]]
assert domain_inversion(domain, 'z', {'x': 5, 'y': 5}) == False
assert domain_inversion(domain, 'z', {'x': 5, 'y': 7}) == [['z', '=', 3]]
def test_andand_inversion():
domain = [[['x', '=', 4], ['y', '>', 6]], ['z', '=', 3]]
assert domain_inversion(domain, 'z') == [['z', '=', 3]]
assert domain_inversion(domain, 'z', {'x': 5}) == False
assert domain_inversion(domain, 'z', {'y': 5}) == False
assert domain_inversion(domain, 'z', {'x': 4, 'y': 7}) == [['z', '=', 3]]
domain = [[['x', '=', 4], ['y', '>', 6], ['z', '=', 2]], [['w', '=', 2]]]
assert domain_inversion(domain, 'z', {'x': 4}) == [['z', '=', 2]]
def test_oror_inversion():
domain = ['OR', ['OR', ['x', '=', 3], ['y', '>', 5]],
['OR', ['x', '=', 2], ['z', '=', 'abc']],
['OR', ['y', '=', 8], ['z', '=', 'y']]]
assert domain_inversion(domain, 'x') == True
assert domain_inversion(domain, 'x', {'y': 4}) == True
assert domain_inversion(domain, 'x', {'z': 'ab'}) == True
assert domain_inversion(domain, 'x', {'y': 7}) == True
assert domain_inversion(domain, 'x', {'z': 'abc'}) == True
assert domain_inversion(domain, 'x', {'z': 'y'}) == True
assert domain_inversion(domain, 'x', {'y': 8}) == True
assert domain_inversion(domain, 'x', {'y': 8, 'z': 'b'}) == True
assert domain_inversion(domain, 'x', {'y': 4, 'z': 'y'}) == True
assert domain_inversion(domain, 'x', {'y': 7, 'z': 'abc'}) == True
assert domain_inversion(domain, 'x', {'y': 4, 'z': 'b'}) == \
['OR', [['x', '=', 3]], [['x', '=', 2]]]
def test_parse():
domain = parse([['x', '=', 5]])
assert domain.variables == set('x')
domain = parse(['OR', ['x', '=', 4], ['y', '>', 6]])
assert domain.variables == set('xy')
domain = parse([['OR', ['x', '=', 4], ['y', '>', 6]], ['z', '=', 3]])
assert domain.variables == set('xyz')
domain = parse([[['x', '=', 4], ['y', '>', 6]], ['z', '=', 3]])
assert domain.variables == set('xyz')
def test_simplify():
domain = [['x', '=', 3]]
assert simplify(domain) == [['x', '=', 3]]
domain = [[['x', '=', 3]]]
assert simplify(domain) == [['x', '=', 3]]
domain = ['OR', ['x', '=', 3]]
assert simplify(domain) == [['x', '=', 3]]
domain = ['OR', [['x', '=', 3]], [['y', '=', 5]]]
assert simplify(domain) == ['OR', [['x', '=', 3]], [['y', '=', 5]]]
domain = ['OR', ['x', '=', 3], ['AND', ['y', '=', 5]]]
assert simplify(domain) == ['OR', ['x', '=', 3], ['y', '=', 5]]
def test_merge():
domain = [['x', '=', 6], ['y', '=', 7]]
assert merge(domain) == ['AND', ['x', '=', 6], ['y', '=', 7]]
domain = ['AND', ['x', '=', 6], ['y', '=', 7]]
assert merge(domain) == ['AND', ['x', '=', 6], ['y', '=', 7]]
domain = [['z', '=', 8], ['AND', ['x', '=', 6], ['y', '=', 7]]]
assert merge(domain) == ['AND', ['z', '=', 8], ['x', '=', 6],
['y', '=', 7]]
domain = ['OR', ['x', '=', 1], ['y', '=', 2], ['z', '=', 3]]
assert merge(domain) == ['OR', ['x', '=', 1], ['y', '=', 2],
['z', '=', 3]]
domain = ['OR', ['x', '=', 1], ['OR', ['y', '=', 2], ['z', '=', 3]]]
assert merge(domain) == ['OR', ['x', '=', 1], ['y', '=', 2],
['z', '=', 3]]
domain = ['OR', ['x', '=', 1], ['AND', ['y', '=', 2], ['z', '=', 3]]]
assert merge(domain) == ['OR', ['x', '=', 1], ['AND', ['y', '=', 2],
['z', '=', 3]]]
domain = [['z', '=', 8], ['OR', ['x', '=', 6], ['y', '=', 7]]]
assert merge(domain) == ['AND', ['z', '=', 8], ['OR', ['x', '=', 6],
['y', '=', 7]]]
domain = ['AND', ['OR', ['a', '=', 1], ['b', '=', 2]],
['OR', ['c', '=', 3], ['AND', ['d', '=', 4], ['d2', '=', 6]]],
['AND', ['d', '=', 5], ['e', '=', 6]], ['f', '=', 7]]
assert merge(domain) == ['AND', ['OR', ['a', '=', 1], ['b', '=', 2]],
['OR', ['c', '=', 3], ['AND', ['d', '=', 4], ['d2', '=', 6]]],
['d', '=', 5], ['e', '=', 6], ['f', '=', 7]]
def test_evaldomain():
domain = [['x', '>', 5]]
assert eval_domain(domain, {'x': 6})
assert not eval_domain(domain, {'x': 4})
domain = ['OR', ['x', '>', 10], ['x', '<', 0]]
assert eval_domain(domain, {'x': 11})
assert eval_domain(domain, {'x': -4})
assert not eval_domain(domain, {'x': 5})
domain = [['x', '>', 0], ['OR', ['x', '=', 3], ['x', '=', 2]]]
assert not eval_domain(domain, {'x': 1})
assert eval_domain(domain, {'x': 3})
assert eval_domain(domain, {'x': 2})
assert not eval_domain(domain, {'x': 4})
assert not eval_domain(domain, {'x': 5})
assert not eval_domain(domain, {'x': 6})
domain = ['OR', ['x', '=', 4], [['x', '>', 6], ['x', '<', 10]]]
assert eval_domain(domain, {'x': 4})
assert eval_domain(domain, {'x': 7})
assert not eval_domain(domain, {'x': 3})
assert not eval_domain(domain, {'x': 5})
assert not eval_domain(domain, {'x': 11})
if __name__ == '__main__':
test_simple_inversion()
test_and_inversion()
test_or_inversion()
test_orand_inversion()
test_andor_inversion()
test_andand_inversion()
test_oror_inversion()
test_parse()
test_simplify()
test_evaldomain()
+38
View File
@@ -0,0 +1,38 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
class EvalEnvironment(dict):
def __init__(self, parent, check_load):
super(EvalEnvironment, self).__init__()
self.parent = parent
self.check_load = check_load
def __getitem__(self, item):
if item == '_parent_' + self.parent.parent_name and self.parent.parent:
return EvalEnvironment(self.parent.parent, self.check_load)
return self.parent.get_eval(check_load=self.check_load)[item]
def __getattr__(self, item):
return self.__getitem__(item)
def get(self, item, default=None):
try:
return self.__getattr__(item)
except Exception:
pass
return super(EvalEnvironment, self).get(item, default)
def __nonzero__(self):
return True
def __str__(self):
return str(self.parent)
__repr__ = __str__
def __contains__(self, item):
if item == '_parent_' + self.parent.parent_name and self.parent.parent:
return item in EvalEnvironment(self.parent.parent, self.check_load)
return item in self.parent.get_eval(check_load=self.check_load)
+86 -12
View File
@@ -1,7 +1,9 @@
#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 tryton.rpc as rpc
from tryton.common import DT_FORMAT, DHM_FORMAT, HM_FORMAT, datetime_strftime
from tryton.common import DT_FORMAT, DHM_FORMAT, HM_FORMAT, datetime_strftime, \
domain_inversion, eval_domain, localize_domain, unlocalize_domain, \
merge, inverse_leaf, EvalEnvironment
import tryton.common as common
from tryton.pyson import PYSONDecoder
import time
@@ -41,8 +43,27 @@ class CharField(object):
record.cond_default(self.attrs['name'], self.get(record))
record.on_change_with(self.name)
def domains_get(self, record):
screen_domain = domain_inversion(record.group.domain,
self.name, EvalEnvironment(record, False))
if isinstance(screen_domain, bool) and not screen_domain:
screen_domain = [('id', '=', False)]
elif isinstance(screen_domain, bool) and screen_domain:
screen_domain = []
attr_domain = record.expr_eval(self.attrs.get('domain', []))
return screen_domain, attr_domain
def domain_get(self, record):
return record.expr_eval(self.attrs.get('domain', []))
screen_domain, attr_domain = self.domains_get(record)
return localize_domain(screen_domain) + attr_domain
def validation_domains(self, record):
screen_domain, attr_domain = self.domains_get(record)
if attr_domain:
return screen_domain, screen_domain + unlocalize_domain(attr_domain,
self.name)
else:
return screen_domain, screen_domain
def context_get(self, record, check_load=True, eval_context=True):
context = {}
@@ -52,13 +73,39 @@ class CharField(object):
check_load=check_load))
return context
def validate(self, record):
def validate(self, record, softvalidation=False):
res = True
if bool(int(self.get_state_attrs(record).get('required') or 0)):
if not self.get(record) \
and not bool(int(self.get_state_attrs(record
).get('readonly') or 0)):
res = False
inverted_domain, domain = self.validation_domains(record)
if not softvalidation:
if bool(int(self.get_state_attrs(record).get('required') or 0)):
if not self.get(record) \
and not bool(int(self.get_state_attrs(record
).get('readonly') or 0)):
res = False
if isinstance(domain, bool):
res = res and domain
else:
if (isinstance(inverted_domain, list) \
and len(inverted_domain) == 1 and inverted_domain[0][1] == '='):
# If the inverted domain is so constraint that only one value is
# possible we should use it. But we must also pay attention to
# the fact that the original domain might be a 'OR' domain and
# thus not preventing the modification of fields.
leftpart, _, value = inverted_domain[0][:3]
setdefault = True
if '.' in leftpart:
recordpart, localpart = leftpart.split('.', 1)
original_domain = merge(record.group.admin)
constraintfields = set()
if original_domain[0] == 'AND':
for leaf in localize_domain(original_domain[1:]):
constraintfields.add(leaf[0])
if localpart != 'id' or recordpart not in constraintfields:
setdefault = False
if setdefault:
self.set_client(record, value)
self.get_state_attrs(record)['domain_readonly'] = True
res = res and eval_domain(domain, EvalEnvironment(record, False))
self.get_state_attrs(record)['valid'] = res
return res
@@ -92,6 +139,7 @@ class CharField(object):
record.value[self.name] = internal
record.modified = prev_modified
return
record.validate(softvalidation=True)
record.signal('record-changed')
def get_client(self, record):
@@ -117,7 +165,8 @@ class CharField(object):
self.get_state_attrs(record)[key] = state_changes[key]
elif key in self.attrs:
self.get_state_attrs(record)[key] = self.attrs[key]
if record.group.readonly:
if (record.group.readonly
or self.get_state_attrs(record).get('domain_readonly')):
self.get_state_attrs(record)['readonly'] = True
if 'value' in state_changes:
value = state_changes['value']
@@ -194,6 +243,7 @@ class FloatField(CharField):
except Exception:
record.value[self.name] = internal
record.modified = prev_modified
record.validate(softvalidation=True)
record.signal('record-changed')
@@ -217,6 +267,7 @@ class NumericField(CharField):
record.value[self.name] = internal
record.modified = prev_modified
return
record.validate(softvalidation=True)
record.signal('record-changed')
@@ -244,6 +295,7 @@ class BooleanField(CharField):
record.value[self.name] = internal
record.modified = prev_modified
return
record.validate(softvalidation=True)
record.signal('record-changed')
def get(self, record, check_load=True, readonly=True, modified=False):
@@ -314,6 +366,7 @@ class M2OField(CharField):
record.value[self.name] = internal
record.modified = prev_modified
return
record.validate(softvalidation=True)
record.signal('record-changed')
elif force_change:
try:
@@ -321,6 +374,7 @@ class M2OField(CharField):
except Exception:
record.value[self.name] = internal
return
record.validate(softvalidation=True)
record.signal('record-changed')
def context_get(self, record, check_load=True, eval_context=True):
@@ -331,6 +385,14 @@ class M2OField(CharField):
check_load=check_load)[self.attrs.get('datetime_field')]
return context
def validation_domains(self, record):
screen_domain, attr_domain = self.domains_get(record)
return screen_domain, screen_domain
def domain_get(self, record):
screen_domain, attr_domain = self.domains_get(record)
return localize_domain(inverse_leaf(screen_domain)) + attr_domain
class O2OField(M2OField):
pass
@@ -349,6 +411,7 @@ class O2MField(CharField):
record.parent.modified = True
record.parent.modified_fields.setdefault(self.name)
self.sig_changed(record.parent)
record.parent.validate(softvalidation=True)
record.parent.signal('record-changed')
def _set_default_value(self, record):
@@ -432,9 +495,11 @@ class O2MField(CharField):
def set_default(self, record, value, modified=False):
from group import Group
# value is a list of id
if value and len(value) and isinstance(value[0], (int, long)):
return self.set(record, value, modified=modified)
# value is a list of dict
fields = {}
if value and len(value):
context = self.context_get(record)
@@ -529,9 +594,13 @@ class O2MField(CharField):
res = [x.get_default() for x in record.value.get(self.name) or []]
return res
def validate(self, record):
def validation_domains(self, record):
screen_domain, attr_domain = self.domains_get(record)
return screen_domain, screen_domain
def validate(self, record, softvalidation=False):
res = True
for record2 in record.value[self.name] or []:
for record2 in record.value.get(self.name, []):
if not record2.loaded:
continue
if not record2.validate():
@@ -539,7 +608,7 @@ class O2MField(CharField):
record.value[self.name].remove(record2)
else:
res = False
if not super(O2MField, self).validate(record):
if not super(O2MField, self).validate(record, softvalidation):
res = False
self.get_state_attrs(record)['valid'] = res
return res
@@ -555,6 +624,10 @@ class O2MField(CharField):
def get_removed_ids(self, record):
return [x.id for x in record.value[self.name].record_removed]
def domain_get(self, record):
screen_domain, attr_domain = self.domains_get(record)
return localize_domain(inverse_leaf(screen_domain)) + attr_domain
class M2MField(O2MField):
@@ -615,6 +688,7 @@ class ReferenceField(CharField):
record.value[self.name] = internal
record.modified = prev_modified
return
record.validate(softvalidation=True)
record.signal('record-changed')
def set(self, record, value, modified=False):
+4 -1
View File
@@ -10,9 +10,12 @@ import tryton.common as common
class Group(SignalEvent, list):
def __init__(self, model_name, fields, window, ids=None, parent=None,
parent_name='', context=None, readonly=False,
parent_name='', context=None, domain=None, readonly=False,
parent_datetime_field=None):
super(Group, self).__init__()
if domain is None:
domain = []
self.domain = domain
self.lock_signal = False
self.__window = window
self.parent = parent
+10 -60
View File
@@ -10,45 +10,10 @@ import logging
import time
class EvalEnvironment(dict):
def __init__(self, parent, check_load):
super(EvalEnvironment, self).__init__()
self.parent = parent
self.check_load = check_load
def __getitem__(self, item):
if item == '_parent_' + self.parent.parent_name and self.parent.parent:
return EvalEnvironment(self.parent.parent, self.check_load)
return self.parent.get_eval(check_load=self.check_load)[item]
def __getattr__(self, item):
return self.__getitem__(item)
def get(self, item, default=None):
try:
return self.__getattr__(item)
except Exception:
pass
return super(EvalEnvironment, self).get(item, default)
def __nonzero__(self):
return True
def __str__(self):
return str(self.parent)
__repr__ = __str__
def __contains__(self, item):
return item in self.parent.group.fields
class Record(SignalEvent):
id = -1
def __init__(self, model_name, obj_id, window, group=None, parent=None,
parent_name=''):
super(Record, self).__init__()
@@ -262,32 +227,16 @@ class Record(SignalEvent):
return ''
return res['rec_name']
def validate_set(self, fields=None):
if isinstance(fields, list) and fields:
self._check_load(fields)
elif fields is None:
self._check_load()
change = False
for name, field in self.group.fields.iteritems():
if fields and name not in fields:
continue
change = change or \
not field.get_state_attrs(self).get('valid', True)
field.get_state_attrs(self)['valid'] = True
if change:
self.signal('record-changed')
return change
def validate(self, fields=None):
def validate(self, fields=None, softvalidation=False):
if isinstance(fields, list) and fields:
self._check_load(fields)
elif fields is None:
self._check_load()
res = True
for name, field in self.group.fields.iteritems():
if fields and name not in fields:
for field_name, field in self.group.fields.iteritems():
if fields and field_name not in fields:
continue
if not field.validate(self):
if not field.validate(self, softvalidation):
res = False
return res
@@ -325,8 +274,9 @@ class Record(SignalEvent):
else:
value = ref_model, (ref_id, ref_id)
self.group.fields[fieldname].set_default(self, value,
modified=modified)
modified=modified)
self._loaded.add(fieldname)
self.validate(softvalidation=True)
if signal:
self.signal('record-changed')
@@ -386,8 +336,8 @@ class Record(SignalEvent):
ctx['active_id'] = self.id
ctx['_user'] = rpc._USER
if self.parent and self.parent_name:
ctx['_parent_' + self.parent_name] = EvalEnvironment(self.parent,
check_load)
ctx['_parent_' + self.parent_name] = \
common.EvalEnvironment(self.parent, check_load)
val = PYSONDecoder(ctx).decode(expr)
return val
@@ -397,8 +347,8 @@ class Record(SignalEvent):
for name, field in self.group.fields.iteritems():
values[name] = field.get_on_change_value(self, check_load=False)
if self.parent and self.parent_name:
values['_parent_' + self.parent_name] = EvalEnvironment(self.parent,
False)
values['_parent_' + self.parent_name] = \
common.EvalEnvironment(self.parent, False)
for arg in args:
scope = values
for i in arg.split('.'):
+8 -46
View File
@@ -194,8 +194,8 @@ class Screen(SignalEvent):
group = property(__get_group, __set_group)
def new_group(self, readonly=False):
self.group = Group(self.model_name, {}, self.window,
context=self.context, readonly=readonly)
self.group = Group(self.model_name, {}, self.window, domain=self.domain,
context=self.context, readonly=readonly)
def _group_cleared(self, group, signal):
for view in self.views:
@@ -288,11 +288,8 @@ class Screen(SignalEvent):
elif self.current_view.view_type == view_type:
break
self.screen_container.set(self.current_view.widget)
if self.current_record:
self.current_record.validate_set(fields)
else:
if self.current_view.view_type == 'form':
self.new(default=default, context=context)
if not self.current_record and self.current_view.view_type == 'form':
self.new(default=default, context=context)
self.current_view.cancel()
self.display(set_cursor=True)
@@ -332,34 +329,8 @@ class Screen(SignalEvent):
def add_view(self, arch, fields, display=False, toolbar=None, context=None):
if toolbar is None:
toolbar = {}
xml_dom = xml.dom.minidom.parseString(arch)
for dom in common.filter_domain(self.domain):
if '.' in dom[0]:
field1, field2 = dom[0].split('.', 1)
else:
field1, field2 = dom[0], 'id'
if field1 in fields:
field_dom = fields[field1].setdefault('domain', [])
if dom[1] in ('child_of', 'not child_of') \
and field2 == 'id':
dom = copy.copy(dom)
if len(dom) == 4:
field2 = dom[3]
dom = (dom[0], dom[1], dom[2])
else:
field2 = field1
if isinstance(field_dom, basestring):
fields[field1]['domain'] = '[' \
+ str(tuple([field2] + list(dom[1:]))) \
+ ',' + field_dom + ']'
else:
fields[field1]['domain'] = [
tuple([field2] + list(dom[1:])),
field_dom]
if dom[1] == '!=' and dom[2] == False:
fields[field1]['required'] = True
for node in xml_dom.childNodes:
if node.localName == 'tree':
self.fields_view_tree = {'arch': arch, 'fields': fields}
@@ -396,11 +367,9 @@ class Screen(SignalEvent):
self.__current_view = len(self.views) - 1
self.screen_container.set(self.current_view.widget)
fields = self.current_view.get_fields()
if self.current_record:
self.current_record.validate_set(fields)
else:
if self.current_view.view_type == 'form':
self.new()
if (not self.current_record
and self.current_view.view_type == 'form'):
self.new()
self.current_view.set_cursor()
self.current_view.cancel()
self.display()
@@ -442,7 +411,6 @@ class Screen(SignalEvent):
fields = None
if self.current_view:
fields = self.current_view.get_fields()
self.current_record.validate_set(fields)
self.display()
if self.current_view:
self.current_view.set_cursor(new=True)
@@ -671,9 +639,6 @@ class Screen(SignalEvent):
self.current_record = len(self.group) \
and self.group[0]
self.current_view.set_cursor(reset_view=False)
if self.current_record:
fields = self.current_view.get_fields()
self.current_record.validate_set(fields)
self.current_view.display()
def display_prev(self):
@@ -701,9 +666,6 @@ class Screen(SignalEvent):
self.current_record = len(self.group) \
and self.group[-1]
self.current_view.set_cursor(reset_view=False)
if self.current_record:
fields = self.current_view.get_fields()
self.current_record.validate_set(fields)
self.current_view.display()
def sel_ids_get(self):
@@ -26,19 +26,18 @@ class Many2One(WidgetInterface):
self.widget = gtk.HBox(spacing=0)
self.widget.set_property('sensitive', True)
self.widget.connect('focus-in-event', lambda x, y: self._focus_in())
self.widget.connect('focus-out-event', lambda x, y: self._focus_out())
self.wid_text = gtk.Entry()
self.wid_text.set_property('width-chars', 13)
self.wid_text.set_property('activates_default', True)
self.wid_text.connect_after('key_press_event', self.sig_key_press)
self.wid_text.connect('populate-popup', self._populate_popup)
self.wid_text.connect('focus-in-event', lambda x, y: self._focus_in())
self.wid_text.connect('focus-out-event', lambda x, y: self._focus_out())
self.wid_text.connect_after('changed', self.sig_changed)
self.changed = True
self.wid_text.connect_after('activate', self.sig_activate)
self.wid_text.connect_after('focus-out-event',
self.sig_activate)
self.wid_text.connect_after('focus-out-event', self.sig_activate)
self.focus_out = True
self.widget.pack_start(self.wid_text, expand=True, fill=True)