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 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 ##############################################################################
24 from os.path import join as opj
31 from tools.safe_eval import safe_eval as eval
33 from tools.translate import _
42 from zipfile import PyZipFile, ZIP_DEFLATED
43 from cStringIO import StringIO
48 logger = netsvc.Logger()
50 _ad = os.path.abspath(opj(tools.ustr(tools.config['root_path']), u'addons')) # default addons path (base)
51 ad_paths= map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(','))
53 sys.path.insert(1, _ad)
58 sys.path.insert(ad_cnt, adp)
61 ad_paths.append(_ad) # for get_module_path
63 # Modules already loaded
68 def addNode(self, name, deps):
69 max_depth, father = 0, None
70 for n in [Node(x, self) for x in deps]:
71 if n.depth >= max_depth:
79 def update_from_db(self, cr):
82 # update the graph with values from the database (if exist)
83 ## First, we set the default values for each package in graph
84 additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
85 ## Then we get the values from the database
86 cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
87 ' FROM ir_module_module'
88 ' WHERE name IN %s',(tuple(additional_data),)
91 ## and we update the default values with values from the database
92 additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
94 for package in self.values():
95 for k, v in additional_data[package.name].items():
96 setattr(package, k, v)
100 done = set(self.keys())
102 level_modules = [(name, module) for name, module in self.items() if module.depth==level]
103 for name, module in level_modules:
108 class Singleton(object):
109 def __new__(cls, name, graph):
113 inst = object.__new__(cls)
119 class Node(Singleton):
121 def __init__(self, name, graph):
123 if not hasattr(self, 'children'):
125 if not hasattr(self, 'depth'):
128 def addChild(self, name):
129 node = Node(name, self.graph)
130 node.depth = self.depth + 1
131 if node not in self.children:
132 self.children.append(node)
133 for attr in ('init', 'update', 'demo'):
134 if hasattr(self, attr):
135 setattr(node, attr, True)
136 self.children.sort(lambda x, y: cmp(x.name, y.name))
138 def __setattr__(self, name, value):
139 super(Singleton, self).__setattr__(name, value)
140 if name in ('init', 'update', 'demo'):
141 tools.config[name][self.name] = 1
142 for child in self.children:
143 setattr(child, name, value)
145 for child in self.children:
146 setattr(child, name, value + 1)
149 return itertools.chain(iter(self.children), *map(iter, self.children))
152 return self._pprint()
154 def _pprint(self, depth=0):
155 s = '%s\n' % self.name
156 for c in self.children:
157 s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
161 def get_module_path(module, downloaded=False):
162 """Return the path of the given module."""
164 if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
165 return opj(adp, module)
168 return opj(_ad, module)
169 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
173 def get_module_filetree(module, dir='.'):
174 path = get_module_path(module)
178 dir = os.path.normpath(dir)
181 if dir.startswith('..') or (dir and dir[0] == '/'):
182 raise Exception('Cannot access file outside the module')
184 if not os.path.isdir(path):
186 zip = zipfile.ZipFile(path + ".zip")
187 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
189 files = tools.osutil.listdir(path, True)
193 if not f.startswith(dir):
197 f = f[len(dir)+int(not dir.endswith('/')):]
198 lst = f.split(os.sep)
201 current = current.setdefault(lst.pop(0), {})
202 current[lst.pop(0)] = None
206 def zip_directory(directory, b64enc=True, src=True):
207 """Compress a directory
209 @param directory: The directory to compress
210 @param base64enc: if True the function will encode the zip file with base64
211 @param src: Integrate the source files
213 @return: a string containing the zip file
216 RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
218 def _zippy(archive, path, src=True):
219 path = os.path.abspath(path)
220 base = os.path.basename(path)
221 for f in tools.osutil.listdir(path, True):
222 bf = os.path.basename(f)
223 if not RE_exclude.search(bf) and (src or bf in ('__openerp__.py', '__terp__.py') or not bf.endswith('.py')):
224 archive.write(os.path.join(path, f), os.path.join(base, f))
226 archname = StringIO()
227 archive = PyZipFile(archname, "w", ZIP_DEFLATED)
229 # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8)
230 directory = tools.ustr(directory).encode('utf-8')
232 archive.writepy(directory)
233 _zippy(archive, directory, src=src)
235 archive_data = archname.getvalue()
239 return base64.encodestring(archive_data)
243 def get_module_as_zip(modulename, b64enc=True, src=True):
244 """Generate a module as zip file with the source or not and can do a base64 encoding
246 @param modulename: The module name
247 @param b64enc: if True the function will encode the zip file with base64
248 @param src: Integrate the source files
250 @return: a stream to store in a file-like object
253 ap = get_module_path(str(modulename))
255 raise Exception('Unable to find path for module %s' % modulename)
257 ap = ap.encode('utf8')
258 if os.path.isfile(ap + '.zip'):
259 val = file(ap + '.zip', 'rb').read()
261 val = base64.encodestring(val)
263 val = zip_directory(ap, b64enc, src)
268 def get_module_resource(module, *args):
269 """Return the full path of a resource of the given module.
271 @param module: the module
272 @param args: the resource path components
274 @return: absolute path to the resource
276 a = get_module_path(module)
277 if not a: return False
278 resource_path = opj(a, *args)
279 if zipfile.is_zipfile( a +'.zip') :
280 zip = zipfile.ZipFile( a + ".zip")
281 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
282 resource_path = '/'.join(args)
283 if resource_path in files:
284 return opj(a, resource_path)
285 elif os.path.exists(resource_path):
292 """Returns the list of module names
296 name = os.path.basename(name)
297 if name[-4:] == '.zip':
301 def is_really_module(name):
302 name = opj(dir, name)
303 return os.path.isdir(name) or zipfile.is_zipfile(name)
304 return map(clean, filter(is_really_module, os.listdir(dir)))
308 plist.extend(listdir(ad))
309 return list(set(plist))
311 def load_information_from_description_file(module):
313 :param module: The name of the module (sale, purchase, ...)
316 for filename in ['__openerp__.py', '__terp__.py']:
317 description_file = get_module_resource(module, filename)
318 if description_file :
319 desc_f = tools.file_open(description_file)
321 return eval(desc_f.read())
325 #TODO: refactor the logger in this file to follow the logging guidelines
327 logging.getLogger('addons').debug('The module %s does not contain a description file:'\
328 '__openerp__.py or __terp__.py (deprecated)', module)
331 def get_modules_with_version():
332 modules = get_modules()
334 for module in modules:
336 info = load_information_from_description_file(module)
337 res[module] = "%s.%s" % (release.major_version, info['version'])
342 def create_graph(cr, module_list, force=None):
344 upgrade_graph(graph, cr, module_list, force)
347 def upgrade_graph(graph, cr, module_list, force=None):
351 len_graph = len(graph)
352 for module in module_list:
353 mod_path = get_module_path(module)
354 terp_file = get_module_resource(module, '__openerp__.py')
356 terp_file = get_module_resource(module, '__terp__.py')
357 if not mod_path or not terp_file:
358 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not found, skipped' % (module))
361 if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
362 terp_f = tools.file_open(terp_file)
364 info = eval(terp_f.read())
366 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
370 if info.get('installable', True):
371 packages.append((module, info.get('depends', []), info))
373 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
375 dependencies = dict([(p, deps) for p, deps, data in packages])
376 current, later = set([p for p, dep, data in packages]), set()
378 while packages and current > later:
379 package, deps, data = packages[0]
381 # if all dependencies of 'package' are already in the graph, add 'package' in the graph
382 if reduce(lambda x, y: x and y in graph, deps, True):
383 if not package in current:
387 current.remove(package)
388 graph.addNode(package, deps)
389 node = Node(package, graph)
391 for kind in ('init', 'demo', 'update'):
392 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
393 setattr(node, kind, True)
396 packages.append((package, deps, data))
399 graph.update_from_db(cr)
401 for package in later:
402 unmet_deps = filter(lambda p: p not in graph, dependencies[package])
403 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
405 result = len(graph) - len_graph
406 if result != len(module_list):
407 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
411 def init_module_objects(cr, module_name, obj_list):
412 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
416 result = obj._auto_init(cr, {'module': module_name})
421 if hasattr(obj, 'init'):
430 def register_class(m):
432 Register module named m, if not already registered
436 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
437 msg = "Couldn't load %smodule %s" % (mt, m)
438 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
439 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
444 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
445 mod_path = get_module_path(m)
448 zip_mod_path = mod_path + '.zip'
449 if not os.path.isfile(zip_mod_path):
450 fm = imp.find_module(m, ad_paths)
452 imp.load_module(m, *fm)
457 zimp = zipimport.zipimporter(zip_mod_path)
466 class MigrationManager(object):
468 This class manage the migration of modules
469 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
470 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
471 folder by version. Version can be 'module' version or 'server.module' version (in this case,
472 the files will only be processed by this version of the server). Python file names must start
473 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
479 | |-- pre-update_table_x.py
480 | |-- pre-update_table_y.py
481 | |-- post-clean-data.py
482 | `-- README.txt # not processed
483 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
484 | |-- pre-delete_table_z.py
485 | `-- post-clean-data.py
486 `-- foo.py # not processed
488 This similar structure is generated by the maintenance module with the migrations files get by
489 the maintenance contract
492 def __init__(self, cr, graph):
498 def _get_files(self):
501 import addons.base.maintenance.utils as maintenance_utils
502 maintenance_utils.update_migrations_files(self.cr)
505 for pkg in self.graph:
506 self.migrations[pkg.name] = {}
507 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
510 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
511 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
513 def migrate_module(self, pkg, stage):
514 assert stage in ('pre', 'post')
515 stageformat = {'pre': '[>%s]',
519 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
522 def convert_version(version):
523 if version.startswith(release.major_version) and version != release.major_version:
524 return version # the version number already containt the server version
525 return "%s.%s" % (release.major_version, version)
527 def _get_migration_versions(pkg):
529 return [d for d in tree if tree[d] is not None]
532 __get_dir(self.migrations[pkg.name]['module']) +
533 __get_dir(self.migrations[pkg.name]['maintenance'])
535 versions.sort(key=lambda k: parse_version(convert_version(k)))
538 def _get_migration_files(pkg, version, stage):
539 """ return a list of tuple (module, file)
541 m = self.migrations[pkg.name]
544 mapping = {'module': opj(pkg.name, 'migrations'),
545 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
548 for x in mapping.keys():
550 for f in m[x][version]:
551 if m[x][version][f] is not None:
553 if not f.startswith(stage + '-'):
555 lst.append(opj(mapping[x], version, f))
564 from tools.parse_version import parse_version
566 parsed_installed_version = parse_version(pkg.installed_version or '')
567 current_version = parse_version(convert_version(pkg.data.get('version', '0')))
569 versions = _get_migration_versions(pkg)
571 for version in versions:
572 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
574 strfmt = {'addon': pkg.name,
576 'version': stageformat[stage] % version,
579 for pyfile in _get_migration_files(pkg, version, stage):
580 name, ext = os.path.splitext(os.path.basename(pyfile))
581 if ext.lower() != '.py':
583 mod = fp = fp2 = None
585 fp = tools.file_open(pyfile)
587 # imp.load_source need a real file object, so we create
588 # one from the file-like object we get from file_open
593 mod = imp.load_source(name, pyfile, fp2)
594 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
595 mod.migrate(self.cr, pkg.installed_version)
597 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
599 except AttributeError:
600 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
611 log = logging.getLogger('init')
613 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, **kwargs):
614 """Migrates+Updates or Installs all module nodes from ``graph``
615 :param graph: graph of module nodes to load
616 :param status: status dictionary for keeping track of progress
617 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
618 for same cases, and even raise osv_except if certificate is invalid)
619 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
620 :return: list of modules that were installed or updated
622 def process_sql_file(cr, fp):
623 queries = fp.read().split(';')
624 for query in queries:
625 new_query = ' '.join(query.split())
627 cr.execute(new_query)
629 def load_init_update_xml(cr, m, idref, mode, kind):
630 for filename in package.data.get('%s_xml' % kind, []):
631 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
632 _, ext = os.path.splitext(filename)
633 fp = tools.file_open(opj(m, filename))
636 noupdate = (kind == 'init')
637 tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
639 process_sql_file(cr, fp)
641 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
643 tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
647 def load_demo_xml(cr, m, idref, mode):
648 for xml in package.data.get('demo_xml', []):
649 name, ext = os.path.splitext(xml)
650 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
651 fp = tools.file_open(opj(m, xml))
654 tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
656 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
658 tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
662 def load_data(cr, module_name, id_map, mode):
663 _load_data(cr, module_name, id_map, mode, 'data')
665 def load_demo(cr, module_name, id_map, mode):
666 _load_data(cr, module_name, id_map, mode, 'demo')
668 def load_test(cr, module_name, id_map, mode):
670 if not tools.config.options['test_disable']:
672 _load_data(cr, module_name, id_map, mode, 'test')
674 logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
676 if tools.config.options['test_commit']:
681 def _load_data(cr, module_name, id_map, mode, kind):
682 noupdate = (kind == 'demo')
683 for filename in package.data.get(kind, []):
684 _, ext = os.path.splitext(filename)
685 log.info("module %s: loading %s", module_name, filename)
686 pathname = os.path.join(module_name, filename)
687 file = tools.file_open(pathname)
690 process_sql_file(cr, file)
692 noupdate = (kind == 'init')
693 tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
695 tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
697 tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
701 # **kwargs is passed directly to convert_xml_import
705 status = status.copy()
706 processed_modules = []
708 pool = pooler.get_pool(cr.dbname)
709 migrations = MigrationManager(cr, graph)
711 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
713 for package in graph:
714 if skip_modules and package.name in skip_modules:
716 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
717 migrations.migrate_module(package, 'pre')
718 register_class(package.name)
719 modules = pool.instanciate(package.name, cr)
720 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
721 init_module_objects(cr, package.name, modules)
724 for package in graph:
725 status['progress'] = (float(statusi)+0.1) / len(graph)
729 if skip_modules and m in skip_modules:
733 modobj = pool.get('ir.module.module')
735 if modobj and perform_checks:
736 modobj.check(cr, 1, [mid])
739 status['progress'] = (float(statusi)+0.4) / len(graph)
742 if hasattr(package, 'init') or package.state == 'to install':
745 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
746 for kind in ('init', 'update'):
747 if package.state=='to upgrade':
748 # upgrading the module information
749 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
750 load_init_update_xml(cr, m, idref, mode, kind)
751 load_data(cr, m, idref, mode)
752 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
753 status['progress'] = (float(statusi)+0.75) / len(graph)
754 load_demo_xml(cr, m, idref, mode)
755 load_demo(cr, m, idref, mode)
756 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
758 # launch tests only in demo mode, as most tests will depend
759 # on demo data. Other tests can be added into the regular
760 # 'data' section, but should probably not alter the data,
761 # as there is no rollback.
762 load_test(cr, m, idref, mode)
764 processed_modules.append(package.name)
766 migrations.migrate_module(package, 'post')
769 ver = release.major_version + '.' + package.data.get('version', '1.0')
770 # Set new modules and dependencies
771 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
773 # Update translations for all installed languages
774 modobj.update_translations(cr, 1, [mid], None)
777 package.state = 'installed'
778 for kind in ('init', 'demo', 'update'):
779 if hasattr(package, kind):
780 delattr(package, kind)
786 return processed_modules
788 def _check_module_names(cr, module_names):
789 mod_names = set(module_names)
790 if 'base' in mod_names:
791 # ignore dummy 'all' module
792 if 'all' in mod_names:
793 mod_names.remove('all')
795 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
796 if cr.dictfetchone()['count'] != len(mod_names):
797 # find out what module name(s) are incorrect:
798 cr.execute("SELECT name FROM ir_module_module")
799 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
800 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
802 def load_modules(db, force_demo=False, status=None, update_module=False):
807 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
808 if len(cr.fetchall())==0:
809 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
811 tools.config["init"]["all"] = 1
812 tools.config['update']['all'] = 1
813 if not tools.config['without_demo']:
814 tools.config["demo"]['all'] = 1
819 # This is a brand new pool, just created in pooler.get_db_and_pool()
820 pool = pooler.get_pool(cr.dbname)
823 processed_modules = []
824 report = tools.assertion_report()
825 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
826 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
827 if 'base' in tools.config['update']:
828 cr.execute("update ir_module_module set state=%s where name=%s", ('to upgrade', 'base'))
830 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
831 graph = create_graph(cr, ['base'], force)
833 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
834 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
835 processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
837 if tools.config['load_language']:
838 for lang in tools.config['load_language'].split(','):
839 tools.load_language(cr, lang)
841 # STEP 2: Mark other modules to be loaded/updated
843 modobj = pool.get('ir.module.module')
844 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
845 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
846 modobj.update_list(cr, 1)
848 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
850 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
852 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
854 modobj.button_install(cr, 1, ids)
856 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
858 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
860 modobj.button_upgrade(cr, 1, ids)
862 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
864 STATES_TO_LOAD += ['to install']
867 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
871 if loop_guardrail > 100:
872 raise ValueError('Possible recursive module tree detected, aborting.')
873 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
875 module_list = [name for (name,) in cr.fetchall() if name not in graph]
879 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
880 if new_modules_in_graph == 0:
884 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
885 processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
887 # STEP 4: Finish and cleanup
888 if processed_modules:
890 cr.execute('select model from ir_model where state=%s', ('manual',))
891 for model in cr.dictfetchall():
892 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
894 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
895 for (model, name) in cr.fetchall():
896 model_obj = pool.get(model)
897 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
898 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
900 # Temporary warning while we remove access rights on osv_memory objects, as they have
901 # been replaced by owner-only access rights
902 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
903 for (model, name) in cr.fetchall():
904 model_obj = pool.get(model)
905 if isinstance(model_obj, osv.osv.osv_memory):
906 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
908 cr.execute("SELECT model from ir_model")
909 for (model,) in cr.fetchall():
910 obj = pool.get(model)
912 obj._check_removed_columns(cr, log=True)
914 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
916 # Cleanup orphan records
917 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
919 if report.get_report():
920 logger.notifyChannel('init', netsvc.LOG_INFO, report)
922 for kind in ('init', 'demo', 'update'):
923 tools.config[kind] = {}
927 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
928 for mod_id, mod_name in cr.fetchall():
929 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
930 for rmod, rid in cr.fetchall():
932 rmod_module= pool.get(rmod)
934 rmod_module.unlink(cr, uid, [rid])
936 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
937 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
940 # TODO: remove menu without actions of children
943 cr.execute('''delete from
946 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
948 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
950 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
955 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
957 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
963 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: