Files
tryton/trytond/modules/__init__.py
T

427 lines
15 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2012-04-01 23:08:15 +02:00
import os
import sys
2018-09-01 17:06:28 +02:00
import importlib
2008-09-13 19:41:02 +02:00
import itertools
import logging
2018-06-26 19:11:19 +02:00
import configparser
from glob import iglob
from collections import defaultdict
2018-09-01 17:06:28 +02:00
from importlib.machinery import FileFinder, SourceFileLoader, SOURCE_SUFFIXES
2013-09-06 22:32:37 +02:00
from sql import Table
2015-07-01 19:41:49 +02:00
from sql.functions import CurrentTimestamp
2010-08-19 22:01:12 +02:00
import trytond.tools as tools
from trytond.config import config
from trytond.exceptions import MissingDependenciesException
2010-08-19 22:01:12 +02:00
from trytond.transaction import Transaction
2015-05-28 21:02:01 +02:00
from trytond import backend
import trytond.convert as convert
2008-09-13 19:41:02 +02:00
2015-03-02 23:39:15 +01:00
logger = logging.getLogger(__name__)
2015-05-28 21:02:01 +02:00
ir_module = Table('ir_module')
2013-09-06 22:32:37 +02:00
ir_model_data = Table('ir_model_data')
2008-09-13 19:41:02 +02:00
OPJ = os.path.join
MODULES_PATH = os.path.abspath(os.path.dirname(__file__))
2008-09-13 19:41:02 +02:00
MODULES = []
2008-11-18 17:40:30 +01:00
EGG_MODULES = {}
2009-08-07 10:42:23 +02:00
2012-04-01 23:08:15 +02:00
2009-08-07 10:42:23 +02:00
def update_egg_modules():
global EGG_MODULES
try:
import pkg_resources
for ep in pkg_resources.iter_entry_points('trytond.modules'):
EGG_MODULES[ep.name] = ep
2009-08-07 10:42:23 +02:00
except ImportError:
pass
2018-06-11 23:22:35 +02:00
2009-08-07 10:42:23 +02:00
update_egg_modules()
2008-11-18 17:40:30 +01:00
2008-09-13 19:41:02 +02:00
2018-09-01 17:06:28 +02:00
def import_module(name, fullname=None):
if fullname is None:
fullname = 'trytond.modules.' + name
try:
module = importlib.import_module(fullname)
except ImportError:
if name not in EGG_MODULES:
raise
ep = EGG_MODULES[name]
# Can not use ep.load because modules are declared in an importable
# path and it can not import submodule.
path = os.path.join(
ep.dist.location, *ep.module_name.split('.')[:-1])
if not os.path.isdir(path):
# Find module in path
for path in sys.path:
path = os.path.join(
path, *ep.module_name.split('.')[:-1])
if os.path.isdir(os.path.join(path, name)):
break
else:
# When testing modules from setuptools location is the
# module directory
path = os.path.dirname(ep.dist.location)
spec = FileFinder(
path, (SourceFileLoader, SOURCE_SUFFIXES)
).find_spec(fullname)
if spec.loader:
module = spec.loader.load_module()
else:
raise
return module
def get_module_info(name):
"Return the content of the tryton.cfg"
2018-06-26 19:11:19 +02:00
module_config = configparser.ConfigParser()
with tools.file_open(os.path.join(name, 'tryton.cfg')) as fp:
2018-06-26 19:11:19 +02:00
module_config.read_file(fp)
directory = os.path.dirname(fp.name)
info = dict(module_config.items('tryton'))
info['directory'] = directory
for key in ('depends', 'extras_depend', 'xml'):
if key in info:
info[key] = info[key].strip().splitlines()
return info
2018-06-11 23:22:35 +02:00
class Graph(dict):
def get(self, name):
if name in self:
node = self[name]
else:
node = self[name] = Node(name)
return node
2008-09-13 19:41:02 +02:00
2018-06-11 23:22:35 +02:00
def add(self, name, deps):
node = self.get(name)
for dep in deps:
self.get(dep).append(node)
return node
def __iter__(self):
2018-06-26 19:11:19 +02:00
for node in sorted(self.values(), key=lambda n: (n.depth, n.name)):
2018-06-11 23:22:35 +02:00
yield node
class Node(list):
def __init__(self, name):
super(Node, self).__init__()
self.name = name
self.info = None
self.__depth = 0
def __repr__(self):
return str((self.name, self.depth, tuple(self)))
@property
def depth(self):
return self.__depth
@depth.setter
def depth(self, value):
if value > self.__depth:
self.__depth = value
for child in self:
child.depth = value + 1
def append(self, node):
assert isinstance(node, Node)
node.depth = self.depth + 1
super(Node, self).append(node)
def create_graph(module_list):
module_list = set(module_list)
all_deps = set()
graph = Graph()
2008-09-13 19:41:02 +02:00
for module in module_list:
info = get_module_info(module)
2018-06-11 23:22:35 +02:00
deps = info.get('depends', []) + [
d for d in info.get('extras_depend', []) if d in module_list]
node = graph.add(module, deps)
assert node.info is None
node.info = info
all_deps.update(deps)
2008-09-13 19:41:02 +02:00
2018-06-11 23:22:35 +02:00
missing = all_deps - module_list
if missing:
raise MissingDependenciesException(list(missing))
return graph
2008-09-13 19:41:02 +02:00
def is_module_to_install(module, update):
if module in update:
return True
return False
2012-04-01 23:08:15 +02:00
def load_module_graph(graph, pool, update=None, lang=None):
2017-11-25 22:00:24 +01:00
from trytond.ir.lang import get_parent_language
2008-09-13 19:41:02 +02:00
if lang is None:
lang = [config.get('database', 'language')]
if update is None:
update = []
2008-09-13 19:41:02 +02:00
modules_todo = []
models_to_update_history = set()
2016-09-10 23:25:03 +02:00
# Load also parent languages
lang = set(lang)
for code in list(lang):
while code:
lang.add(code)
2017-11-25 22:00:24 +01:00
code = get_parent_language(code)
2016-09-10 23:25:03 +02:00
transaction = Transaction()
with transaction.connection.cursor() as cursor:
modules = [x.name for x in graph]
2018-10-24 17:35:09 +02:00
module2state = dict()
for sub_modules in tools.grouped_slice(modules):
cursor.execute(*ir_module.select(ir_module.name, ir_module.state,
where=ir_module.name.in_(list(sub_modules))))
module2state.update(cursor.fetchall())
2018-03-20 17:27:12 +01:00
modules = set(modules)
2018-06-11 23:22:35 +02:00
for node in graph:
module = node.name
if module not in MODULES:
2015-01-16 10:21:52 +01:00
continue
logger.info(module)
2018-03-20 17:27:12 +01:00
classes = pool.fill(module, modules)
2016-03-17 22:56:58 +01:00
if update:
pool.setup(classes)
2016-10-21 12:44:57 +02:00
package_state = module2state.get(module, 'not activated')
if (is_module_to_install(module, update)
2016-03-17 22:56:58 +01:00
or (update
and package_state in ('to activate', 'to upgrade'))):
if package_state not in ('to activate', 'to upgrade'):
2016-10-21 12:44:57 +02:00
if package_state == 'activated':
package_state = 'to upgrade'
elif package_state != 'to remove':
2016-10-21 12:44:57 +02:00
package_state = 'to activate'
2018-06-11 23:22:35 +02:00
for child in node:
module2state[child.name] = package_state
2018-06-26 19:11:19 +02:00
for type in list(classes.keys()):
for cls in classes[type]:
logger.info('%s:register %s', module, cls.__name__)
cls.__register__(module)
for model in classes['model']:
if hasattr(model, '_history'):
models_to_update_history.add(model.__name__)
2008-09-13 19:41:02 +02:00
2018-06-11 23:22:35 +02:00
# Instanciate a new parser for the module
2018-03-20 17:27:12 +01:00
tryton_parser = convert.TrytondXmlHandler(
pool, module, package_state, modules)
2008-09-13 19:41:02 +02:00
2018-06-11 23:22:35 +02:00
for filename in node.info.get('xml', []):
filename = filename.replace('/', os.sep)
logger.info('%s:loading %s', module, filename)
# Feed the parser with xml content:
with tools.file_open(OPJ(module, filename), 'rb') as fp:
tryton_parser.parse_xmlstream(fp)
modules_todo.append((module, list(tryton_parser.to_delete)))
2008-09-13 19:41:02 +02:00
2018-06-11 23:22:35 +02:00
localedir = '%s/%s' % (node.info['directory'], 'locale')
lang2filenames = defaultdict(list)
for filename in itertools.chain(
iglob('%s/*.po' % localedir),
iglob('%s/override/*.po' % localedir)):
filename = filename.replace('/', os.sep)
lang2 = os.path.splitext(os.path.basename(filename))[0]
if lang2 not in lang:
continue
lang2filenames[lang2].append(filename)
2018-06-11 23:22:35 +02:00
base_path_position = len(node.info['directory']) + 1
2018-06-26 19:11:19 +02:00
for language, files in lang2filenames.items():
filenames = [f[base_path_position:] for f in files]
logger.info('%s:loading %s', module, ','.join(filenames))
Translation = pool.get('ir.translation')
Translation.translation_import(language, module, files)
if package_state == 'to remove':
continue
cursor.execute(*ir_module.select(ir_module.id,
2018-06-11 23:22:35 +02:00
where=(ir_module.name == module)))
try:
module_id, = cursor.fetchone()
cursor.execute(*ir_module.update([ir_module.state],
2016-10-21 12:44:57 +02:00
['activated'], where=(ir_module.id == module_id)))
except TypeError:
cursor.execute(*ir_module.insert(
[ir_module.create_uid, ir_module.create_date,
ir_module.name, ir_module.state],
2018-06-11 23:22:35 +02:00
[[0, CurrentTimestamp(), module, 'activated'],
]))
2018-06-11 23:22:35 +02:00
module2state[module] = 'activated'
transaction.commit()
2016-03-17 22:56:58 +01:00
if not update:
pool.setup()
else:
# Remove unknown models and fields
Model = pool.get('ir.model')
Model.clean()
ModelField = pool.get('ir.model.field')
ModelField.clean()
transaction.commit()
2016-03-17 22:56:58 +01:00
2018-01-12 15:46:52 +01:00
pool.setup_mixin(modules)
for model_name in models_to_update_history:
model = pool.get(model_name)
if model._history:
logger.info('history:update %s', model.__name__)
model._update_history_table()
# Vacuum :
while modules_todo:
(module, to_delete) = modules_todo.pop()
convert.post_import(pool, module, to_delete)
2016-03-17 22:56:58 +01:00
logger.info('all modules loaded')
2008-09-13 19:41:02 +02:00
2012-04-01 23:08:15 +02:00
2008-09-13 19:41:02 +02:00
def get_module_list():
2008-12-01 20:25:09 +01:00
module_list = set()
2008-09-13 19:41:02 +02:00
if os.path.exists(MODULES_PATH) and os.path.isdir(MODULES_PATH):
for file in os.listdir(MODULES_PATH):
if file.startswith('.'):
continue
2015-03-15 15:52:03 +01:00
if file == '__pycache__':
continue
2008-09-13 19:41:02 +02:00
if os.path.isdir(OPJ(MODULES_PATH, file)):
2008-12-01 20:25:09 +01:00
module_list.add(file)
2009-08-07 10:42:23 +02:00
update_egg_modules()
module_list.update(EGG_MODULES.keys())
2008-12-01 20:25:09 +01:00
module_list.add('ir')
module_list.add('res')
2013-09-16 11:38:11 +02:00
module_list.add('tests')
2008-12-01 20:25:09 +01:00
return list(module_list)
2008-09-13 19:41:02 +02:00
2012-04-01 23:08:15 +02:00
def register_classes():
2009-08-07 10:42:23 +02:00
'''
Import modules to register the classes in the Pool
'''
import trytond.ir
2012-09-24 22:16:53 +02:00
trytond.ir.register()
import trytond.res
2012-09-24 22:16:53 +02:00
trytond.res.register()
2013-09-16 11:38:11 +02:00
import trytond.tests
trytond.tests.register()
2009-02-11 14:52:22 +01:00
2018-06-11 23:22:35 +02:00
for node in create_graph(get_module_list()):
module = node.name
2015-03-05 11:32:18 +01:00
logger.info('%s:registering classes', module)
2008-09-13 19:41:02 +02:00
2016-01-16 19:20:30 +01:00
if module in ('ir', 'res', 'tests'):
2008-09-13 19:41:02 +02:00
MODULES.append(module)
continue
2018-09-01 17:06:28 +02:00
the_module = import_module(module)
2012-10-24 23:38:12 +02:00
# Some modules register nothing in the Pool
if hasattr(the_module, 'register'):
the_module.register()
2008-09-13 19:41:02 +02:00
MODULES.append(module)
2012-04-01 23:08:15 +02:00
def load_modules(
database_name, pool, update=None, lang=None, activatedeps=False):
2008-09-30 11:55:07 +02:00
res = True
if update:
update = update[:]
else:
update = []
2014-01-31 00:02:03 +01:00
def _load_modules(update):
2014-07-20 12:39:15 +02:00
global res
2015-05-28 21:02:01 +02:00
TableHandler = backend.get('TableHandler')
transaction = Transaction()
2015-05-28 21:02:01 +02:00
with transaction.connection.cursor() as cursor:
# Migration from 3.6: remove double module
old_table = 'ir_module_module'
new_table = 'ir_module'
if TableHandler.table_exist(old_table):
TableHandler.table_rename(old_table, new_table)
2016-10-21 12:44:57 +02:00
# Migration from 4.0: rename installed to activated
cursor.execute(*ir_module.select(ir_module.name,
where=ir_module.state.in_(('installed', 'uninstalled'))))
if cursor.fetchone():
cursor.execute(*ir_module.update(
[ir_module.state], ['activated'],
where=ir_module.state == 'installed'))
cursor.execute(*ir_module.update(
[ir_module.state], ['not activated'],
where=ir_module.state == 'uninstalled'))
if update:
cursor.execute(*ir_module.select(ir_module.name,
2016-10-21 12:44:57 +02:00
where=ir_module.state.in_(('activated', 'to activate',
'to upgrade', 'to remove'))))
else:
cursor.execute(*ir_module.select(ir_module.name,
2016-10-21 12:44:57 +02:00
where=ir_module.state.in_(('activated', 'to upgrade',
'to remove'))))
module_list = [name for (name,) in cursor.fetchall()]
graph = None
while graph is None:
module_list += update
try:
2018-06-11 23:22:35 +02:00
graph = create_graph(module_list)
except MissingDependenciesException as e:
if not activatedeps:
raise
update += e.missings
2008-09-13 19:41:02 +02:00
load_module_graph(graph, pool, update, lang)
2008-09-13 19:41:02 +02:00
if update:
cursor.execute(*ir_module.select(ir_module.name,
2013-09-06 22:32:37 +02:00
where=(ir_module.state == 'to remove')))
fetchall = cursor.fetchall()
if fetchall:
for (mod_name,) in fetchall:
# TODO check if ressource not updated by the user
cursor.execute(*ir_model_data.select(
ir_model_data.model, ir_model_data.db_id,
where=(ir_model_data.module == mod_name),
order_by=ir_model_data.id.desc))
for rmod, rid in cursor.fetchall():
Model = pool.get(rmod)
Model.delete([Model(rid)])
Transaction().connection.commit()
cursor.execute(*ir_module.update([ir_module.state],
2016-10-21 12:44:57 +02:00
['not activated'],
where=(ir_module.state == 'to remove')))
Transaction().connection.commit()
res = False
2008-09-13 19:41:02 +02:00
Module = pool.get('ir.module')
Module.update_list()
# Need to commit to unlock SQLite database
transaction.commit()
2014-01-31 00:02:03 +01:00
if not Transaction().connection:
2014-01-31 00:02:03 +01:00
with Transaction().start(database_name, 0):
_load_modules(update)
2014-01-31 00:02:03 +01:00
else:
with Transaction().new_transaction(), \
2014-01-31 00:02:03 +01:00
Transaction().set_user(0), \
Transaction().reset_context():
_load_modules(update)
2014-01-31 00:02:03 +01:00
2008-09-30 11:55:07 +02:00
return res