diff --git a/base_external_dbsource/__init__.py b/base_external_dbsource/__init__.py new file mode 100755 index 000000000..81d83fc87 --- /dev/null +++ b/base_external_dbsource/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Daniel Reis +# 2011 +# +# 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 . +# +############################################################################## + +import base_external_dbsource + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/base_external_dbsource/__openerp__.py b/base_external_dbsource/__openerp__.py new file mode 100644 index 000000000..f40b09a90 --- /dev/null +++ b/base_external_dbsource/__openerp__.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Daniel Reis, 2011 +# Additional contributions by Maxime Chambreuil, Savoir-faire Linux +# +# 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 . +# +############################################################################## + +{ + 'name': 'External Database Sources', + 'version': '61.3', + 'category': 'Tools', + 'description': """ +This module allows you to define connections to foreign databases using ODBC, +Oracle Client or SQLAlchemy. + +Databases sources can be configured in Settings > Configuration -> Data sources. + +Depending on the database, you need: + * to install unixodbc and python-pyodbc packages to use ODBC connections. + * to install FreeTDS driver (tdsodbc package) and configure it through ODBC to + connect to Microsoft SQL Server. + * to install and configure Oracle Instant Client and cx_Oracle python library + to connect to Oracle. + """, + 'author': 'Daniel Reis', + 'website': 'http://launchpad.net/addons-tko', + 'images': [ + 'images/screenshot01.png', + ], + 'depends': [ + 'base', + ], + 'init': [], + 'data': [ + 'base_external_dbsource_view.xml', + 'security/ir.model.access.csv', + ], + 'demo': [ + 'base_external_dbsource_demo.xml', + ], + 'test': [ + 'dbsource_connect.yml', + ], + 'installable': True, + 'active': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/base_external_dbsource/base_external_dbsource.py b/base_external_dbsource/base_external_dbsource.py new file mode 100644 index 000000000..92ff9b3de --- /dev/null +++ b/base_external_dbsource/base_external_dbsource.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Daniel Reis +# 2011 +# +# 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 . +# +############################################################################## + +import os +from osv import fields, osv +from openerp.tools.translate import _ +import openerp.tools as tools +import logging +_logger = logging.getLogger(__name__) + +CONNECTORS = [] + +try: + import sqlalchemy + import pymssql + CONNECTORS.append( ('mssql', 'Microsoft SQL Server') ) +except: + _logger.info('MS SQL Server not available. Please install "slqalchemy" and "pymssql" python package.') + +try: + import sqlalchemy + import MySQLdb + CONNECTORS.append( ('mysql', 'MySQL') ) +except: + _logger.info('MySQL not available. Please install "slqalchemy" and "mysqldb" python package.') + +try: + import pyodbc + CONNECTORS.append( ('pyodbc', 'ODBC') ) +except: + _logger.info('ODBC libraries not available. Please install "unixodbc" and "python-pyodbc" packages.') + +try: + import cx_Oracle + CONNECTORS.append( ('cx_Oracle', 'Oracle') ) +except: + _logger.info('Oracle libraries not available. Please install "cx_Oracle" python package.') + +import psycopg2 +CONNECTORS.append( ('postgresql', 'PostgreSQL') ) + +try: + import sqlalchemy + CONNECTORS.append( ('sqlite', 'SQLite') ) +except: + _logger.info('SQLAlchemy not available. Please install "slqalchemy" python package.') + +class base_external_dbsource(osv.osv): + _name = "base.external.dbsource" + _description = 'External Database Sources' + _columns = { + 'name': fields.char('Datasource name', required=True, size=64), + 'conn_string': fields.text('Connection string', help="""\ +Sample connection strings: +- Microsoft SQL Server: mssql+pymssql://username:%s@server:port/dbname?charset=utf8 +- MySQL: mysql://user:%s@server:port/dbname +- ODBC: DRIVER={FreeTDS};SERVER=server.address;Database=mydb;UID=sa +- ORACLE: username/%s@//server.address:port/instance +- PostgreSQL: dbname='template1' user='dbuser' host='localhost' port='5432' password=%s +- SQLite: sqlite:///test.db +"""), + 'password': fields.char('Password' , size=40), + 'connector': fields.selection(CONNECTORS, 'Connector', required=True, + help = "If a connector is missing from the list, check the " \ + + "server log to confirm that the required componentes were detected."), + } + + def conn_open(self, cr, uid, id1): + #Get dbsource record + data = self.browse(cr, uid, id1) + #Build the full connection string + connStr = data.conn_string + if data.password: + if '%s' not in data.conn_string: + connStr += ';PWD=%s' + connStr = connStr % data.password + #Try to connect + if data.connector == 'cx_Oracle': + os.environ['NLS_LANG'] = 'AMERICAN_AMERICA.UTF8' + conn = cx_Oracle.connect(connStr) + elif data.connector == 'pyodbc': + conn = pyodbc.connect(connStr) + elif data.connector in ('sqlite','mysql','mssql'): + conn = sqlalchemy.create_engine(connStr).connect() + elif data.connector == 'postgresql': + conn = psycopg2.connect(connStr) + + return conn + + def execute_and_inspect(self, cr, uid, ids, sqlquery, sqlparams=None, context=None): + """Executes SQL and returns a dict with the rows and the list of columns. + + "sqlparams" can be a dict of values, that can be referenced in the SQL statement + using "%(key)s" or, in the case of Oracle, ":key". + Example: + sqlquery = "select * from mytable where city = %(city)s and date > %(dt)s" + params = {'city': 'Lisbon', 'dt': datetime.datetime(2000, 12, 31)} + + Sample result: { 'colnames'; ['col_a', 'col_b', ...] + , 'rows': [ (a0, b0, ...), (a1, b1, ...), ...] } + """ + data = self.browse(cr, uid, ids) + nams = dict() + rows = list() + for obj in data: + conn = self.conn_open(cr, uid, obj.id) + if obj.connector in ["sqlite","mysql","mssql"]: + #using sqlalchemy + cur = conn.execute(sqlquery, sqlparams) + nams = cur.keys() + rows = [r for r in cur] + else: + #using other db connectors + cur = conn.cursor() + cur.execute(sqlquery, sqlparams) + nams = [d[0] for d in cur.description] + rows = cur.fetchall() + conn.close() + return {'colnames': nams, 'rows': rows} + + def execute(self, cr, uid, ids, sqlquery, sqlparams=None, context=None): + return self.execute_and_inspect(self, cr, uid, ids, sqlquery, sqlparams)['rows'] + + def connection_test(self, cr, uid, ids, context=None): + for obj in self.browse(cr, uid, ids, context): + conn = False + try: + conn = self.conn_open(cr, uid, obj.id) + except Exception, e: + raise osv.except_osv(_("Connection test failed!"), _("Here is what we got instead:\n %s") % tools.ustr(e)) + finally: + try: + if conn: conn.close() + except Exception: + # ignored, just a consequence of the previous exception + pass + #TODO: if OK a (wizard) message box should be displayed + raise osv.except_osv(_("Connection test succeeded!"), _("Everything seems properly set up!")) + +base_external_dbsource() diff --git a/base_external_dbsource/base_external_dbsource_demo.xml b/base_external_dbsource/base_external_dbsource_demo.xml new file mode 100755 index 000000000..29e4e2eb8 --- /dev/null +++ b/base_external_dbsource/base_external_dbsource_demo.xml @@ -0,0 +1,15 @@ + + + + + + PostgreSQL local + dbname='postgres' password=%s + postgresql + postgresql + + + + + + diff --git a/base_external_dbsource/base_external_dbsource_view.xml b/base_external_dbsource/base_external_dbsource_view.xml new file mode 100644 index 000000000..2be95a06f --- /dev/null +++ b/base_external_dbsource/base_external_dbsource_view.xml @@ -0,0 +1,54 @@ + + + + + + + + base.external.dbsource.tree + base.external.dbsource + tree + + + + + + + + + + + base.external.dbsource.form + base.external.dbsource + form + +
+ + + + + + + +