← Back to team overview

credativ team mailing list archive

lp:~therp-nl/openupgrade-server/7.0-add_compare_noupdate_xml_records into lp:openupgrade-server

 

Stefan Rijnhart (Therp) has proposed merging lp:~therp-nl/openupgrade-server/7.0-add_compare_noupdate_xml_records into lp:openupgrade-server.

Requested reviews:
  OpenUpgrade Committers (openupgrade-committers)

For more details, see:
https://code.launchpad.net/~therp-nl/openupgrade-server/7.0-add_compare_noupdate_xml_records/+merge/176656
-- 
https://code.launchpad.net/~therp-nl/openupgrade-server/7.0-add_compare_noupdate_xml_records/+merge/176656
Your team OpenUpgrade Committers is requested to review the proposed merge of lp:~therp-nl/openupgrade-server/7.0-add_compare_noupdate_xml_records into lp:openupgrade-server.
=== added file 'scripts/compare_noupdate_xml_records.py'
--- scripts/compare_noupdate_xml_records.py	1970-01-01 00:00:00 +0000
+++ scripts/compare_noupdate_xml_records.py	2013-07-24 11:03:24 +0000
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module copyright (C) 2013 Therp BV (<http://therp.nl>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import sys, os, ast, argparse
+from copy import deepcopy
+from lxml import etree
+
+def read_manifest(addon_dir):
+    with open(os.path.join(addon_dir, '__openerp__.py'), 'r') as f:
+        manifest_string = f.read()
+    return ast.literal_eval(manifest_string)
+
+# from openerp.tools
+def nodeattr2bool(node, attr, default=False):
+    if not node.get(attr):
+        return default
+    val = node.get(attr).strip()
+    if not val:
+        return default
+    return val.lower() not in ('0', 'false', 'off')
+
+def get_node_dict(element):
+    res = {}
+    for child in element:
+        if 'name' in child.attrib:
+            key = "./%s[@name='%s']" % (
+                child.tag, child.attrib['name'])
+            res[key] = child
+    return res
+
+def get_node_value(element):
+    if 'eval' in element.attrib.keys():
+        return element.attrib['eval']
+    if 'ref' in element.attrib.keys():
+        return element.attrib['ref']
+    if not len(element):
+        return element.text
+    return etree.tostring(element)
+
+def update_node(target, source):
+    for element in source:
+        if 'name' in element.attrib:
+            query = "./%s[@name='%s']" % (
+                element.tag, element.attrib['name'])
+        else:
+            # query = "./%s" % element.tag
+            continue
+        for existing in target.xpath(query):
+            target.remove(existing)
+        target.append(element)
+
+def get_records(addon_dir):
+    addon_dir = addon_dir.rstrip(os.sep)
+    addon_name = os.path.basename(addon_dir)
+    manifest = read_manifest(addon_dir)
+    # The order of the keys are important.
+    # Load files in the same order as in 
+    # module/loading.py:load_module_graph
+    keys = ['init_xml', 'update_xml', 'data']
+    records_update = {}
+    records_noupdate = {}
+
+    def process_data_node(data_node):
+        noupdate = nodeattr2bool(data_node, 'noupdate', False)
+        record_nodes = data_node.xpath("./record")
+        for record in record_nodes:
+            xml_id = record.get("id")
+            if ('.' in xml_id
+                    and xml_id.startswith(addon_name + '.')):
+                xml_id = xml_id[len(addon_name) + 1:]
+            for records in records_noupdate, records_update:
+                # records can occur multiple times in the same module
+                # with different noupdate settings
+                if xml_id in records:
+                    # merge records (overwriting an existing element
+                    # with the same tag). The order processing the
+                    # various directives from the manifest is
+                    # important here
+                    update_node(records[xml_id], record)
+                    break
+            else:
+                target_dict = (
+                    records_noupdate if noupdate else records_update)
+                target_dict[xml_id] = record
+
+    for key in keys:
+        if not manifest.get(key):
+            continue
+        for xml_file in manifest[key]:
+            xml_path = xml_file.split('/')
+            try:
+                tree = etree.parse(os.path.join(addon_dir, *xml_path))
+            except etree.XMLSyntaxError:
+                continue
+            for data_node in tree.xpath("/openerp/data"):
+                process_data_node(data_node)
+    return records_update, records_noupdate
+
+def main(argv=None):
+    """
+    Attempt to represent the differences in data records flagged with
+    'noupdate' between to different versions of the same OpenERP module.
+
+    Print out a complete XML data file that can be loaded in a post-migration
+    script using openupgrade::load_xml().
+
+    Known issues:
+    - Does not detect if a deleted value belongs to a field
+      which has been removed.
+    - Ignores forcecreate=False. This hardly occurs, but you should
+      check manually for new data records with this tag. Note that
+      'True' is the default value for data elements without this tag.
+    - Does not take csv data into account (obviously)
+    - Is not able to check cross module data
+    - etree's pretty_print is not *that* pretty
+    - Does not take translations into account (e.g. in the case of
+      email templates)
+    """
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        'olddir', metavar='older_module_directory')
+    parser.add_argument(
+        'newdir', metavar='newer_module_directory')
+    arguments = parser.parse_args(argv)
+
+    old_update, old_noupdate = get_records(arguments.olddir)
+    new_update, new_noupdate = get_records(arguments.newdir)
+
+    data = etree.Element("data")
+
+    for xml_id, record_new in new_noupdate.items():
+        record_old = None
+        if xml_id in old_update:
+            record_old = old_update[xml_id]
+        elif xml_id in old_noupdate:
+            record_old = old_noupdate[xml_id]
+    
+        if record_old is None:
+            continue
+
+        element = etree.Element(
+            "record", id=xml_id, model=record_new.attrib['model'])
+        record_old_dict = get_node_dict(record_old)
+        record_new_dict = get_node_dict(record_new)
+        for key in record_old_dict.keys():
+            if not record_new.xpath(key):
+                # The element is no longer present.
+                # Overwrite an existing value with an
+                # empty one. Of course, we do not know
+                # if this field has actually been removed
+                attribs = deepcopy(record_old_dict[key]).attrib
+                for attr in ['eval', 'ref']:
+                    if attr in attribs:
+                        del attribs[attr]
+                element.append(etree.Element(record_old_dict[key].tag, attribs))
+            else:
+                oldrepr = get_node_value(record_old_dict[key])
+                newrepr = get_node_value(record_new_dict[key])
+            
+                if oldrepr != newrepr:
+                    element.append(deepcopy(record_new_dict[key]))
+
+        for key in record_new_dict.keys():
+            if not record_old.xpath(key):
+                element.append(deepcopy(record_new_dict[key]))
+
+        if len(element):
+            data.append(element)
+
+    openerp = etree.Element("openerp")
+    openerp.append(data)
+    document = etree.ElementTree(openerp)
+
+    print etree.tostring(
+        document, pretty_print=True, xml_declaration=True, encoding='utf-8')
+
+if __name__ == "__main__":
+    main()
+


Follow ups