[Formed-commits] r327 - in trunk: . formed/formed formed/formed/plugins/export

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Fri Apr 24 14:29:33 CEST 2009


Author: teichmann
Date: 2009-04-24 14:29:32 +0200 (Fri, 24 Apr 2009)
New Revision: 327

Added:
   trunk/formed/formed/plugins/export/rg_sql.py
Modified:
   trunk/ChangeLog
   trunk/formed/formed/config.py
Log:
New plug-in to generate tree structures in database.


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2009-04-16 15:18:22 UTC (rev 326)
+++ trunk/ChangeLog	2009-04-24 12:29:32 UTC (rev 327)
@@ -1,3 +1,10 @@
+2009-04-24	Sascha L. Teichmann <teichmann at intevation.de>
+
+	* formed/formed/plugins/export/rg_sql.py: new plug-in to generate
+	  tree structure functions in database.
+
+	* formed/formed/config.py: load plug-in.
+
 2009-04-16	Sascha L. Teichmann <teichmann at intevation.de>
 
 	* formed/formed/model/data.py: Added 'digest' attribute to 

Modified: trunk/formed/formed/config.py
===================================================================
--- trunk/formed/formed/config.py	2009-04-16 15:18:22 UTC (rev 326)
+++ trunk/formed/formed/config.py	2009-04-24 12:29:32 UTC (rev 327)
@@ -33,7 +33,8 @@
 #    ExportStoredProcedureFilter, \
 #    ExportViewsFilter
 
-from formed.plugins.export.sql  import ExportAsSQLFilter
+from formed.plugins.export.sql    import ExportAsSQLFilter
+from formed.plugins.export.rg_sql import ExportAsRepeatGroupSQLFilter
 
 from formed.plugins.export.data import DataImportFilter, DataExportFilter
 
@@ -57,6 +58,7 @@
             ExportDiffAsHTMLFilter(),
             ExportAsTypeDict(),
             ExportAsSQLFilter(),
+            ExportAsRepeatGroupSQLFilter(),
 #            ExportStoredProcedureFilter(),
 #            ExportViewsFilter(),
             ExportAsXMLSchema(),

Added: trunk/formed/formed/plugins/export/rg_sql.py
===================================================================
--- trunk/formed/formed/plugins/export/rg_sql.py	2009-04-16 15:18:22 UTC (rev 326)
+++ trunk/formed/formed/plugins/export/rg_sql.py	2009-04-24 12:29:32 UTC (rev 327)
@@ -0,0 +1,283 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2007 by Intevation GmbH
+#
+# FormEd is free software under the terms of the
+# GNU General Public License Version 3. See file
+# LICENSE coming with the source of FormEd for details.
+#
+# Authors:
+# Sascha L. Teichmann <teichmann at intevation.de>
+#
+
+from formed.plugins.ui.controls import FileDialogFilter
+
+from formed.model.misc import checkMode, ModeChecker
+
+import formed.model.data as data
+from formed.model.nodecomponents import Node, Leaf
+
+from string import Template
+
+import codecs
+
+SQL_TEMPLATE = Template(
+'''
+--
+-- THIS IS MACHINE GENERATED BY FormEd. BE CAREFUL WITH MANUAL EDITING
+--
+BEGIN;
+
+-- need to be postgres because of untrusted language
+
+-- DROP FUNCTION get_case_structure(int4);
+CREATE OR REPLACE FUNCTION get_case_structure(case_id int4) RETURNS TEXT AS $$
+
+class Node(object):
+
+    def __init__(self, name, id = None, children = None):
+        self.name     = name
+        self.id       = id
+        self.children = children
+
+    def to_json(self, out):
+        out.append('{"name":"%s","id":%d' % (self.name, self.id))
+        if self.children:
+            out.append(',"children":[')
+            first = True
+            for child in self.children:
+                if first: first = False
+                else:     out.append(",")
+                child.to_json(out)
+            out.append("]")
+        out.append("}")
+
+    def recursive_build(self, mid, children):
+        select = "SELECT id FROM %s WHERE master_id = %d" % (
+            self.name, mid)
+        ids = [r['id'] for r in plpy.execute(select)]
+        for id in ids:
+            sub_children = []
+            node = Node(self.name, id, sub_children)
+            for child in self.children:
+                child.recursive_build(id, sub_children)
+            children.append(node)
+
+TREE_STRUCTURE = \    
+$TREE_STRUCTURE
+
+r = plpy.execute(
+    "SELECT modified, cache FROM case_structure "
+    "WHERE master_id = %d" % case_id, 1)
+
+if not r: return ""
+r = r[0]
+
+if r['modified']: # need refresh
+    children = []
+    tree = Node(TREE_STRUCTURE.name, case_id, children)
+    for child in TREE_STRUCTURE.children:
+        child.recursive_build(case_id, children)
+    out = []
+    tree.to_json(out)
+    cache = "".join(out)
+    plan = plpy.prepare(
+        "UPDATE case_structure SET modified = false, "
+        "cache = $1 WHERE master_id = $2", ["text", "int4"])
+    plpy.execute(plan, [cache, case_id])
+    return cache
+
+return r['cache']
+
+$$ LANGUAGE plpythonu;
+
+--
+-- These triggers are used to keep case_structure table in sync.
+-- Each time a repeat group is created or deleted the case_structure
+-- modified field has to be set to 'true'.
+--
+
+$TRIGGERS
+
+COMMIT;
+''')
+
+TRIGGER_TMPL = Template(
+'''
+-- DROP TRIGGER ${RELATION}_insert_trigger ON master_tbl CASCADE;
+-- DROP FUNCTION ${RELATION}_insert_func();
+-- DROP TRIGGER ${RELATION}_delete_trigger ON master_tbl CASCADE;
+-- DROP FUNCTION ${RELATION}_delete_func();
+
+CREATE OR REPLACE FUNCTION ${RELATION}_insert_func() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE case_structure SET modified = true WHERE master_id
+    ${SUBSELECT_INSERT}
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER ${RELATION}_insert_trigger AFTER INSERT ON rg1_rg
+    FOR EACH ROW EXECUTE PROCEDURE ${RELATION}_insert_func();
+
+CREATE OR REPLACE FUNCTION ${RELATION}_delete_func() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE case_structure SET modified = true WHERE master_id
+    ${SUBSELECT_DELETE}
+    RETURN OLD;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER ${RELATION}_delete_trigger AFTER DELETE ON rg1_rg
+    FOR EACH ROW EXECUTE PROCEDURE ${RELATION}_delete_func();
+''')
+
+SUBSELECT_TMPL = Template(
+'''IN (
+        SELECT master_tbl.id FROM $RELATION 
+        $INNER_JOINS
+        WHERE $RELATION.id = $NEW_OLD.id
+    )''')
+
+class RGNode(object):
+
+    def __init__(self, name, min = 1, children = None, parent = None):
+        if name == "master": self.name = "master_tbl"
+        else:                self.name = "rg_%s_tbl" % name.replace('-', '_')
+        self.min      = min
+        if children is None: children = []
+        self.children = children
+        self.parent   = parent
+
+    def to_tree_structure(self, out, indent = 0):
+        ind = "    " * indent
+
+        if not self.children:
+            out.append("%sNode('%s')" % (ind, self.name))
+        else:
+            out.append("%sNode('%s', children = [\n" % (ind, self.name))
+            for idx, child in enumerate(self.children):
+                child.to_tree_structure(out, indent + 1)
+                out.append((idx < len(self.children) - 1) and ",\n" or "\n")
+            out.append("%s])" % ind)
+
+    def create_triggers(self, out):
+        if self.parent: # only when not master_tbl
+            current = self
+            inner_joins = []
+            while current.parent:
+                inner_joins.append(
+                    "INNER JOIN %(parent)s ON %(current)s.master_id = %(parent)s.id" % {
+                        'current': current.name,
+                        'parent':  current.parent.name
+                    })
+                current = current.parent
+            inner_joins = "\n        ".join(inner_joins)
+
+            out.append(TRIGGER_TMPL.safe_substitute({
+                "RELATION": self.name,
+                "SUBSELECT_INSERT": SUBSELECT_TMPL.safe_substitute({
+                    "RELATION"   : self.name,
+                    "INNER_JOINS": inner_joins,
+                    "NEW_OLD"    : "NEW" 
+                }),
+                "SUBSELECT_DELETE": SUBSELECT_TMPL.safe_substitute({
+                    "RELATION"   : self.name,
+                    "INNER_JOINS": inner_joins,
+                    "NEW_OLD"    : "OLD" 
+                })
+            }))
+        for child in self.children:
+            child.create_triggers(out)
+
+
+class RGCreator(ModeChecker):
+
+    def __init__(self, mode, allModes):
+        ModeChecker.__init__(self, mode, allModes, None)
+
+    def createRGs(self, root):
+        self.prepareDescent()
+        self.stack = [RGNode("master", 1, [])]
+        self._createRecursive(root)
+        return self.stack.pop()
+
+    @checkMode
+    def _createRecursive(self, node):
+        for child in node.children:
+            self._createChild(child)
+
+    @checkMode
+    def _createRepeatGroup(self, child):
+        min = child.getMin()
+        if not min: min = 1
+        min = int(min)
+        rg = RGNode(child.getName(), min, [], self.stack[-1])
+        self.stack.append(rg)
+        self._createRecursive(child)
+        self.stack.pop()
+        self.stack[-1].children.append(rg)
+
+    @checkMode
+    def _createChild(self, child):
+        if isinstance(child, Leaf):
+            return
+        elif isinstance(child, Node):
+            if isinstance(child, data.RepeatNode):
+                self._createRepeatGroup(child)
+            else:
+                self._createRecursive(child)
+
+class ExportAsRepeatGroupSQLFilter(FileDialogFilter):
+
+    def __init__(self):
+        FileDialogFilter.__init__(self)
+
+    def getMenuName(self):
+        return _("Export As &Repeat Group SQL...")
+
+    def getDescription(self):
+        return _("Exports document as repeat group SQL statements")
+
+    def wildcards(self):
+        return _("SQL files (*.sql)|*.sql")
+
+    def dialogMessage(self):
+        return _("Export as RG SQL statements")
+
+    def errorTitle(self):
+        return _("Error occured while exporting RG SQL")
+
+    def beforeFileDialog(self, main):
+        pass
+
+    def doExport(self, path, main, tup):
+        root     = main.getDocument().root
+        mode     = main.getSelectedMode()
+        allModes = main.getAllModes()
+        creator  = RGCreator(mode, allModes)
+        rg_root  = creator.createRGs(root)
+
+        out = []
+        rg_root.to_tree_structure(out)
+        tree_structure = "".join(out)
+
+        out = []
+        rg_root.create_triggers(out)
+        triggers = "".join(out)
+
+        f = None
+        try:
+            out = SQL_TEMPLATE.safe_substitute({
+                "TREE_STRUCTURE": tree_structure,
+                "TRIGGERS"      : triggers
+            })
+            f = codecs.open(path, "w", "UTF-8")
+            print >> f, out
+        finally:
+            if f:
+                try: f.close()
+                except: pass
+
+
+# vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 enc=utf-8:



More information about the Formed-commits mailing list