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-2011 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 ##############################################################################
23 """ Modules (also called addons) management.
28 from os.path import join as opj
34 import openerp.osv as osv
35 import openerp.tools as tools
36 import openerp.tools.osutil as osutil
37 from openerp.tools.safe_eval import safe_eval as eval
38 import openerp.pooler as pooler
39 from openerp.tools.translate import _
41 import openerp.netsvc as netsvc
44 import openerp.release as release
48 from zipfile import PyZipFile, ZIP_DEFLATED
49 from cStringIO import StringIO
53 logger = netsvc.Logger()
55 _ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base)
58 # Modules already loaded
61 def initialize_sys_path():
67 ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
69 sys.path.insert(1, _ad)
74 sys.path.insert(ad_cnt, adp)
77 ad_paths.append(_ad) # for get_module_path
79 def open_openerp_namespace():
80 # See comment for open_openerp_namespace.
81 if openerp.conf.deprecation.open_openerp_namespace:
82 for k, v in list(sys.modules.items()):
83 if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
84 sys.modules[k[8:]] = v
87 """ Modules dependency graph.
89 The graph is a mapping from module name to Nodes.
93 def addNode(self, name, deps):
94 max_depth, father = 0, None
95 for n in [Node(x, self) for x in deps]:
96 if n.depth >= max_depth:
100 father.addChild(name)
104 def update_from_db(self, cr):
107 # update the graph with values from the database (if exist)
108 ## First, we set the default values for each package in graph
109 additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
110 ## Then we get the values from the database
111 cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
112 ' FROM ir_module_module'
113 ' WHERE name IN %s',(tuple(additional_data),)
116 ## and we update the default values with values from the database
117 additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
119 for package in self.values():
120 for k, v in additional_data[package.name].items():
121 setattr(package, k, v)
125 done = set(self.keys())
127 level_modules = [(name, module) for name, module in self.items() if module.depth==level]
128 for name, module in level_modules:
133 class Singleton(object):
134 def __new__(cls, name, graph):
138 inst = object.__new__(cls)
144 class Node(Singleton):
145 """ One module in the modules dependency graph.
147 Node acts as a per-module singleton.
151 def __init__(self, name, graph):
153 if not hasattr(self, 'children'):
155 if not hasattr(self, 'depth'):
158 def addChild(self, name):
159 node = Node(name, self.graph)
160 node.depth = self.depth + 1
161 if node not in self.children:
162 self.children.append(node)
163 for attr in ('init', 'update', 'demo'):
164 if hasattr(self, attr):
165 setattr(node, attr, True)
166 self.children.sort(lambda x, y: cmp(x.name, y.name))
168 def __setattr__(self, name, value):
169 super(Singleton, self).__setattr__(name, value)
170 if name in ('init', 'update', 'demo'):
171 tools.config[name][self.name] = 1
172 for child in self.children:
173 setattr(child, name, value)
175 for child in self.children:
176 setattr(child, name, value + 1)
179 return itertools.chain(iter(self.children), *map(iter, self.children))
182 return self._pprint()
184 def _pprint(self, depth=0):
185 s = '%s\n' % self.name
186 for c in self.children:
187 s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
191 def get_module_path(module, downloaded=False):
192 """Return the path of the given module.
194 Search the addons paths and return the first path where the given
195 module is found. If downloaded is True, return the default addons
196 path if nothing else is found.
199 initialize_sys_path()
201 if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
202 return opj(adp, module)
205 return opj(_ad, module)
206 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
210 def get_module_filetree(module, dir='.'):
211 path = get_module_path(module)
215 dir = os.path.normpath(dir)
218 if dir.startswith('..') or (dir and dir[0] == '/'):
219 raise Exception('Cannot access file outside the module')
221 if not os.path.isdir(path):
223 zip = zipfile.ZipFile(path + ".zip")
224 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
226 files = osutil.listdir(path, True)
230 if not f.startswith(dir):
234 f = f[len(dir)+int(not dir.endswith('/')):]
235 lst = f.split(os.sep)
238 current = current.setdefault(lst.pop(0), {})
239 current[lst.pop(0)] = None
243 def zip_directory(directory, b64enc=True, src=True):
244 """Compress a directory
246 @param directory: The directory to compress
247 @param base64enc: if True the function will encode the zip file with base64
248 @param src: Integrate the source files
250 @return: a string containing the zip file
253 RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
255 def _zippy(archive, path, src=True):
256 path = os.path.abspath(path)
257 base = os.path.basename(path)
258 for f in osutil.listdir(path, True):
259 bf = os.path.basename(f)
260 if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
261 archive.write(os.path.join(path, f), os.path.join(base, f))
263 archname = StringIO()
264 archive = PyZipFile(archname, "w", ZIP_DEFLATED)
266 # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
267 directory = tools.ustr(directory).encode('utf-8')
269 archive.writepy(directory)
270 _zippy(archive, directory, src=src)
272 archive_data = archname.getvalue()
276 return base64.encodestring(archive_data)
280 def get_module_as_zip(modulename, b64enc=True, src=True):
281 """Generate a module as zip file with the source or not and can do a base64 encoding
283 @param modulename: The module name
284 @param b64enc: if True the function will encode the zip file with base64
285 @param src: Integrate the source files
287 @return: a stream to store in a file-like object
290 ap = get_module_path(str(modulename))
292 raise Exception('Unable to find path for module %s' % modulename)
294 ap = ap.encode('utf8')
295 if os.path.isfile(ap + '.zip'):
296 val = file(ap + '.zip', 'rb').read()
298 val = base64.encodestring(val)
300 val = zip_directory(ap, b64enc, src)
305 def get_module_resource(module, *args):
306 """Return the full path of a resource of the given module.
308 @param module: the module
309 @param args: the resource path components
311 @return: absolute path to the resource
313 TODO name it get_resource_path
314 TODO make it available inside on osv object (self.get_resource_path)
316 a = get_module_path(module)
317 if not a: return False
318 resource_path = opj(a, *args)
319 if zipfile.is_zipfile( a +'.zip') :
320 zip = zipfile.ZipFile( a + ".zip")
321 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
322 resource_path = '/'.join(args)
323 if resource_path in files:
324 return opj(a, resource_path)
325 elif os.path.exists(resource_path):
330 """Returns the list of module names
334 name = os.path.basename(name)
335 if name[-4:] == '.zip':
339 def is_really_module(name):
340 name = opj(dir, name)
341 return os.path.isdir(name) or zipfile.is_zipfile(name)
342 return map(clean, filter(is_really_module, os.listdir(dir)))
345 initialize_sys_path()
347 plist.extend(listdir(ad))
348 return list(set(plist))
350 def load_information_from_description_file(module):
352 :param module: The name of the module (sale, purchase, ...)
355 for filename in ['__openerp__.py', '__terp__.py']:
356 description_file = get_module_resource(module, filename)
357 if description_file :
358 desc_f = tools.file_open(description_file)
360 return eval(desc_f.read())
364 #TODO: refactor the logger in this file to follow the logging guidelines
366 logging.getLogger('addons').debug('The module %s does not contain a description file:'\
367 '__openerp__.py or __terp__.py (deprecated)', module)
370 def get_modules_with_version():
371 modules = get_modules()
373 for module in modules:
375 info = load_information_from_description_file(module)
376 res[module] = "%s.%s" % (release.major_version, info['version'])
381 def create_graph(cr, module_list, force=None):
383 upgrade_graph(graph, cr, module_list, force)
386 def upgrade_graph(graph, cr, module_list, force=None):
390 len_graph = len(graph)
391 for module in module_list:
392 mod_path = get_module_path(module)
393 terp_file = get_module_resource(module, '__openerp__.py')
395 terp_file = get_module_resource(module, '__terp__.py')
396 if not mod_path or not terp_file:
397 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not found, skipped' % (module))
400 if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
401 terp_f = tools.file_open(terp_file)
403 info = eval(terp_f.read())
405 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
409 if info.get('installable', True):
410 packages.append((module, info.get('depends', []), info))
412 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
414 dependencies = dict([(p, deps) for p, deps, data in packages])
415 current, later = set([p for p, dep, data in packages]), set()
417 while packages and current > later:
418 package, deps, data = packages[0]
420 # if all dependencies of 'package' are already in the graph, add 'package' in the graph
421 if reduce(lambda x, y: x and y in graph, deps, True):
422 if not package in current:
426 current.remove(package)
427 graph.addNode(package, deps)
428 node = Node(package, graph)
430 for kind in ('init', 'demo', 'update'):
431 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
432 setattr(node, kind, True)
435 packages.append((package, deps, data))
438 graph.update_from_db(cr)
440 for package in later:
441 unmet_deps = filter(lambda p: p not in graph, dependencies[package])
442 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
444 result = len(graph) - len_graph
445 if result != len(module_list):
446 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
450 def init_module_objects(cr, module_name, obj_list):
451 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
455 result = obj._auto_init(cr, {'module': module_name})
460 if hasattr(obj, 'init'):
469 def load_module(module_name):
470 """ Load a Python module found on the addons paths."""
471 fm = imp.find_module(module_name, ad_paths)
473 imp.load_module(module_name, *fm)
479 def register_class(m):
481 Register module named m, if not already registered
485 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
486 msg = "Couldn't load %smodule %s" % (mt, m)
487 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
488 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
493 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
494 mod_path = get_module_path(m)
496 initialize_sys_path()
498 zip_mod_path = mod_path + '.zip'
499 if not os.path.isfile(zip_mod_path):
502 zimp = zipimport.zipimporter(zip_mod_path)
511 class MigrationManager(object):
513 This class manage the migration of modules
514 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
515 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
516 folder by version. Version can be 'module' version or 'server.module' version (in this case,
517 the files will only be processed by this version of the server). Python file names must start
518 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
524 | |-- pre-update_table_x.py
525 | |-- pre-update_table_y.py
526 | |-- post-clean-data.py
527 | `-- README.txt # not processed
528 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
529 | |-- pre-delete_table_z.py
530 | `-- post-clean-data.py
531 `-- foo.py # not processed
533 This similar structure is generated by the maintenance module with the migrations files get by
534 the maintenance contract
537 def __init__(self, cr, graph):
543 def _get_files(self):
546 import addons.base.maintenance.utils as maintenance_utils
547 maintenance_utils.update_migrations_files(self.cr)
550 for pkg in self.graph:
551 self.migrations[pkg.name] = {}
552 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
555 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
556 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
558 def migrate_module(self, pkg, stage):
559 assert stage in ('pre', 'post')
560 stageformat = {'pre': '[>%s]',
564 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
567 def convert_version(version):
568 if version.startswith(release.major_version) and version != release.major_version:
569 return version # the version number already containt the server version
570 return "%s.%s" % (release.major_version, version)
572 def _get_migration_versions(pkg):
574 return [d for d in tree if tree[d] is not None]
577 __get_dir(self.migrations[pkg.name]['module']) +
578 __get_dir(self.migrations[pkg.name]['maintenance'])
580 versions.sort(key=lambda k: parse_version(convert_version(k)))
583 def _get_migration_files(pkg, version, stage):
584 """ return a list of tuple (module, file)
586 m = self.migrations[pkg.name]
589 mapping = {'module': opj(pkg.name, 'migrations'),
590 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
593 for x in mapping.keys():
595 for f in m[x][version]:
596 if m[x][version][f] is not None:
598 if not f.startswith(stage + '-'):
600 lst.append(opj(mapping[x], version, f))
609 from openerp.tools.parse_version import parse_version
611 parsed_installed_version = parse_version(pkg.installed_version or '')
612 current_version = parse_version(convert_version(pkg.data.get('version', '0')))
614 versions = _get_migration_versions(pkg)
616 for version in versions:
617 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
619 strfmt = {'addon': pkg.name,
621 'version': stageformat[stage] % version,
624 for pyfile in _get_migration_files(pkg, version, stage):
625 name, ext = os.path.splitext(os.path.basename(pyfile))
626 if ext.lower() != '.py':
628 mod = fp = fp2 = None
630 fp = tools.file_open(pyfile)
632 # imp.load_source need a real file object, so we create
633 # one from the file-like object we get from file_open
638 mod = imp.load_source(name, pyfile, fp2)
639 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
640 mod.migrate(self.cr, pkg.installed_version)
642 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
644 except AttributeError:
645 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
657 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, **kwargs):
658 """Migrates+Updates or Installs all module nodes from ``graph``
659 :param graph: graph of module nodes to load
660 :param status: status dictionary for keeping track of progress
661 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
662 for same cases, and even raise osv_except if certificate is invalid)
663 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
664 :return: list of modules that were installed or updated
666 def process_sql_file(cr, fp):
667 queries = fp.read().split(';')
668 for query in queries:
669 new_query = ' '.join(query.split())
671 cr.execute(new_query)
673 def load_init_update_xml(cr, m, idref, mode, kind):
674 for filename in package.data.get('%s_xml' % kind, []):
675 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
676 _, ext = os.path.splitext(filename)
677 fp = tools.file_open(opj(m, filename))
680 noupdate = (kind == 'init')
681 tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
683 process_sql_file(cr, fp)
685 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
687 tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
691 def load_demo_xml(cr, m, idref, mode):
692 for xml in package.data.get('demo_xml', []):
693 name, ext = os.path.splitext(xml)
694 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
695 fp = tools.file_open(opj(m, xml))
698 tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
700 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
702 tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
706 def load_data(cr, module_name, id_map, mode):
707 _load_data(cr, module_name, id_map, mode, 'data')
709 def load_demo(cr, module_name, id_map, mode):
710 _load_data(cr, module_name, id_map, mode, 'demo')
712 def load_test(cr, module_name, id_map, mode):
714 if not tools.config.options['test_disable']:
716 _load_data(cr, module_name, id_map, mode, 'test')
718 logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
720 if tools.config.options['test_commit']:
725 def _load_data(cr, module_name, id_map, mode, kind):
726 for filename in package.data.get(kind, []):
727 noupdate = (kind == 'demo')
728 _, ext = os.path.splitext(filename)
729 log = logging.getLogger('init')
730 log.info("module %s: loading %s", module_name, filename)
731 pathname = os.path.join(module_name, filename)
732 file = tools.file_open(pathname)
735 process_sql_file(cr, file)
737 noupdate = (kind == 'init')
738 tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
740 tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
742 tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
746 # **kwargs is passed directly to convert_xml_import
750 status = status.copy()
751 processed_modules = []
753 pool = pooler.get_pool(cr.dbname)
754 migrations = MigrationManager(cr, graph)
756 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
758 for package in graph:
759 if skip_modules and package.name in skip_modules:
761 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
762 migrations.migrate_module(package, 'pre')
763 register_class(package.name)
764 modules = pool.instanciate(package.name, cr)
765 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
766 init_module_objects(cr, package.name, modules)
769 for package in graph:
770 status['progress'] = (float(statusi)+0.1) / len(graph)
774 if skip_modules and m in skip_modules:
778 modobj = pool.get('ir.module.module')
780 if modobj and perform_checks:
781 modobj.check(cr, 1, [mid])
784 status['progress'] = (float(statusi)+0.4) / len(graph)
787 if hasattr(package, 'init') or package.state == 'to install':
790 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
791 for kind in ('init', 'update'):
792 if package.state=='to upgrade':
793 # upgrading the module information
794 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
795 load_init_update_xml(cr, m, idref, mode, kind)
796 load_data(cr, m, idref, mode)
797 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
798 status['progress'] = (float(statusi)+0.75) / len(graph)
799 load_demo_xml(cr, m, idref, mode)
800 load_demo(cr, m, idref, mode)
801 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
803 # launch tests only in demo mode, as most tests will depend
804 # on demo data. Other tests can be added into the regular
805 # 'data' section, but should probably not alter the data,
806 # as there is no rollback.
807 load_test(cr, m, idref, mode)
809 processed_modules.append(package.name)
811 migrations.migrate_module(package, 'post')
814 ver = release.major_version + '.' + package.data.get('version', '1.0')
815 # Set new modules and dependencies
816 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
818 # Update translations for all installed languages
819 modobj.update_translations(cr, 1, [mid], None, {'overwrite': tools.config['overwrite_existing_translations']})
822 package.state = 'installed'
823 for kind in ('init', 'demo', 'update'):
824 if hasattr(package, kind):
825 delattr(package, kind)
831 return processed_modules
833 def _check_module_names(cr, module_names):
834 mod_names = set(module_names)
835 if 'base' in mod_names:
836 # ignore dummy 'all' module
837 if 'all' in mod_names:
838 mod_names.remove('all')
840 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
841 if cr.dictfetchone()['count'] != len(mod_names):
842 # find out what module name(s) are incorrect:
843 cr.execute("SELECT name FROM ir_module_module")
844 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
845 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
847 def load_modules(db, force_demo=False, status=None, update_module=False):
849 initialize_sys_path()
851 open_openerp_namespace()
857 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
858 if len(cr.fetchall())==0:
859 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
861 tools.config["init"]["all"] = 1
862 tools.config['update']['all'] = 1
863 if not tools.config['without_demo']:
864 tools.config["demo"]['all'] = 1
869 # This is a brand new pool, just created in pooler.get_db_and_pool()
870 pool = pooler.get_pool(cr.dbname)
873 processed_modules = []
874 report = tools.assertion_report()
875 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
876 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
877 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
878 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
880 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
881 graph = create_graph(cr, ['base'], force)
883 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
884 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
885 processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
887 if tools.config['load_language']:
888 for lang in tools.config['load_language'].split(','):
889 tools.load_language(cr, lang)
891 # STEP 2: Mark other modules to be loaded/updated
893 modobj = pool.get('ir.module.module')
894 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
895 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
896 modobj.update_list(cr, 1)
898 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
900 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
902 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
904 modobj.button_install(cr, 1, ids)
906 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
908 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
910 modobj.button_upgrade(cr, 1, ids)
912 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
914 STATES_TO_LOAD += ['to install']
917 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
921 if loop_guardrail > 100:
922 raise ValueError('Possible recursive module tree detected, aborting.')
923 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
925 module_list = [name for (name,) in cr.fetchall() if name not in graph]
929 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
930 if new_modules_in_graph == 0:
934 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
935 processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
938 cr.execute('select model from ir_model where state=%s', ('manual',))
939 for model in cr.dictfetchall():
940 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
942 # STEP 4: Finish and cleanup
943 if processed_modules:
944 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
945 for (model, name) in cr.fetchall():
946 model_obj = pool.get(model)
947 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
948 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
950 # Temporary warning while we remove access rights on osv_memory objects, as they have
951 # been replaced by owner-only access rights
952 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
953 for (model, name) in cr.fetchall():
954 model_obj = pool.get(model)
955 if isinstance(model_obj, osv.osv.osv_memory):
956 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
958 cr.execute("SELECT model from ir_model")
959 for (model,) in cr.fetchall():
960 obj = pool.get(model)
962 obj._check_removed_columns(cr, log=True)
964 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
966 # Cleanup orphan records
967 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
969 if report.get_report():
970 logger.notifyChannel('init', netsvc.LOG_INFO, report)
972 for kind in ('init', 'demo', 'update'):
973 tools.config[kind] = {}
977 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
978 for mod_id, mod_name in cr.fetchall():
979 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
980 for rmod, rid in cr.fetchall():
982 rmod_module= pool.get(rmod)
984 rmod_module.unlink(cr, uid, [rid])
986 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
987 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
990 # TODO: remove menu without actions of children
993 cr.execute('''delete from
996 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
998 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
1000 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
1005 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
1007 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
1013 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: