Add hostname configuration to list database

issue7375
review44991002
This commit is contained in:
Cédric Krier
2018-05-22 23:42:35 +02:00
parent 487ffc07e4
commit 0aed819d47
10 changed files with 70 additions and 42 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add hostname configuration to list database
* Add tree mixin
* Rename "install-dependences" into "activate-dependencies"
* Use recursive common table expression for child_of/parent_of operators
+5
View File
@@ -73,6 +73,7 @@ def run(options):
with Transaction().start(db_name, 0) as transaction:
pool = Pool()
User = pool.get('res.user')
Configuration = pool.get('ir.configuration')
with transaction.set_context(active_test=False):
admin, = User.search([('login', '=', 'admin')])
@@ -112,3 +113,7 @@ def run(options):
admin.save()
if options.reset_password:
User.reset_password([admin])
if options.hostname is not None:
configuration = Configuration(1)
configuration.hostname = options.hostname or None
configuration.save()
+2 -2
View File
@@ -69,7 +69,7 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
def list(self):
def list(self, hostname=None):
'''
Get the list of database
@@ -83,7 +83,7 @@ class DatabaseInterface(object):
'''
raise NotImplementedError
def test(self):
def test(self, hostname=None):
'''
Test if it is a Tryton database.
'''
+33 -21
View File
@@ -71,8 +71,8 @@ class Database(DatabaseInterface):
_lock = RLock()
_databases = {}
_connpool = None
_list_cache = None
_list_cache_timestamp = None
_list_cache = {}
_list_cache_timestamp = {}
_version_cache = {}
_search_path = None
_current_user = None
@@ -153,12 +153,12 @@ class Database(DatabaseInterface):
cursor.execute('CREATE DATABASE "' + database_name + '" '
'TEMPLATE "' + template + '" ENCODING \'unicode\'')
connection.commit()
cls._list_cache = None
cls._list_cache.clear()
def drop(self, connection, database_name):
cursor = connection.cursor()
cursor.execute('DROP DATABASE "' + database_name + '"')
self.__class__._list_cache = None
self.__class__._list_cache.clear()
def get_version(self, connection):
if self.name not in self._version_cache:
@@ -170,11 +170,12 @@ class Database(DatabaseInterface):
self._version_cache[self.name] = (major, minor, patch)
return self._version_cache[self.name]
def list(self):
def list(self, hostname=None):
now = time.time()
timeout = config.getint('session', 'timeout')
res = self.__class__._list_cache
if res and abs(self.__class__._list_cache_timestamp - now) < timeout:
res = self.__class__._list_cache.get(hostname)
timestamp = self.__class__._list_cache_timestamp.get(hostname, now)
if res and abs(timestamp - now) < timeout:
return res
connection = self.get_connection()
@@ -186,15 +187,15 @@ class Database(DatabaseInterface):
for db_name, in cursor:
try:
with connect(self.dsn(db_name)) as conn:
if self._test(conn):
if self._test(conn, hostname=hostname):
res.append(db_name)
except Exception:
continue
finally:
self.put_connection(connection)
self.__class__._list_cache = res
self.__class__._list_cache_timestamp = now
self.__class__._list_cache[hostname] = res
self.__class__._list_cache_timestamp[hostname] = now
return res
def init(self):
@@ -230,22 +231,33 @@ class Database(DatabaseInterface):
finally:
self.put_connection(connection)
def test(self):
def test(self, hostname=None):
connection = self.get_connection()
is_tryton_database = self._test(connection)
self.put_connection(connection)
try:
is_tryton_database = self._test(connection, hostname=hostname)
except Exception:
is_tryton_database = False
finally:
self.put_connection(connection)
return is_tryton_database
@classmethod
def _test(cls, connection):
def _test(cls, connection, hostname=None):
cursor = connection.cursor()
cursor.execute('SELECT 1 FROM information_schema.tables '
'WHERE table_name IN %s',
(('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu',
'res_user', 'res_group', 'ir_module',
'ir_module_dependency', 'ir_translation',
'ir_lang'),))
return len(cursor.fetchall()) != 0
tables = ('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu',
'res_user', 'res_group', 'ir_module', 'ir_module_dependency',
'ir_translation', 'ir_lang', 'ir_configuration')
cursor.execute('SELECT table_name FROM information_schema.tables '
'WHERE table_name IN %s', (tables,))
if len(cursor.fetchall()) != len(tables):
return False
if hostname:
cursor.execute(
'SELECT hostname FROM ir_configuration')
hostnames = {h for h, in cursor.fetchall() if h}
if hostnames and hostname not in hostnames:
return False
return True
def nextid(self, connection, table):
cursor = connection.cursor()
+1
View File
@@ -3,6 +3,7 @@ CREATE SEQUENCE ir_configuration_id_seq;
CREATE TABLE ir_configuration (
id INTEGER DEFAULT NEXTVAL('ir_configuration_id_seq') NOT NULL,
language VARCHAR,
hostname VARCHAR,
PRIMARY KEY(id)
);
+20 -16
View File
@@ -327,7 +327,7 @@ class Database(DatabaseInterface):
os.remove(os.path.join(config.get('database', 'path'),
database_name + '.sqlite'))
def list(self):
def list(self, hostname=None):
res = []
listdir = [':memory:']
try:
@@ -344,7 +344,7 @@ class Database(DatabaseInterface):
database = Database(db_name).connect()
except Exception:
continue
if database.test():
if database.test(hostname=hostname):
res.append(db_name)
database.close()
return res
@@ -384,29 +384,33 @@ class Database(DatabaseInterface):
cursor.execute(*insert)
conn.commit()
def test(self):
def test(self, hostname=None):
tables = ['ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu',
'res_user', 'res_group', 'ir_module', 'ir_module_dependency',
'ir_translation', 'ir_lang', 'ir_configuration']
sqlite_master = Table('sqlite_master')
select = sqlite_master.select(sqlite_master.name)
select.where = sqlite_master.type == 'table'
select.where &= sqlite_master.name.in_([
'ir_model',
'ir_model_field',
'ir_ui_view',
'ir_ui_menu',
'res_user',
'res_group',
'ir_module',
'ir_module_dependency',
'ir_translation',
'ir_lang',
])
select.where &= sqlite_master.name.in_(tables)
with self._conn as conn:
cursor = conn.cursor()
try:
cursor.execute(*select)
except Exception:
return False
return len(cursor.fetchall()) != 0
if len(cursor.fetchall()) != len(tables):
return False
if hostname:
configuration = Table('ir_configuration')
try:
cursor.execute(*configuration.select(
configuration.hostname))
except Exception:
return False
hostnames = {h for h, in cursor.fetchall() if h}
if hostnames and hostname not in hostnames:
return False
return True
def lastid(self, cursor):
# This call is not thread safe
+2 -1
View File
@@ -1,6 +1,7 @@
CREATE TABLE ir_configuration (
id INTEGER PRIMARY KEY AUTOINCREMENT,
language VARCHAR
language VARCHAR,
hostname VARCHAR
);
CREATE TABLE ir_model (
+2
View File
@@ -59,6 +59,8 @@ def get_parser_admin():
dest="update_modules_list", help="Update list of tryton modules")
parser.add_argument("-l", "--language", dest="languages", nargs='+',
default=[], metavar='CODE', help="Load language translations")
parser.add_argument("--hostname", dest="hostname", default=None,
help="Limit database listing to the hostname")
parser.epilog = ('The first time a database is initialized '
'or when the password is set, the admin password is read '
+1
View File
@@ -11,6 +11,7 @@ class Configuration(ModelSingleton, ModelSQL):
'Configuration'
__name__ = 'ir.configuration'
language = fields.Char('language')
hostname = fields.Char("Hostname")
_get_language_cache = Cache('ir_configuration.get_language')
@staticmethod
+3 -2
View File
@@ -10,7 +10,7 @@ from sql import Table
from trytond import security
from trytond import backend
from trytond.config import config
from trytond.config import config, get_hostname
from trytond import __version__
from trytond.transaction import Transaction
from trytond.exceptions import (
@@ -100,10 +100,11 @@ def db_list(request, *args):
if not config.getboolean('database', 'list'):
raise Exception('AccessDenied')
context = {'_request': request.context}
hostname = get_hostname(request.host)
with Transaction().start(
None, 0, context=context, close=True, _nocache=True
) as transaction:
return transaction.database.list()
return transaction.database.list(hostname=hostname)
@app.auth_required