changeset ce1e97be71de in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=ce1e97be71de
description:
        Add RPC view_get method to View

        This allows to extend the inherit mechanism of the view and support 
inherit for
        the board view.
        Also the inherit is now applied to all element found by the xpath.

        issue11107
        issue11108
        review389601002
diffstat:

 CHANGELOG                      |    3 +
 doc/topics/views/extension.rst |    4 +-
 trytond/ir/ui/view.py          |  112 ++++++++++++++++++++++++++++++++
 trytond/model/modelview.py     |  142 ++--------------------------------------
 4 files changed, 125 insertions(+), 136 deletions(-)

diffs (369 lines):

diff -r 453649ad7314 -r ce1e97be71de CHANGELOG
--- a/CHANGELOG Sun Jan 23 13:27:52 2022 +0100
+++ b/CHANGELOG Sun Jan 23 13:32:59 2022 +0100
@@ -1,3 +1,6 @@
+* Apply view inheritance to board
+* Add RPC view_get method to View
+* Apply view inheritance to all matching elements
 * Limit board action domain to active id and ids
 * Remove entropy check on password
 * Set view_id in the context when calling view_attributes
diff -r 453649ad7314 -r ce1e97be71de doc/topics/views/extension.rst
--- a/doc/topics/views/extension.rst    Sun Jan 23 13:27:52 2022 +0100
+++ b/doc/topics/views/extension.rst    Sun Jan 23 13:32:59 2022 +0100
@@ -26,9 +26,9 @@
 xpath
 -----
 
-    * ``expr``: the xpath expression to find a node in the inherited view.
+    * ``expr``: the xpath expression to find the nodes in the inherited view.
 
-    * ``position``: Define the position in relation to the node found. It can
+    * ``position``: Define the position in relation to the nodes found. It can
       be ``before``, ``after``, ``replace``, ``inside`` or
       ``replace_attributes`` which will change the attributes.
 
diff -r 453649ad7314 -r ce1e97be71de trytond/ir/ui/view.py
--- a/trytond/ir/ui/view.py     Sun Jan 23 13:27:52 2022 +0100
+++ b/trytond/ir/ui/view.py     Sun Jan 23 13:32:59 2022 +0100
@@ -64,10 +64,13 @@
             'invisible': ~Eval('inherit'),
             }, depends=['inherit'])
     _get_rng_cache = Cache('ir_ui_view.get_rng')
+    _view_get_cache = Cache('ir_ui_view.view_get')
+    __module_index = None
 
     @classmethod
     def __setup__(cls):
         super(View, cls).__setup__()
+        cls.__rpc__['view_get'] = RPC(instantiate=0, cache=dict(days=1))
         cls._order.insert(0, ('priority', 'ASC'))
         cls._buttons.update({
                 'show': {
@@ -185,12 +188,14 @@
     def delete(cls, views):
         super(View, cls).delete(views)
         # Restart the cache
+        cls._view_get_cache.clear()
         ModelView._fields_view_get_cache.clear()
 
     @classmethod
     def create(cls, vlist):
         views = super(View, cls).create(vlist)
         # Restart the cache
+        cls._view_get_cache.clear()
         ModelView._fields_view_get_cache.clear()
         return views
 
@@ -198,8 +203,115 @@
     def write(cls, views, values, *args):
         super(View, cls).write(views, values, *args)
         # Restart the cache
+        cls._view_get_cache.clear()
         ModelView._fields_view_get_cache.clear()
 
+    @property
+    def _module_index(self):
+        from trytond.modules import create_graph, get_module_list
+        if self.__class__.__module_index is None:
+            graph = create_graph(get_module_list())
+            modules = [m.name for m in graph]
+            self.__class__.__module_index = {
+                m: i for i, m in enumerate(reversed(modules))}
+        return self.__class__.__module_index
+
+    def view_get(self, model=None):
+        key = (self.id, model)
+        result = self._view_get_cache.get(key)
+        if result:
+            return result
+        if self.inherit:
+            if self.model == model:
+                return self.inherit.view_get(model=model)
+            else:
+                arch = self.inherit.arch
+                view_id = self.inherit.id
+        else:
+            arch = self.arch
+            view_id = self.id
+
+        views = self.__class__.search(['OR', [
+                    ('inherit', '=', view_id),
+                    ('model', '=', model),
+                    ], [
+                    ('id', '=', view_id),
+                    ('inherit', '!=', None),
+                    ],
+                ])
+        views.sort(
+            key=lambda v: self._module_index.get(v.module, -1), reverse=True)
+        parser = etree.XMLParser(remove_comments=True)
+        tree = etree.fromstring(arch, parser=parser)
+        decoder = PYSONDecoder({'context': Transaction().context})
+        for view in views:
+            if view.domain and not decoder.decode(view.domain):
+                continue
+            if not view.arch or not view.arch.strip():
+                continue
+            tree_inherit = etree.fromstring(view.arch, parser=parser)
+            tree = self.inherit_apply(tree, tree_inherit)
+        arch = etree.tostring(tree, encoding='utf-8').decode('utf-8')
+        result = {
+            'type': self.rng_type,
+            'view_id': view_id,
+            'arch': arch,
+            'field_childs': self.field_childs,
+            }
+        self._view_get_cache.set(key, result)
+        return result
+
+    @classmethod
+    def inherit_apply(cls, tree, inherit):
+        root_inherit = inherit.getroottree().getroot()
+        for element in root_inherit:
+            targets = tree.xpath(element.get('expr'))
+            assert targets
+            for target in targets:
+                position = element.get('position', 'inside')
+                new_tree = getattr(cls, '_inherit_apply_%s' % position)(
+                    tree, element, target)
+                if new_tree:
+                    tree = new_tree
+        return tree
+
+    @classmethod
+    def _inherit_apply_replace(cls, tree, element, target):
+        parent = target.getparent()
+        if parent is None:
+            tree, = element
+            return tree
+        cls._inherit_apply_after(tree, element, target)
+        parent.remove(target)
+
+    @classmethod
+    def _inherit_apply_replace_attributes(cls, tree, element, target):
+        child, = element
+        for attr in child.attrib:
+            target.set(attr, child.get(attr))
+
+    @classmethod
+    def _inherit_apply_inside(cls, tree, element, target):
+        target.extend(list(element))
+
+    @classmethod
+    def _inherit_apply_after(cls, tree, element, target):
+        parent = target.getparent()
+        next_ = target.getnext()
+        if next_ is not None:
+            for child in element:
+                index = parent.index(next_)
+                parent.insert(index, child)
+        else:
+            parent.extend(list(element))
+
+    @classmethod
+    def _inherit_apply_before(cls, tree, element, target):
+        parent = target.getparent()
+        for child in element:
+            index = parent.index(target)
+            parent.insert(index, child)
+
 
 class ShowViewStart(ModelView):
     'Show view'
diff -r 453649ad7314 -r ce1e97be71de trytond/model/modelview.py
--- a/trytond/model/modelview.py        Sun Jan 23 13:27:52 2022 +0100
+++ b/trytond/model/modelview.py        Sun Jan 23 13:32:59 2022 +0100
@@ -9,9 +9,9 @@
 from trytond.exceptions import UserError
 from trytond.i18n import gettext
 from trytond.pool import Pool
-from trytond.pyson import PYSONDecoder, PYSONEncoder
+from trytond.pyson import PYSONEncoder
 from trytond.rpc import RPC
-from trytond.tools import ClassProperty, is_instance_method
+from trytond.tools import is_instance_method
 from trytond.transaction import Transaction
 
 from . import fields
@@ -25,65 +25,6 @@
     pass
 
 
-def _find(tree, element):
-    if element.tag == 'xpath':
-        res = tree.xpath(element.get('expr'))
-        if res:
-            return res[0]
-    return None
-
-
-def _inherit_apply(tree, inherit):
-    root_inherit = inherit.getroottree().getroot()
-    for element2 in root_inherit:
-        if element2.tag != 'xpath':
-            continue
-        element = _find(tree, element2)
-        if element is not None:
-            pos = element2.get('position', 'inside')
-            if pos == 'replace':
-                parent = element.getparent()
-                if parent is None:
-                    tree, = element2
-                    continue
-                enext = element.getnext()
-                if enext is not None:
-                    for child in element2:
-                        index = parent.index(enext)
-                        parent.insert(index, child)
-                else:
-                    parent.extend(list(element2))
-                parent.remove(element)
-            elif pos == 'replace_attributes':
-                child = element2[0]
-                for attr in child.attrib:
-                    element.set(attr, child.get(attr))
-            elif pos == 'inside':
-                element.extend(list(element2))
-            elif pos == 'after':
-                parent = element.getparent()
-                enext = element.getnext()
-                if enext is not None:
-                    for child in list(element2):
-                        index = parent.index(enext)
-                        parent.insert(index, child)
-                else:
-                    parent.extend(list(element2))
-            elif pos == 'before':
-                parent = element.getparent()
-                for child in list(element2):
-                    index = parent.index(element)
-                    parent.insert(index, child)
-            else:
-                raise AttributeError(
-                    'Unknown position in inherited view %s!' % pos)
-        else:
-            raise AttributeError(
-                'Couldn\'t find tag (%s: %s) in parent view!'
-                % (element2.tag, element2.get('expr')))
-    return tree
-
-
 def on_change(func):
     @wraps(func)
     def wrapper(self, *args, **kwargs):
@@ -99,24 +40,9 @@
     Define a model with views in Tryton.
     """
     __slots__ = ()
-    __modules_list = None  # Cache for the modules list sorted by dependency
     _fields_view_get_cache = Cache('modelview.fields_view_get')
     _view_toolbar_get_cache = Cache('modelview.view_toolbar_get')
 
-    @staticmethod
-    def _reset_modules_list():
-        ModelView.__modules_list = None
-
-    @ClassProperty
-    @classmethod
-    def _modules_list(cls):
-        from trytond.modules import create_graph, get_module_list
-        if ModelView.__modules_list:
-            return ModelView.__modules_list
-        graph = create_graph(get_module_list())
-        ModelView.__modules_list = [x.name for x in graph] + [None]
-        return ModelView.__modules_list
-
     @classmethod
     def __setup__(cls):
         super(ModelView, cls).__setup__()
@@ -260,8 +186,6 @@
         pool = Pool()
         View = pool.get('ir.ui.view')
 
-        view = None
-        inherit_view_id = None
         if view_id:
             view = View(view_id)
         else:
@@ -276,64 +200,13 @@
             views = [v for v in views if v.rng_type == view_type]
             if views:
                 view = views[0]
-        if view:
-            if view.inherit:
-                inherit_view_id = view.id
-                view = view.inherit
+                view_id = view.id
+            else:
+                view = None
 
         # if a view was found
         if view:
-            result['type'] = view.rng_type
-            result['view_id'] = view_id
-            result['arch'] = view.arch
-            result['field_childs'] = view.field_childs
-
-            # Check if view is not from an inherited model
-            if view.model != cls.__name__:
-                Inherit = pool.get(view.model)
-                result['arch'] = Inherit.fields_view_get(view.id)['arch']
-                real_view_id = inherit_view_id
-            else:
-                real_view_id = view.id
-
-            # get all views which inherit from (ie modify) this view
-            views = View.search([
-                    'OR', [
-                        ('inherit', '=', real_view_id),
-                        ('model', '=', cls.__name__),
-                        ], [
-                        ('id', '=', real_view_id),
-                        ('inherit', '!=', None),
-                        ],
-                    ])
-            raise_p = False
-            while True:
-                try:
-                    views.sort(key=lambda x:
-                        cls._modules_list.index(x.module or None))
-                    break
-                except ValueError:
-                    if raise_p:
-                        raise
-                    # There is perhaps a new module in the directory
-                    ModelView._reset_modules_list()
-                    raise_p = True
-            if not result['arch']:
-                raise ValueError("Missing view architecture for %s" % ((
-                            cls.__name__, view_id, view_type),))
-            parser = etree.XMLParser(remove_comments=True)
-            tree = etree.fromstring(result['arch'], parser=parser)
-            for view in views:
-                if view.domain:
-                    if not PYSONDecoder({'context': Transaction().context}
-                            ).decode(view.domain):
-                        continue
-                if not view.arch or not view.arch.strip():
-                    continue
-                tree_inherit = etree.fromstring(view.arch, parser=parser)
-                tree = _inherit_apply(tree, tree_inherit)
-            result['arch'] = etree.tostring(
-                tree, encoding='utf-8').decode('utf-8')
+            result = view.view_get(model=cls.__name__)
 
         # otherwise, build some kind of default view
         else:
@@ -368,9 +241,10 @@
             else:
                 xml = ''
             result['type'] = view_type
+            result['view_id'] = view_id
             result['arch'] = xml
             result['field_childs'] = None
-            result['view_id'] = view_id
+        result['model'] = cls.__name__
 
         if level is None:
             level = 1 if result['type'] == 'tree' else 0

Reply via email to