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)

Reply via email to