1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
31 from cStringIO import StringIO
32 from os.path import join as opj
37 import openerp.tools as tools
38 import openerp.release as release
39 from openerp.tools.safe_eval import safe_eval as eval
41 _logger = logging.getLogger(__name__)
42 _test_logger = logging.getLogger('openerp.tests')
44 # addons path ','.joined
45 _ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
47 # addons path as a list
50 # Modules already loaded
53 class AddonsImportHook(object):
55 Import hook to load OpenERP addons from multiple paths.
57 OpenERP implements its own import-hook to load its addons. OpenERP
58 addons are Python modules. Originally, they were each living in their
59 own top-level namespace, e.g. the sale module, or the hr module. For
60 backward compatibility, `import <module>` is still supported. Now they
61 are living in `openerp.addons`. The good way to import such modules is
62 thus `import openerp.addons.module`.
65 def find_module(self, module_name, package_path):
66 module_parts = module_name.split('.')
67 if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):
68 return self # We act as a loader too.
70 def load_module(self, module_name):
71 if module_name in sys.modules:
72 return sys.modules[module_name]
74 _1, _2, module_part = module_name.split('.')
75 # Note: we don't support circular import.
76 f, path, descr = imp.find_module(module_part, ad_paths)
77 mod = imp.load_module('openerp.addons.' + module_part, f, path, descr)
78 sys.modules['openerp.addons.' + module_part] = mod
81 def initialize_sys_path():
83 Setup an import-hook to be able to import OpenERP addons from the different
86 This ensures something like ``import crm`` (or even
87 ``import openerp.addons.crm``) works even if the addons are not in the
94 ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
95 ad_paths.append(os.path.abspath(_ad)) # for get_module_path
96 sys.meta_path.append(AddonsImportHook())
98 def get_module_path(module, downloaded=False, display_warning=True):
99 """Return the path of the given module.
101 Search the addons paths and return the first path where the given
102 module is found. If downloaded is True, return the default addons
103 path if nothing else is found.
106 initialize_sys_path()
108 if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
109 return opj(adp, module)
112 return opj(_ad, module)
114 _logger.warning('module %s: module not found', module)
117 def get_module_filetree(module, dir='.'):
118 path = get_module_path(module)
122 dir = os.path.normpath(dir)
125 if dir.startswith('..') or (dir and dir[0] == '/'):
126 raise Exception('Cannot access file outside the module')
128 files = openerp.tools.osutil.listdir(path, True)
132 if not f.startswith(dir):
136 f = f[len(dir)+int(not dir.endswith('/')):]
137 lst = f.split(os.sep)
140 current = current.setdefault(lst.pop(0), {})
141 current[lst.pop(0)] = None
145 def get_module_resource(module, *args):
146 """Return the full path of a resource of the given module.
148 :param module: module name
149 :param list(str) args: resource path components within module
152 :return: absolute path to the resource
154 TODO name it get_resource_path
155 TODO make it available inside on osv object (self.get_resource_path)
157 mod_path = get_module_path(module)
158 if not mod_path: return False
159 resource_path = opj(mod_path, *args)
160 if os.path.isdir(mod_path):
161 # the module is a directory - ignore zip behavior
162 if os.path.exists(resource_path):
166 def get_module_icon(module):
167 iconpath = ['static', 'description', 'icon.png']
168 if get_module_resource(module, *iconpath):
169 return ('/' + module + '/') + '/'.join(iconpath)
170 return '/base/' + '/'.join(iconpath)
172 def load_information_from_description_file(module):
174 :param module: The name of the module (sale, purchase, ...)
177 terp_file = get_module_resource(module, '__openerp__.py')
178 mod_path = get_module_path(module)
181 if os.path.isfile(terp_file):
182 # default values for descriptor
184 'application': False,
186 'auto_install': False,
187 'category': 'Uncategorized',
190 'icon': get_module_icon(module),
201 info.update(itertools.izip(
202 'depends data demo test init_xml update_xml demo_xml'.split(),
205 f = tools.file_open(terp_file)
207 info.update(eval(f.read()))
212 # 'active' has been renamed 'auto_install'
213 info['auto_install'] = info['active']
215 info['version'] = adapt_version(info['version'])
218 #TODO: refactor the logger in this file to follow the logging guidelines
220 _logger.debug('module %s: no __openerp__.py file found.', module)
223 def init_module_models(cr, module_name, obj_list):
224 """ Initialize a list of models.
226 Call _auto_init and init on each model to create or update the
227 database tables supporting the models.
229 TODO better explanation of _auto_init and init.
232 _logger.info('module %s: creating or updating database tables', module_name)
235 result = obj._auto_init(cr, {'module': module_name})
238 if hasattr(obj, 'init'):
242 obj._auto_end(cr, {'module': module_name})
249 def load_openerp_module(module_name):
250 """ Load an OpenERP module, if not already loaded.
252 This loads the module and register all of its models, thanks to either
253 the MetaModel metaclass, or the explicit instantiation of the model.
254 This is also used to load server-wide module (i.e. it is also used
255 when there is no model to register).
258 if module_name in loaded:
261 initialize_sys_path()
263 mod_path = get_module_path(module_name)
264 __import__('openerp.addons.' + module_name)
266 # Call the module's post-load hook. This can done before any model or
267 # data has been initialized. This is ok as the post-load hook is for
268 # server-wide (instead of registry-specific) functionalities.
269 info = load_information_from_description_file(module_name)
270 if info['post_load']:
271 getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])()
274 msg = "Couldn't load module %s" % (module_name)
275 _logger.critical(msg)
279 loaded.append(module_name)
282 """Returns the list of module names
286 name = os.path.basename(name)
287 if name[-4:] == '.zip':
291 def is_really_module(name):
292 manifest_name = opj(dir, name, '__openerp__.py')
293 zipfile_name = opj(dir, name)
294 return os.path.isfile(manifest_name)
295 return map(clean, filter(is_really_module, os.listdir(dir)))
298 initialize_sys_path()
300 plist.extend(listdir(ad))
301 return list(set(plist))
303 def get_modules_with_version():
304 modules = get_modules()
305 res = dict.fromkeys(modules, adapt_version('1.0'))
306 for module in modules:
308 info = load_information_from_description_file(module)
309 res[module] = info['version']
314 def adapt_version(version):
315 serie = release.major_version
316 if version == serie or not version.startswith(serie + '.'):
317 version = '%s.%s' % (serie, version)
320 def get_test_modules(module):
321 """ Return a list of module for the addons potentialy containing tests to
322 feed unittest2.TestLoader.loadTestsFromModule() """
323 # Try to import the module
324 module = 'openerp.addons.' + module + '.tests'
328 # If module has no `tests` sub-module, no problem.
329 if str(e) != 'No module named tests':
330 _logger.exception('Can not `import %s`.', module)
333 # include submodules too
334 result = [mod_obj for name, mod_obj in sys.modules.iteritems()
335 if mod_obj # mod_obj can be None
336 if name.startswith(module)]
339 # Use a custom stream object to log the test executions.
340 class TestStream(object):
342 self.r = re.compile(r'^-*$|^ *... *$|^ok$')
349 for c in s.split('\n'):
357 def run_unit_tests(module_name, dbname):
359 Return True or False if some tests were found and succeeded or failed.
360 Return None if no test was found.
363 current_test = module_name
364 mods = get_test_modules(module_name)
367 suite = unittest2.TestSuite()
368 suite.addTests(unittest2.TestLoader().loadTestsFromModule(m))
369 _logger.info('module %s: running test %s.', module_name, m.__name__)
371 result = unittest2.TextTestRunner(verbosity=2, stream=TestStream()).run(suite)
372 if not result.wasSuccessful():
374 _logger.error('module %s: at least one error occurred in a test', module_name)
378 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: