Script - datalog download form (Jython)

Summary

The manufacturing company I work for is demoing an OPC-based data acquisition solution that stores its logs in a MySQL database. These scripts build a form that allows a user to select data by serial number and download copy as a CSV file.

This demonstrates database connectivity using the zxJDBC library, plus form generation in Jython. Using zxJDBC required that I replace the installed Jython JAR with the Jython 2.5.2 JAR.

I'm still working on it, so feedback is welcome!

JFileChooser

Just found a problem with JFileChooser. No matter where the page is accessed from, the save dialog opens on the server. Not sure if it's something in the script or what.

Code
{jython:output=wiki}
'''
KEPServerEX Purifier Datalog Request Form

This script allows the user to request a datalog by serial number then passes
the request to the datalog retrieval script. To build the form, the script
queries the datalogging DB for all available tables and then creates a run
macro selection list populated with those entries.
'''

from __future__ import with_statement

from com.ziclix.python.sql import zxJDBC


def query_db(query):
    '''
    Query the datalogging DB, return the results, and close the connection.

    Use of JNDI lookup to locate a preconfigured datasource, instead of direct
    connection, is recommended when running Jython in an application server
    like Tomcat. See <confluence install>/conf/server.xml for the datasource
    configuration.
    '''

    with zxJDBC.lookup('java:/comp/env/jdbc/datalogging') as conn:
        with conn.cursor() as c:
            c.execute(query)
            return c.fetchall()


def build_selection_list():
    '''
    Returns wiki markup to create a selection list given the body text.

    This wiki markup creates a selection list of DB tables as a run macro. It
    is assumed that all table names begin with an underscore.
    '''

    heading = '{run:id=request|' \
              'titleRun=Download|' \
              'hideColumnHeadings=true|' \
              'heading=Serial #|' \
              'replace=serial:::select-required:25:'

    result_set = query_db('SHOW TABLES')
    for row in result_set:
        heading += row[0].lstrip('_') + '::'

    return heading + '}\n' \
           '{goto-url:url=' \
           'http://localhost:8085/display/~easjos/Purifier+Datalogs?' \
           'run_retrieve_serial=$serial&run_retrieve=run}\n' + \
           '{run}\n'


#Main routine

print(build_selection_list())

{jython}


{run:id=retrieve|replace=serial|hideParameters=true|hideRun=true}

{jython:serial_num=$serial}
'''
KEPServerEX Purifier Datalog Retrieval

Upon user-confirmation, this script establishes a connection to the
datalogging database and queries for the datalog matching the supplied serial
number/table name. The log is returned to the user-specifed location as a CSV
file.
'''

from __future__ import with_statement

import csv

from com.ziclix.python.sql import zxJDBC
from java.io import File
from javax.swing import UIManager, JFileChooser, JFrame


def get_save_destination(default_file_name):
    '''
    Display a file save dialog to the user and return the file path or None.

    Jython relies on Java Swing to create UI elements, but to avoid mixing
    languages too much, JFileChooser is only used to determine the file path.
    The file reference it returns is discarded, and Jython file handling is
    used from that point.
    '''

    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())

    chooser = JFileChooser()
    chooser.setSelectedFile(File(default_file_name))
    user_choice = chooser.showSaveDialog(None)

    if user_choice == JFileChooser.APPROVE_OPTION:
        return chooser.getSelectedFile().getPath()
    else:
        return None


def query_db(query):
    '''
    Query the datalogging DB, return the results, and close the connection.

    Use of JNDI lookup to locate a preconfigured datasource, instead of direct
    connection, is recommended when running Jython in an application server
    like Tomcat. See <confluence install>/conf/server.xml for the datasource
    configuration.
    '''

    with zxJDBC.lookup('java:/comp/env/jdbc/datalogging') as conn:
        with conn.cursor() as c:
            c.execute(query)
            return c.fetchall()


def retrieve_datalog(serial):
    '''
    Formats and returns the datalog for the supplied table name.

    Datalog is returned as a sequence of sequences.
    '''

    table_columns = retrieve_columns(serial)

    result_set = query_db(
        'SELECT ' + str.join(', ', table_columns) + '\n' + \
        'FROM _' + serial + '\n')

    headings = ['TIMESTAMP']
    headings.extend([get_tag_name(column) for column in table_columns[1:]])
    result_set.insert(0, headings)

    return result_set


def retrieve_columns(serial):
    '''
    Returns a list of the required columns for the desired table.

    Includes the first TIMESTAMP column and any VALUE columns.
    '''

    result_set = query_db('SHOW COLUMNS\n' \
                          'FROM _' + serial + '\n' \
                          'LIKE "%\_VALUE"\n')

    result_set = [row[0] for row in result_set]
    result_set.insert(0, result_set[0].rstrip('VALUE') + 'TIMESTAMP')

    return result_set


def get_tag_name(column):
    '''
    Returns just the tag name from the supplied column heading

    Also strips off READ if that has been appended to the tag name
    '''

    tokens = column.split('_')
    tokens = tokens[2:len(tokens) - 1]

    if tokens[len(tokens) - 1] == 'READ':
        tokens = tokens[:len(tokens) - 1]

    return str.join('_', tokens)


def save_as_csv(file_path, datalog):
    '''
    Save datalog to specified location as a CSV file.
    '''

    with open(file_path, 'w') as csv_file:
        csv_writer = csv.writer(csv_file, lineterminator = '\n')

        for entry in datalog:
            csv_writer.writerow(entry)


#Main routine

file_path = get_save_destination(serial_num + '.csv')

if file_path:
    save_as_csv(file_path, retrieve_datalog(serial_num))
    print('Log saved to ' + file_path + '\n')
else:
    print('Operation cancelled.')

{jython}

{run}
\\
\\
\\
\\
{jython}
'''Print the Jython version information'''

import sys

print('Built using Jython ' + sys.version.partition('(')[0])
{jython}