changeset e77b88fe6393 in tryton:4.8
details: https://hg.tryton.org/tryton?cmd=changeset;node=e77b88fe6393
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)
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 7c9b40df5b5d -r e77b88fe6393 tryton/common/__init__.py
--- a/tryton/common/__init__.py Mon Dec 03 00:44:51 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 7c9b40df5b5d -r e77b88fe6393 tryton/common/domain_inversion.py
--- a/tryton/common/domain_inversion.py Mon Dec 03 00:44:51 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 7c9b40df5b5d -r e77b88fe6393 tryton/common/selection.py
--- a/tryton/common/selection.py        Mon Dec 03 00:44:51 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 7c9b40df5b5d -r e77b88fe6393 tryton/gui/window/view_form/model/field.py
--- a/tryton/gui/window/view_form/model/field.py        Mon Dec 03 00:44:51 
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
@@ -874,10 +875,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):
@@ -955,8 +962,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.get_context(record)

Reply via email to