Browse Source
[IMP] base_external_dbsource: Refactor & Split by source
[IMP] base_external_dbsource: Refactor & Split by source
* Heavily refactor code for reusability * Split all sources into independent modules * Add more test coverage * Add CRUD methods * Add iterator execute return to roadmappull/659/head
Dave Lasley
8 years ago
No known key found for this signature in database
GPG Key ID: 7DDBA4BA81B934CF
56 changed files with 1827 additions and 217 deletions
-
2.travis.yml
-
33base_external_dbsource/README.rst
-
1base_external_dbsource/__init__.py
-
6base_external_dbsource/__manifest__.py
-
13base_external_dbsource/exceptions.py
-
1base_external_dbsource/models/__init__.py
-
425base_external_dbsource/models/base_external_dbsource.py
-
2base_external_dbsource/tests/__init__.py
-
247base_external_dbsource/tests/test_base_external_dbsource.py
-
66base_external_dbsource/tests/test_create_dbsource.py
-
77base_external_dbsource_firebird/README.rst
-
2base_external_dbsource_firebird/__init__.py
-
28base_external_dbsource_firebird/__manifest__.py
-
9base_external_dbsource_firebird/demo/base_external_dbsource.xml
-
3base_external_dbsource_firebird/models/__init__.py
-
56base_external_dbsource_firebird/models/base_external_dbsource.py
-
3base_external_dbsource_firebird/tests/__init__.py
-
41base_external_dbsource_firebird/tests/test_base_external_dbsource.py
-
80base_external_dbsource_mssql/README.rst
-
2base_external_dbsource_mssql/__init__.py
-
29base_external_dbsource_mssql/__manifest__.py
-
9base_external_dbsource_mssql/demo/base_external_dbsource.xml
-
3base_external_dbsource_mssql/models/__init__.py
-
45base_external_dbsource_mssql/models/base_external_dbsource.py
-
3base_external_dbsource_mssql/tests/__init__.py
-
42base_external_dbsource_mssql/tests/test_base_external_dbsource.py
-
80base_external_dbsource_mysql/README.rst
-
2base_external_dbsource_mysql/__init__.py
-
29base_external_dbsource_mysql/__manifest__.py
-
9base_external_dbsource_mysql/demo/base_external_dbsource.xml
-
3base_external_dbsource_mysql/models/__init__.py
-
43base_external_dbsource_mysql/models/base_external_dbsource.py
-
3base_external_dbsource_mysql/tests/__init__.py
-
42base_external_dbsource_mysql/tests/test_base_external_dbsource.py
-
79base_external_dbsource_odbc/README.rst
-
2base_external_dbsource_odbc/__init__.py
-
29base_external_dbsource_odbc/__manifest__.py
-
9base_external_dbsource_odbc/demo/base_external_dbsource.xml
-
3base_external_dbsource_odbc/models/__init__.py
-
42base_external_dbsource_odbc/models/base_external_dbsource.py
-
3base_external_dbsource_odbc/tests/__init__.py
-
46base_external_dbsource_odbc/tests/test_base_external_dbsource.py
-
79base_external_dbsource_oracle/README.rst
-
2base_external_dbsource_oracle/__init__.py
-
25base_external_dbsource_oracle/__manifest__.py
-
3base_external_dbsource_oracle/models/__init__.py
-
47base_external_dbsource_oracle/models/base_external_dbsource.py
-
77base_external_dbsource_sqlite/README.rst
-
2base_external_dbsource_sqlite/__init__.py
-
28base_external_dbsource_sqlite/__manifest__.py
-
9base_external_dbsource_sqlite/demo/base_external_dbsource.xml
-
3base_external_dbsource_sqlite/models/__init__.py
-
59base_external_dbsource_sqlite/models/base_external_dbsource.py
-
3base_external_dbsource_sqlite/tests/__init__.py
-
42base_external_dbsource_sqlite/tests/test_base_external_dbsource.py
-
5requirements.txt
@ -1,2 +1,3 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
|
|
||||
from . import models |
from . import models |
@ -0,0 +1,13 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
|
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
class ConnectionFailedError(UserError): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class ConnectionSuccessError(UserError): |
||||
|
pass |
@ -1,2 +1,3 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
|
|
||||
from . import base_external_dbsource |
from . import base_external_dbsource |
@ -1,3 +1,3 @@ |
|||||
# -*- encoding: utf-8 -*- |
# -*- encoding: utf-8 -*- |
||||
|
|
||||
from . import test_create_dbsource |
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,247 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
from ..exceptions import ConnectionFailedError, ConnectionSuccessError |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref('base_external_dbsource.demo_postgre') |
||||
|
|
||||
|
def _test_adapter_method( |
||||
|
self, method_name, side_effect=None, return_value=None, |
||||
|
create=False, args=None, kwargs=None, |
||||
|
): |
||||
|
if args is None: |
||||
|
args = [] |
||||
|
if kwargs is None: |
||||
|
kwargs = {} |
||||
|
adapter = '%s_postgresql' % method_name |
||||
|
with mock.patch.object(self.dbsource, |
||||
|
adapter, create=create) as adapter: |
||||
|
if side_effect is not None: |
||||
|
adapter.side_effect = side_effect |
||||
|
elif return_value is not None: |
||||
|
adapter.return_value = return_value |
||||
|
res = getattr(self.dbsource, method_name)(*args, **kwargs) |
||||
|
return res, adapter |
||||
|
|
||||
|
def test_conn_string_full(self): |
||||
|
""" It should add password if string interpolation not detected """ |
||||
|
self.dbsource.conn_string = 'User=Derp;' |
||||
|
self.dbsource.password = 'password' |
||||
|
expect = self.dbsource.conn_string + 'PWD=%s;' % self.dbsource.password |
||||
|
self.assertEqual( |
||||
|
self.dbsource.conn_string_full, expect, |
||||
|
) |
||||
|
|
||||
|
# Interface |
||||
|
|
||||
|
def test_connection_success(self): |
||||
|
""" It should raise for successful connection """ |
||||
|
with self.assertRaises(ConnectionSuccessError): |
||||
|
self.dbsource.connection_test() |
||||
|
|
||||
|
def test_connection_fail(self): |
||||
|
""" It should raise for failed/invalid connection """ |
||||
|
with mock.patch.object(self.dbsource, 'connection_open') as conn: |
||||
|
conn.side_effect = Exception |
||||
|
with self.assertRaises(ConnectionFailedError): |
||||
|
self.dbsource.connection_test() |
||||
|
|
||||
|
def test_connection_open_calls_close(self): |
||||
|
""" It should close connection after context ends """ |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, 'connection_close', |
||||
|
) as close: |
||||
|
with self.dbsource.connection_open(): |
||||
|
pass |
||||
|
close.assert_called_once() |
||||
|
|
||||
|
def test_connection_close(self): |
||||
|
""" It should call adapter's close method """ |
||||
|
args = [mock.MagicMock()] |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'connection_close', args=args, |
||||
|
) |
||||
|
adapter.assert_called_once_with(args[0]) |
||||
|
|
||||
|
def test_execute_asserts_query_arg(self): |
||||
|
""" It should raise a TypeError if query and sqlquery not in args """ |
||||
|
with self.assertRaises(TypeError): |
||||
|
self.dbsource.execute() |
||||
|
|
||||
|
def test_execute_calls_adapter(self): |
||||
|
""" It should call the adapter methods with proper args """ |
||||
|
expect = ('query', 'execute', 'metadata') |
||||
|
return_value = 'rows', 'cols' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'execute', args=expect, return_value=return_value, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*expect) |
||||
|
|
||||
|
def test_execute_return(self): |
||||
|
""" It should return rows if not metadata """ |
||||
|
expect = (True, True, False) |
||||
|
return_value = 'rows', 'cols' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'execute', args=expect, return_value=return_value, |
||||
|
) |
||||
|
self.assertEqual(res, return_value[0]) |
||||
|
|
||||
|
def test_execute_return_metadata(self): |
||||
|
""" It should return rows and cols if metadata """ |
||||
|
expect = (True, True, True) |
||||
|
return_value = 'rows', 'cols' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'execute', args=expect, return_value=return_value, |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
res, |
||||
|
{'rows': return_value[0], |
||||
|
'cols': return_value[1]}, |
||||
|
) |
||||
|
|
||||
|
def test_remote_browse(self): |
||||
|
""" It should call the adapter method with proper args """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
self.dbsource.current_table = 'table' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_browse', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*args, **kwargs) |
||||
|
self.assertEqual(res, adapter()) |
||||
|
|
||||
|
def test_remote_browse_asserts_current_table(self): |
||||
|
""" It should raise AssertionError if a table not selected """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
with self.assertRaises(AssertionError): |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_browse', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
|
||||
|
def test_remote_create(self): |
||||
|
""" It should call the adapter method with proper args """ |
||||
|
args = {'val': 'Value'}, 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
self.dbsource.current_table = 'table' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_create', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*args, **kwargs) |
||||
|
self.assertEqual(res, adapter()) |
||||
|
|
||||
|
def test_remote_create_asserts_current_table(self): |
||||
|
""" It should raise AssertionError if a table not selected """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
with self.assertRaises(AssertionError): |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_create', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
|
||||
|
def test_remote_delete(self): |
||||
|
""" It should call the adapter method with proper args """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
self.dbsource.current_table = 'table' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_delete', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*args, **kwargs) |
||||
|
self.assertEqual(res, adapter()) |
||||
|
|
||||
|
def test_remote_delete_asserts_current_table(self): |
||||
|
""" It should raise AssertionError if a table not selected """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
with self.assertRaises(AssertionError): |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_delete', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
|
||||
|
def test_remote_search(self): |
||||
|
""" It should call the adapter method with proper args """ |
||||
|
args = {'search': 'query'}, 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
self.dbsource.current_table = 'table' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_search', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*args, **kwargs) |
||||
|
self.assertEqual(res, adapter()) |
||||
|
|
||||
|
def test_remote_search_asserts_current_table(self): |
||||
|
""" It should raise AssertionError if a table not selected """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
with self.assertRaises(AssertionError): |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_search', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
|
||||
|
def test_remote_update(self): |
||||
|
""" It should call the adapter method with proper args """ |
||||
|
args = [1], {'vals': 'Value'}, 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
self.dbsource.current_table = 'table' |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_update', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
adapter.assert_called_once_with(*args, **kwargs) |
||||
|
self.assertEqual(res, adapter()) |
||||
|
|
||||
|
def test_remote_update_asserts_current_table(self): |
||||
|
""" It should raise AssertionError if a table not selected """ |
||||
|
args = [1], 'args' |
||||
|
kwargs = {'kwargs': True} |
||||
|
with self.assertRaises(AssertionError): |
||||
|
res, adapter = self._test_adapter_method( |
||||
|
'remote_update', create=True, args=args, kwargs=kwargs, |
||||
|
) |
||||
|
|
||||
|
# Postgres |
||||
|
|
||||
|
def test_execute_postgresql(self): |
||||
|
""" It should call generic executor with proper args """ |
||||
|
expect = ('query', 'execute', 'metadata') |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_execute_generic', autospec=True, |
||||
|
) as execute: |
||||
|
execute.return_value = 'rows', 'cols' |
||||
|
self.dbsource.execute(*expect) |
||||
|
execute.assert_called_once_with(*expect) |
||||
|
|
||||
|
# Old API Compat |
||||
|
|
||||
|
def test_execute_calls_adapter_old_api(self): |
||||
|
""" It should call the adapter correctly if old kwargs provided """ |
||||
|
expect = [None, None, 'metadata'] |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, 'execute_postgresql', autospec=True, |
||||
|
) as psql: |
||||
|
psql.return_value = 'rows', 'cols' |
||||
|
self.dbsource.execute( |
||||
|
*expect, sqlparams='params', sqlquery='query' |
||||
|
) |
||||
|
expect[0], expect[1] = 'query', 'params' |
||||
|
psql.assert_called_once_with(*expect) |
||||
|
|
||||
|
def test_conn_open(self): |
||||
|
""" It should return open connection for use """ |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, 'connection_open', autospec=True, |
||||
|
) as connection: |
||||
|
res = self.dbsource.conn_open() |
||||
|
self.assertEqual( |
||||
|
res, |
||||
|
connection().__enter__(), |
||||
|
) |
@ -1,66 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
from odoo.exceptions import Warning as UserError |
|
||||
from odoo.tests import common |
|
||||
import logging |
|
||||
|
|
||||
|
|
||||
class TestCreateDbsource(common.TransactionCase): |
|
||||
"""Test class for base_external_dbsource.""" |
|
||||
|
|
||||
def test_create_dbsource(self): |
|
||||
"""source creation should succeed.""" |
|
||||
dbsource = self.env.ref('base_external_dbsource.demo_postgre') |
|
||||
try: |
|
||||
dbsource.connection_test() |
|
||||
except UserError as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Everything seems properly set up!' in str(e)) |
|
||||
|
|
||||
def test_create_dbsource_failed(self): |
|
||||
"""source creation without connection string should failed.""" |
|
||||
dbsource = self.env.ref('base_external_dbsource.demo_postgre') |
|
||||
|
|
||||
# Connection without connection_string |
|
||||
dbsource.conn_string = "" |
|
||||
try: |
|
||||
dbsource.connection_test() |
|
||||
except UserError as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Here is what we got instead:' in str(e)) |
|
||||
|
|
||||
def test_create_dbsource_without_connector_failed(self): |
|
||||
"""source creation with other connector should failed.""" |
|
||||
dbsource = self.env.ref('base_external_dbsource.demo_postgre') |
|
||||
|
|
||||
# Connection to mysql |
|
||||
try: |
|
||||
dbsource.connector = "mysql" |
|
||||
dbsource.connection_test() |
|
||||
except ValueError as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Wrong value for' in str(e)) |
|
||||
|
|
||||
# Connection to mysql |
|
||||
try: |
|
||||
dbsource.connector = "pyodbc" |
|
||||
dbsource.connection_test() |
|
||||
except ValueError as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Wrong value for' in str(e)) |
|
||||
|
|
||||
# Connection to oracle |
|
||||
try: |
|
||||
dbsource.connector = "cx_Oracle" |
|
||||
dbsource.connection_test() |
|
||||
except ValueError as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Wrong value for' in str(e)) |
|
||||
|
|
||||
# Connection to firebird |
|
||||
try: |
|
||||
dbsource.connector = "fdb" |
|
||||
dbsource.connection_test() |
|
||||
except Exception as e: |
|
||||
logging.warning("Log = " + str(e)) |
|
||||
self.assertTrue(u'Wrong value for' in str(e)) |
|
@ -0,0 +1,77 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
=================================== |
||||
|
External Database Source - Firebird |
||||
|
=================================== |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign Firebird databases. |
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install ``fdb`` python library |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string : Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Setting ``metadata`` to ``True`` in ``execute_fdb`` will do nothing. |
||||
|
* ``execute`` is susceptible to SQL injection. |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,28 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - Firebird', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': { |
||||
|
# 'python': [ |
||||
|
# 'fdb', |
||||
|
# ] |
||||
|
# }, |
||||
|
'demo': [ |
||||
|
'demo/base_external_dbsource.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="base.external.dbsource" id="demo_firebird"> |
||||
|
<field name="name">Firebird Demo</field> |
||||
|
<field name="conn_string">User=firebird;Database=SampleDatabase.fdb;DataSource=localhost;Port=3050;Dialect=3;Charset=NONE;Role=;Connection lifetime=15;Pooling=true;MinPoolSize=0;MaxPoolSize=50;Packet Size=8192;ServerType=0;</field> |
||||
|
<field name="password">password</field> |
||||
|
<field name="connector">fdb</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,56 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from odoo import api |
||||
|
from odoo import models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import fdb |
||||
|
CONNECTORS.append(('fdb', 'Firebird')) |
||||
|
except: |
||||
|
_logger.info('Firebird library not available. Please install "fdb" ' |
||||
|
'python package.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to an Firebird data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
PWD_STRING_FDB = 'Password=%s;' |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_fdb(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_fdb(self): |
||||
|
kwargs = {} |
||||
|
for option in self.conn_string_full.split(';'): |
||||
|
try: |
||||
|
key, value = option.split('=') |
||||
|
except ValueError: |
||||
|
continue |
||||
|
kwargs[key.lower()] = value |
||||
|
return fdb.connect(**kwargs) |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_fdb(self, sqlquery, sqlparams, metadata): |
||||
|
with self.connection_open_fdb() as conn: |
||||
|
cur = conn.cursor() |
||||
|
cur.execute(sqlquery % sqlparams) |
||||
|
rows = cur.fetchall() |
||||
|
return rows, [] |
@ -0,0 +1,3 @@ |
|||||
|
# -*- encoding: utf-8 -*- |
||||
|
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,41 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
ADAPTER = ('odoo.addons.base_external_dbsource_firebird.models' |
||||
|
'.base_external_dbsource.fdb') |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref( |
||||
|
'base_external_dbsource_firebird.demo_firebird', |
||||
|
) |
||||
|
|
||||
|
def test_connection_close_fdb(self): |
||||
|
""" It should close the connection """ |
||||
|
connection = mock.MagicMock() |
||||
|
res = self.dbsource.connection_close_fdb(connection) |
||||
|
self.assertEqual(res, connection.close()) |
||||
|
|
||||
|
@mock.patch(ADAPTER) |
||||
|
def test_connection_open_fdb(self, fdb): |
||||
|
""" It should open the connection with the split conn string """ |
||||
|
self.dbsource.conn_string = 'User=User;' |
||||
|
self.dbsource.connection_open_fdb() |
||||
|
fdb.connect.assert_called_once_with(**{ |
||||
|
'user': 'User', |
||||
|
'password': 'password', |
||||
|
}) |
||||
|
|
||||
|
@mock.patch(ADAPTER) |
||||
|
def test_connection_open_fdb_return(self, fdb): |
||||
|
""" It should return the newly opened connection """ |
||||
|
res = self.dbsource.connection_open_fdb() |
||||
|
self.assertEqual(res, fdb.connect()) |
@ -0,0 +1,80 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
================================ |
||||
|
External Database Source - MSSQL |
||||
|
================================ |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign MSSQL databases using SQLAlchemy. |
||||
|
|
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install & configure FreeTDS driver (tdsodbc package) |
||||
|
* Install ``sqlalchemy`` and ``pymssql`` python libraries |
||||
|
* Install ``base_external_dbsource_sqlite`` Odoo module |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string: Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Add X.509 authentication |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,29 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - MSSQL', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource_sqlite', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': [ |
||||
|
# 'python': [ |
||||
|
# 'sqlalchemy', |
||||
|
# 'pymssql', |
||||
|
# ] |
||||
|
# ], |
||||
|
'demo': [ |
||||
|
'demo/base_external_dbsource.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="base.external.dbsource" id="demo_mssql"> |
||||
|
<field name="name">MSSQL Demo</field> |
||||
|
<field name="conn_string">Server=myServerAddress;Database=myDataBase;User Id=myUsername;</field> |
||||
|
<field name="password">password</field> |
||||
|
<field name="connector">mssql</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,45 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from odoo import api, models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import pymssql |
||||
|
CONNECTORS.append(('mssql', 'Microsoft SQL Server')) |
||||
|
assert pymssql |
||||
|
except (ImportError, AssertionError): |
||||
|
_logger.info('MS SQL Server not available. Please install "pymssql" ' |
||||
|
'python package.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to a MSSQL data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
PWD_STRING_MSSQL = 'Password=%s;' |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_mssql(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_mssql(self): |
||||
|
return self._connection_open_sqlalchemy() |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_mssql(self, sqlquery, sqlparams, metadata): |
||||
|
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata) |
@ -0,0 +1,3 @@ |
|||||
|
# -*- encoding: utf-8 -*- |
||||
|
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
ADAPTER = ('odoo.addons.base_external_dbsource_mssql.models' |
||||
|
'.base_external_dbsource.pymssql') |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref( |
||||
|
'base_external_dbsource_mssql.demo_mssql', |
||||
|
) |
||||
|
|
||||
|
def test_connection_close_mssql(self): |
||||
|
""" It should close the connection """ |
||||
|
connection = mock.MagicMock() |
||||
|
res = self.dbsource.connection_close_mssql(connection) |
||||
|
self.assertEqual(res, connection.close()) |
||||
|
|
||||
|
def test_connection_open_mssql(self): |
||||
|
""" It should call SQLAlchemy open """ |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_connection_open_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.connection_open_mssql() |
||||
|
parent_method.assert_called_once_with() |
||||
|
|
||||
|
def test_excecute_mssql(self): |
||||
|
""" It should pass args to SQLAlchemy execute """ |
||||
|
expect = 'sqlquery', 'sqlparams', 'metadata' |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_execute_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.execute_mssql(*expect) |
||||
|
parent_method.assert_called_once_with(*expect) |
@ -0,0 +1,80 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
================================ |
||||
|
External Database Source - MySQL |
||||
|
================================ |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign MySQL databases using SQLAlchemy. |
||||
|
|
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install ``sqlalchemy`` and ``MySQLdb`` python libraries |
||||
|
* Install ``base_external_dbsource_sqlite`` Odoo module |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string: Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
|
||||
|
* Add X.509 authentication |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,29 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - MySQL', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource_sqlite', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': [ |
||||
|
# 'python': [ |
||||
|
# 'sqlalchemy', |
||||
|
# 'MySQLdb', |
||||
|
# ] |
||||
|
# ], |
||||
|
'demo': [ |
||||
|
'demo/base_external_dbsource.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="base.external.dbsource" id="demo_mysql"> |
||||
|
<field name="name">MySQL Demo</field> |
||||
|
<field name="conn_string">Server=myServerAddress;Database=myDataBase;Uid=myUsername;</field> |
||||
|
<field name="password">password</field> |
||||
|
<field name="connector">mysql</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,43 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from odoo import api, models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import MySQLdb |
||||
|
CONNECTORS.append(('mysql', 'MySQL')) |
||||
|
assert MySQLdb |
||||
|
except (ImportError, AssertionError): |
||||
|
_logger.info('MySQL not available. Please install "mysqldb" ' |
||||
|
'python package.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to a MySQL data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_mysql(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_mysql(self): |
||||
|
return self._connection_open_sqlalchemy() |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_mysql(self, sqlquery, sqlparams, metadata): |
||||
|
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata) |
@ -0,0 +1,3 @@ |
|||||
|
# -*- encoding: utf-8 -*- |
||||
|
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
ADAPTER = ('odoo.addons.base_external_dbsource_mysql.models' |
||||
|
'.base_external_dbsource.MySQLdb') |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref( |
||||
|
'base_external_dbsource_mysql.demo_mysql', |
||||
|
) |
||||
|
|
||||
|
def test_connection_close_mysql(self): |
||||
|
""" It should close the connection """ |
||||
|
connection = mock.MagicMock() |
||||
|
res = self.dbsource.connection_close_mysql(connection) |
||||
|
self.assertEqual(res, connection.close()) |
||||
|
|
||||
|
def test_connection_open_mysql(self): |
||||
|
""" It should call SQLAlchemy open """ |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_connection_open_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.connection_open_mysql() |
||||
|
parent_method.assert_called_once_with() |
||||
|
|
||||
|
def test_excecute_mysql(self): |
||||
|
""" It should pass args to SQLAlchemy execute """ |
||||
|
expect = 'sqlquery', 'sqlparams', 'metadata' |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_execute_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.execute_mysql(*expect) |
||||
|
parent_method.assert_called_once_with(*expect) |
@ -0,0 +1,79 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
=============================== |
||||
|
External Database Source - ODBC |
||||
|
=============================== |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign ODBC databases using PyODBC. |
||||
|
|
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install ``unixodbc`` and ``python-pyodbc`` packages |
||||
|
* Install ``base_external_dbsource_sqlite`` Odoo module |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string: Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Add X.509 authentication |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,29 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - ODBC', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource_sqlite', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': [ |
||||
|
# 'python': [ |
||||
|
# 'sqlalchemy', |
||||
|
# 'pyodbc', |
||||
|
# ] |
||||
|
# ], |
||||
|
'demo': [ |
||||
|
'demo/base_external_dbsource.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="base.external.dbsource" id="demo_odbc"> |
||||
|
<field name="name">ODBC Demo</field> |
||||
|
<field name="conn_string">Driver='ODBC Driver 11 for SQL Server';Server=myServerAddress;Database=myDataBase;Uid=myUsername;</field> |
||||
|
<field name="password">password</field> |
||||
|
<field name="connector">pyodbc</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from odoo import api, models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import pyodbc |
||||
|
CONNECTORS.append(('pyodbc', 'ODBC')) |
||||
|
except ImportError: |
||||
|
_logger.info('ODBC libraries not available. Please install ' |
||||
|
'"unixodbc" and "python-pyodbc" packages.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to a ODBC data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_pyodbc(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_pyodbc(self): |
||||
|
return pyodbc.connect(self.conn_string_full) |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_pyodbc(self, sqlquery, sqlparams, metadata): |
||||
|
return self._execute_generic(sqlquery, sqlparams, metadata) |
@ -0,0 +1,3 @@ |
|||||
|
# -*- encoding: utf-8 -*- |
||||
|
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,46 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
ADAPTER = ('odoo.addons.base_external_dbsource_odbc.models' |
||||
|
'.base_external_dbsource.pyodbc') |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref( |
||||
|
'base_external_dbsource_odbc.demo_odbc', |
||||
|
) |
||||
|
|
||||
|
def test_connection_close_pyodbc(self): |
||||
|
""" It should close the connection """ |
||||
|
connection = mock.MagicMock() |
||||
|
res = self.dbsource.connection_close_pyodbc(connection) |
||||
|
self.assertEqual(res, connection.close()) |
||||
|
|
||||
|
@mock.patch(ADAPTER) |
||||
|
def test_connection_open_pyodbc(self, pyodbc): |
||||
|
""" It should open the connection with the full conn string """ |
||||
|
self.dbsource.connection_open_pyodbc() |
||||
|
pyodbc.connect.assert_called_once_with( |
||||
|
self.dbsource.conn_string_full, |
||||
|
) |
||||
|
|
||||
|
@mock.patch(ADAPTER) |
||||
|
def test_connection_open_pyodbc_return(self, pyodbc): |
||||
|
""" It should return the newly opened connection """ |
||||
|
res = self.dbsource.connection_open_pyodbc() |
||||
|
self.assertEqual(res, pyodbc.connect()) |
||||
|
|
||||
|
def test_execute_pyodbc(self): |
||||
|
""" It should call the generic execute method w/ proper args """ |
||||
|
expect = 'sqlquery', 'sqlparams', 'metadata' |
||||
|
with mock.patch.object(self.dbsource, '_execute_generic') as execute: |
||||
|
self.dbsource.execute_pyodbc(*expect) |
||||
|
execute.assert_called_once_with(*expect) |
@ -0,0 +1,79 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
================================= |
||||
|
External Database Source - Oracle |
||||
|
================================= |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign Oracle databases. |
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install and configure Oracle Instant Client |
||||
|
* Install ``cx_Oracle`` python library |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string: Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Find a way to test (cx_Oracle requires InstantClient, which Travis doesn't have) |
||||
|
* Add X.509 authentication |
||||
|
|
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,25 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - Oracle', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': [ |
||||
|
# 'python': [ |
||||
|
# 'cx_Oracle', |
||||
|
# ] |
||||
|
# ], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,47 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
import os |
||||
|
|
||||
|
from odoo import api |
||||
|
from odoo import models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import cx_Oracle |
||||
|
CONNECTORS.append(('cx_Oracle', 'Oracle')) |
||||
|
except ImportError: |
||||
|
_logger.info('Oracle libraries not available. Please install ' |
||||
|
'"cx_Oracle" python package.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to an Oracle data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
PWD_STRING_CX_ORACLE = 'Password=%s;' |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_cx_Oracle(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_cx_Oracle(self): |
||||
|
os.environ['NLS_LANG'] = 'AMERICAN_AMERICA.UTF8' |
||||
|
return cx_Oracle.connect(self.conn_string_full) |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_cx_Oracle(self, sqlquery, sqlparams, metadata): |
||||
|
return self._execute_generic(sqlquery, sqlparams, metadata) |
@ -0,0 +1,77 @@ |
|||||
|
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg |
||||
|
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html |
||||
|
:alt: License: LGPL-3 |
||||
|
|
||||
|
================================= |
||||
|
External Database Source - SQLite |
||||
|
================================= |
||||
|
|
||||
|
This module extends ``base_external_dbsource``, allowing you to connect to |
||||
|
foreign SQLite databases using SQLAlchemy. |
||||
|
|
||||
|
|
||||
|
Installation |
||||
|
============ |
||||
|
|
||||
|
* Install ``sqlalchemy`` python library |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
Database sources can be configured in Settings > Configuration -> Data sources. |
||||
|
|
||||
|
|
||||
|
Usage |
||||
|
===== |
||||
|
|
||||
|
To use this module: |
||||
|
|
||||
|
* Go to Settings > Database Structure > Database Sources |
||||
|
* Click on Create to enter the following information: |
||||
|
|
||||
|
* Datasource name |
||||
|
* Pasword |
||||
|
* Connector: Choose the database to which you want to connect |
||||
|
* Connection string: Specify how to connect to database |
||||
|
|
||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
||||
|
:alt: Try me on Runbot |
||||
|
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* Add X.509 authentication |
||||
|
|
||||
|
Bug Tracker |
||||
|
=========== |
||||
|
|
||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
||||
|
In case of trouble, please check there if your issue has already been reported. |
||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Daniel Reis <dreis.pt@hotmail.com> |
||||
|
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> |
||||
|
* Gervais Naoussi <gervaisnaoussi@gmail.com> |
||||
|
* Dave Lasley <dave@laslabs.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
from . import models |
@ -0,0 +1,28 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux> |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
||||
|
{ |
||||
|
'name': 'External Database Source - SQLite', |
||||
|
'version': '10.0.1.0.0', |
||||
|
'category': 'Tools', |
||||
|
'author': "Daniel Reis, " |
||||
|
"LasLabs, " |
||||
|
"Odoo Community Association (OCA)", |
||||
|
'website': 'https://github.com/OCA/server-tools', |
||||
|
'license': 'LGPL-3', |
||||
|
'depends': [ |
||||
|
'base_external_dbsource', |
||||
|
], |
||||
|
# Uncomment this for v11 |
||||
|
# 'external_dependencies': [ |
||||
|
# 'python': [ |
||||
|
# 'sqlalchemy', |
||||
|
# ] |
||||
|
# ], |
||||
|
'demo': [ |
||||
|
'demo/base_external_dbsource.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
'auto_install': True, # Remove this key for v11 |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<odoo> |
||||
|
<record model="base.external.dbsource" id="demo_sqlite"> |
||||
|
<field name="name">SQLite Demo</field> |
||||
|
<field name="conn_string">Data Source=:memory:;Version=3;New=True;</field> |
||||
|
<field name="password">password</field> |
||||
|
<field name="connector">sqlite</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,3 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
from . import base_external_dbsource |
@ -0,0 +1,59 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2011 Daniel Reis |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
import logging |
||||
|
|
||||
|
from odoo import api, models |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
try: |
||||
|
from odoo.addons.base_external_dbsource.models import ( |
||||
|
base_external_dbsource, |
||||
|
) |
||||
|
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS |
||||
|
try: |
||||
|
import sqlalchemy |
||||
|
CONNECTORS.append(('sqlite', 'SQLite')) |
||||
|
except ImportError: |
||||
|
_logger.info('SQLAlchemy library not available. Please ' |
||||
|
'install "sqlalchemy" python package.') |
||||
|
except ImportError: |
||||
|
_logger.info('base_external_dbsource Odoo module not found.') |
||||
|
|
||||
|
|
||||
|
class BaseExternalDbsource(models.Model): |
||||
|
""" It provides logic for connection to a SQLite data source. """ |
||||
|
|
||||
|
_inherit = "base.external.dbsource" |
||||
|
|
||||
|
PWD_STRING_SQLITE = 'Password=%s;' |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_close_sqlite(self, connection): |
||||
|
return connection.close() |
||||
|
|
||||
|
@api.multi |
||||
|
def connection_open_sqlite(self): |
||||
|
return self._connection_open_sqlalchemy() |
||||
|
|
||||
|
@api.multi |
||||
|
def execute_sqlite(self, sqlquery, sqlparams, metadata): |
||||
|
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata) |
||||
|
|
||||
|
@api.multi |
||||
|
def _connection_open_sqlalchemy(self): |
||||
|
return sqlalchemy.create_engine(self.conn_string_full).connect() |
||||
|
|
||||
|
@api.multi |
||||
|
def _execute_sqlalchemy(self, sqlquery, sqlparams, metadata): |
||||
|
rows, cols = list(), list() |
||||
|
for record in self: |
||||
|
with record.connection_open() as connection: |
||||
|
cur = connection.execute(sqlquery, sqlparams) |
||||
|
if metadata: |
||||
|
cols = cur.keys() |
||||
|
rows = [r for r in cur] |
||||
|
return rows, cols |
@ -0,0 +1,3 @@ |
|||||
|
# -*- encoding: utf-8 -*- |
||||
|
|
||||
|
from . import test_base_external_dbsource |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 LasLabs Inc. |
||||
|
|
||||
|
import mock |
||||
|
|
||||
|
from odoo.tests import common |
||||
|
|
||||
|
|
||||
|
ADAPTER = ('odoo.addons.base_external_dbsource_sqlite.models' |
||||
|
'.base_external_dbsource.sqlalchemy') |
||||
|
|
||||
|
|
||||
|
class TestBaseExternalDbsource(common.TransactionCase): |
||||
|
|
||||
|
def setUp(self): |
||||
|
super(TestBaseExternalDbsource, self).setUp() |
||||
|
self.dbsource = self.env.ref( |
||||
|
'base_external_dbsource_sqlite.demo_sqlite', |
||||
|
) |
||||
|
|
||||
|
def test_connection_close_sqlite(self): |
||||
|
""" It should close the connection """ |
||||
|
connection = mock.MagicMock() |
||||
|
res = self.dbsource.connection_close_sqlite(connection) |
||||
|
self.assertEqual(res, connection.close()) |
||||
|
|
||||
|
def test_connection_open_sqlite(self): |
||||
|
""" It should call SQLAlchemy open """ |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_connection_open_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.connection_open_sqlite() |
||||
|
parent_method.assert_called_once_with() |
||||
|
|
||||
|
def test_excecute_sqlite(self): |
||||
|
""" It should pass args to SQLAlchemy execute """ |
||||
|
expect = 'sqlquery', 'sqlparams', 'metadata' |
||||
|
with mock.patch.object( |
||||
|
self.dbsource, '_execute_sqlalchemy' |
||||
|
) as parent_method: |
||||
|
self.dbsource.execute_sqlite(*expect) |
||||
|
parent_method.assert_called_once_with(*expect) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue