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