changeset c1e5655f02d9 in tryton:4.2 details: https://hg.tryton.org/tryton?cmd=changeset;node=c1e5655f02d9 description: Fix domain inversion of child_of mixing M2O and Reference fields
Domain inversion on Reference field does not work when using the dotted notation with a M2O field using child_of or parent_of. To solve this we correctly compute the localization of child_of leaves when using the dotted notation (which renders the inverse_leaf function useless). But we must also ensure that the selection used by reference fields restrict the choice to the right models. issue7869 review60441002 (grafted from 0655117211707b03fc496c0e30a9ec6c7af26e4c) (grafted from e77b88fe6393385fc2b52cf83a2bc0568efb7c46) diffstat: tryton/common/__init__.py | 5 +- tryton/common/domain_inversion.py | 71 ++++++++++++++++++++++++++++++ tryton/common/selection.py | 25 +++++++--- tryton/gui/window/view_form/model/field.py | 12 +++- 4 files changed, 100 insertions(+), 13 deletions(-) diffs (202 lines): diff -r ea395d6b6289 -r c1e5655f02d9 tryton/common/__init__.py --- a/tryton/common/__init__.py Mon Nov 12 23:35:42 2018 +0100 +++ b/tryton/common/__init__.py Thu Dec 20 18:44:31 2018 +0100 @@ -2,7 +2,8 @@ # 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, \ - merge, inverse_leaf, filter_leaf, concat, simplify, unique_value +from .domain_inversion import domain_inversion, eval_domain, localize_domain, \ + merge, inverse_leaf, filter_leaf, prepare_reference_domain, \ + extract_reference_models, concat, simplify, unique_value from environment import EvalEnvironment import timedelta diff -r ea395d6b6289 -r c1e5655f02d9 tryton/common/domain_inversion.py --- a/tryton/common/domain_inversion.py Mon Nov 12 23:35:42 2018 +0100 +++ b/tryton/common/domain_inversion.py Thu Dec 20 18:44:31 2018 +0100 @@ -127,6 +127,36 @@ return [filter_leaf(d, field, model) for d in domain] +def prepare_reference_domain(domain, reference): + "convert domain to replace reference fields by their local part" + if domain in ('AND', 'OR'): + return domain + elif is_leaf(domain): + # When a Reference field is using the dotted notation the model + # specified must be removed from the clause + if domain[0].count('.') and len(domain) > 3: + local_name, target_name = domain[0].split('.', 1) + if local_name == reference: + return [target_name] + list(domain[1:3] + domain[4:]) + return domain + else: + return [prepare_reference_domain(d, reference) for d in domain] + + +def extract_reference_models(domain, field_name): + "returns the set of the models available for field_name" + if domain in ('AND', 'OR'): + return set() + elif is_leaf(domain): + local_part = domain[0].split('.', 1)[0] + if local_part == field_name and len(domain) > 3: + return {domain[3]} + return set() + else: + return reduce(operator.or_, + (extract_reference_models(d, field_name) for d in domain)) + + def eval_domain(domain, context, boolop=operator.and_): "compute domain boolean value according to the context" if is_leaf(domain): @@ -150,6 +180,9 @@ return domain elif is_leaf(domain): if 'child_of' in domain[1]: + if domain[0].count('.'): + _, target_part = domain[0].split('.', 1) + return [target_part] + list(domain[1:]) if len(domain) == 3: return domain else: @@ -628,6 +661,13 @@ domain = [['x', 'child_of', [1], 'y']] assert localize_domain(domain, 'x') == [['y', 'child_of', [1]]] + domain = [['x.y', 'child_of', [1], 'parent']] + assert localize_domain(domain, 'x') == [['y', 'child_of', [1], 'parent']] + + domain = [['x.y.z', 'child_of', [1], 'parent', 'model']] + assert localize_domain(domain, 'x') == \ + [['y.z', 'child_of', [1], 'parent', 'model']] + domain = [['x.id', '=', 1, 'y']] assert localize_domain(domain, 'x', False) == [['id', '=', 1, 'y']] assert localize_domain(domain, 'x', True) == [['id', '=', 1]] @@ -636,6 +676,35 @@ assert localize_domain(domain, 'x', False) == [['b.c', '=', 1, 'y', 'z']] assert localize_domain(domain, 'x', True) == [['b.c', '=', 1, 'z']] + +def test_prepare_reference_domain(): + domain = [['x', 'like', 'A%']] + assert prepare_reference_domain(domain, 'x') == [['x', 'like', 'A%']] + + domain = [['x.y', 'like', 'A%', 'model']] + assert prepare_reference_domain(domain, 'x') == [['y', 'like', 'A%']] + + domain = [['x.y', 'child_of', [1], 'model', 'parent']] + assert prepare_reference_domain(domain, 'x') == \ + [['y', 'child_of', [1], 'parent']] + + +def test_extract_models(): + domain = [['x', 'like', 'A%']] + assert extract_reference_models(domain, 'x') == set() + assert extract_reference_models(domain, 'y') == set() + + domain = [['x', 'like', 'A%', 'model']] + assert extract_reference_models(domain, 'x') == {'model'} + assert extract_reference_models(domain, 'y') == set() + + domain = ['OR', + ['x.y', 'like', 'A%', 'model_A'], + ['x.z', 'like', 'B%', 'model_B']] + assert extract_reference_models(domain, 'x') == {'model_A', 'model_B'} + assert extract_reference_models(domain, 'y') == set() + + if __name__ == '__main__': test_simple_inversion() test_and_inversion() @@ -648,3 +717,5 @@ test_simplify() test_evaldomain() test_localize() + test_prepare_reference_domain() + test_extract_models() diff -r ea395d6b6289 -r c1e5655f02d9 tryton/common/selection.py --- a/tryton/common/selection.py Mon Nov 12 23:35:42 2018 +0100 +++ b/tryton/common/selection.py Thu Dec 20 18:44:31 2018 +0100 @@ -50,10 +50,6 @@ return domain = field.domain_get(record) - if field.attrs['type'] == 'reference': - # The domain on reference field is not only based on the selection - # so the selection can not be filtered. - domain = [] if 'relation' not in self.attrs: change_with = self.attrs.get('selection_change_with') or [] value = record._get_on_change_args(change_with) @@ -90,10 +86,23 @@ def filter_selection(self, domain, record, field): if not domain: return - test = lambda value: eval_domain(domain, { - self.field_name: value[0], - }) - self.selection = filter(test, self.selection) + + def _value_evaluator(value): + return eval_domain(domain, { + self.field_name: value[0], + }) + + def _model_evaluator(allowed_models): + def test(value): + return value[0] in allowed_models + return test + + if field.attrs['type'] == 'reference': + allowed_models = field.get_models(record) + evaluator = _model_evaluator(allowed_models) + else: + evaluator = _value_evaluator + self.selection = list(filter(evaluator, self.selection)) def get_inactive_selection(self, value): if 'relation' not in self.attrs: diff -r ea395d6b6289 -r c1e5655f02d9 tryton/gui/window/view_form/model/field.py --- a/tryton/gui/window/view_form/model/field.py Mon Nov 12 23:35:42 2018 +0100 +++ b/tryton/gui/window/view_form/model/field.py Thu Dec 20 18:44:31 2018 +0100 @@ -6,7 +6,8 @@ import locale from tryton.common import \ domain_inversion, eval_domain, localize_domain, \ - merge, inverse_leaf, filter_leaf, concat, simplify, unique_value, \ + merge, inverse_leaf, filter_leaf, prepare_reference_domain, \ + extract_reference_models, concat, simplify, unique_value, \ EvalEnvironment import tryton.common as common import datetime @@ -860,10 +861,16 @@ else: model = None screen_domain, attr_domain = self.domains_get(record) + screen_domain = prepare_reference_domain(screen_domain, self.name) return concat(localize_domain( filter_leaf(screen_domain, self.name, model), strip_target=True), attr_domain) + def get_models(self, record): + screen_domain, attr_domain = self.domains_get(record) + return extract_reference_models( + concat(screen_domain, attr_domain), self.name) + class _FileCache(object): def __init__(self, path): @@ -941,8 +948,7 @@ def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) - return concat(localize_domain(inverse_leaf(screen_domain)), - attr_domain) + return concat(localize_domain(screen_domain), attr_domain) def date_format(self, record): context = self.context_get(record)