From d6d3387c360ea84ec212e9fc806b483250d3baa3 Mon Sep 17 00:00:00 2001
From: Renaud Le Gac <legac@cppm.in2p3.fr>
Date: Wed, 17 Oct 2012 17:03:01 +0200
Subject: [PATCH] Add a the class Selector to help building reports.

---
 controllers/reports.py          |  35 ++-----
 modules/plugin_dbui/__init__.py |   1 +
 modules/plugin_dbui/report.py   | 169 ++++++++++++++++++++++++++++++++
 static/plugin_dbui/CHANGELOG    |   3 +-
 4 files changed, 181 insertions(+), 27 deletions(-)
 create mode 100644 modules/plugin_dbui/report.py

diff --git a/controllers/reports.py b/controllers/reports.py
index 8893c987..e52fc824 100644
--- a/controllers/reports.py
+++ b/controllers/reports.py
@@ -1,6 +1,8 @@
 """ Collection of controllers to build reports 
 
 """
+from plugin_dbui import Selector
+
 
 def index():
     """Main Controller handling report.
@@ -20,29 +22,12 @@ def report_2():
     """Return the url arguments
     
     """
-
-    vars = request.vars
-    fmt = vars.Foo1My_format 
+    selector = Selector(db, extfield='my_format')
+    
     # user request a file with a specific format (latex,...)
-    # return to the browser na iframe balise with a new URL
-    # the browser will request the file locate at the url
-    # The iframe occupy all the space in the receiving container
-    # it is usefull for embeded application like acroread.
-    if  fmt and fmt != 'html':
-
-        del vars.Foo1My_format
-
-        s = IFRAME(_id='MyId',
-                   _frameborder=0,
-                   _width="100%",
-                   _height="100%",
-                   _src=URL('reports', 'report_2.%s' % fmt, vars=vars)).xml()
-
-        # remove the field use to select the file format
-        # in order to avoid circular loop
-        del fmt
-
-        return s
-
-    # standart reponse
-    return dict(test=vars)
\ No newline at end of file
+    iframe = selector.download()
+    if iframe:
+        return iframe
+    
+    # standart response with the selected values
+    return dict(test=selector.as_dict())
\ No newline at end of file
diff --git a/modules/plugin_dbui/__init__.py b/modules/plugin_dbui/__init__.py
index df358a1b..208c9c5c 100755
--- a/modules/plugin_dbui/__init__.py
+++ b/modules/plugin_dbui/__init__.py
@@ -68,6 +68,7 @@ from helper import (as_list,
                     rows_serializer)
 from mapper import map_default, map_tabpanel
 from navtree import Node
+from report import Selector
 from viewportmodifier import ViewportModifier
 
 UNDEF = 'undefined'
diff --git a/modules/plugin_dbui/report.py b/modules/plugin_dbui/report.py
new file mode 100644
index 00000000..7220f5e7
--- /dev/null
+++ b/modules/plugin_dbui/report.py
@@ -0,0 +1,169 @@
+""" Base tools to build report
+
+A database report relies on four main components:
+    - A selector form allowing the user to defined conditions.
+      It can be build using a virtual database,
+    - The Selector class to decode the selected values and to build query.
+    - A controller to extract value from the selector and 
+      to interrogate the database.
+    - A view to render the selected records.
+
+A powerful mechanism is also in place to render the report in different format
+like HTML, CSV, .... It relies on the Selector class, on the IFRAME and 
+on the view.
+
+"""
+
+from gluon import current
+from gluon.html import IFRAME, URL
+from gluon.storage import Storage
+from helper import (decode_field, 
+                    get_foreign_field, 
+                    is_foreign_field, 
+                    is_table_with_foreign_fields)
+                         
+
+class Selector(Storage):
+    """Basic tool to build a report. 
+    
+    Decode the data send by the selector widget. 
+    Build the query for a given table of the database.
+
+    All fields of the selector are attributes of this class.
+    They can be accessed via myselector.myfield or myselector['myfield'].
+    
+    A mechanism is provide to download the report in different format
+    which can be selected by the user. It relies on the extfield and
+    on the method download. Inside a controller:
+    
+        selector = Selector(db, extfield='format')
+        iframe = selector.download()
+        if iframe:
+            return iframe
+            
+    """    
+    def __init__(self, db, extfield='format'):
+
+        self._db = db
+        self._ext_field = extfield
+        self._extension = None
+        
+        # Decode the current request
+        for key in current.request.vars:
+            t = decode_field(key)
+            
+            if len(t) != 2 :
+                continue
+            
+            field = t[1]
+            if field == self._ext_field:
+                self._extension = current.request.vars[key]
+                
+            elif field != 'id':
+                self[field] = current.request.vars[key]
+                
+
+    def as_dict(self):
+        """Return the selector fields and their values as a dictionary.
+        
+        """
+        di = {}
+        for field in self.fields():
+            di[field] = self[field]
+            
+        return di
+    
+    
+    def download(self):
+        """The report can be download and receive as a file with a given format.
+        this method initiate this process by return and IFRAME.
+        The IFRAME is used by the browser to download the file.
+        
+        The file format is defined by one of the field of the list selector.
+        By default is it 'format'. It can be changed via the argument extfield
+        of the constructor.
+        
+        The IFRAME contain and URL. it it the same as the one defined in the
+        current request (application/controller/function) but the extension 
+        is defined by the format field.
+        
+        In this process all data conversion are performed by the view.
+        
+        """
+        if  self._extension and self._extension != 'html':
+    
+            # remove the field use to select the file format
+            # in order to avoid circular loop
+            for key in current.request.vars:
+                t = decode_field(key)
+                if self._ext_field in t:
+                    del current.request.vars[key]
+                    break
+    
+            # The new URL is equal to the one defined in the current
+            # request but with the proper extension
+            url = URL(current.request.function, extension=self._extension, 
+                                                vars=current.request.vars)
+            
+            # Return the IFRAME
+            # the browser will request the file locate at the URL of the IFRAME
+            # The IFRAME occupy all the space in the receiving container
+            # Useful for embedded pdf application.
+            return IFRAME(_frameborder=0,
+                          _width="100%",
+                          _height="100%",
+                          _src=url)
+
+        return None
+
+
+    def fields(self):
+        """Lists of fields defined in the selector.
+        
+        """
+        li = []
+        for k in self:
+            if not k.startswith('_'):
+                li.append(k)
+        return li
+
+
+    def query(self, tablename, exclude_fields=()):
+        """Build the database query for the table tablename
+        including inner join for foreign keys and selector constraints.
+        
+        The extfield and fields in the exclude_fields tuple are 
+        not take into account.
+        
+        """
+        db = self._db
+        query = None
+        table = self._db[tablename]
+    
+        # inner join for foreign keys
+        if is_table_with_foreign_fields(table):
+            for field in table:
+                if is_foreign_field(field):
+                    k_table, k_field, k_id = get_foreign_field(field)
+                    q = field == db[k_table][k_id]
+                    if query:
+                        query = (query) & (q)
+                    else:
+                        query = q
+                        
+        # constraint from the selector
+        for fieldname in self.fields():
+            
+            if not self[fieldname] or \
+               fieldname in exclude_fields or \
+               fieldname == self._ext_field:
+                continue
+            
+            q = db[tablename][fieldname] == self[fieldname]
+            
+            if query:
+                query = (query) & (q)
+            else:
+                query = q
+
+        return query
\ No newline at end of file
diff --git a/static/plugin_dbui/CHANGELOG b/static/plugin_dbui/CHANGELOG
index f72f1a01..5789d66d 100644
--- a/static/plugin_dbui/CHANGELOG
+++ b/static/plugin_dbui/CHANGELOG
@@ -1,8 +1,6 @@
 --------------------------------- CHANGE LOG ----------------------------------
 
 HEAD
-
-0.4.9 (Oct 2012)
   - New syntax for grid filter via the method GridModifier.append_filter.
   - Improve the files organization for the model and javascript code.
   - Enable tab scrolling in viewport.
@@ -10,6 +8,7 @@ HEAD
   - Add a new widget LinkedComboBox.
   - Add a plugin pRegExp and remove custom widget app.form.TextField 
     and app.form.TextArea.
+  - Add a new python class Selector to help building reports
   
 0.4.8.2 (Jul 2012)
   - Consolidation version
-- 
GitLab