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 ##############################################################################
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):
290 """Returns the list of module names
294 name = os.path.basename(name)
295 if name[-4:] == '.zip':
299 def is_really_module(name):
300 name = opj(dir, name)
301 return os.path.isdir(name) or zipfile.is_zipfile(name)
302 return map(clean, filter(is_really_module, os.listdir(dir)))
306 plist.extend(listdir(ad))
307 return list(set(plist))
309 def load_information_from_description_file(module):
311 :param module: The name of the module (sale, purchase, ...)
314 for filename in ['__openerp__.py', '__terp__.py']:
315 description_file = get_module_resource(module, filename)
316 if description_file :
317 desc_f = tools.file_open(description_file)
319 return eval(desc_f.read())
323 #TODO: refactor the logger in this file to follow the logging guidelines
325 logging.getLogger('addons').debug('The module %s does not contain a description file:'\
326 '__openerp__.py or __terp__.py (deprecated)', module)
329 def get_modules_with_version():
330 modules = get_modules()
332 for module in modules:
334 info = load_information_from_description_file(module)
335 res[module] = "%s.%s" % (release.major_version, info['version'])
340 def create_graph(cr, module_list, force=None):
342 upgrade_graph(graph, cr, module_list, force)
345 def upgrade_graph(graph, cr, module_list, force=None):
349 len_graph = len(graph)
350 for module in module_list:
351 mod_path = get_module_path(module)
352 terp_file = get_module_resource(module, '__openerp__.py')
354 terp_file = get_module_resource(module, '__terp__.py')
355 if not mod_path or not terp_file:
356 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not found, skipped' % (module))
359 if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
360 terp_f = tools.file_open(terp_file)
362 info = eval(terp_f.read())
364 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
368 if info.get('installable', True):
369 packages.append((module, info.get('depends', []), info))
371 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
373 dependencies = dict([(p, deps) for p, deps, data in packages])
374 current, later = set([p for p, dep, data in packages]), set()
376 while packages and current > later:
377 package, deps, data = packages[0]
379 # if all dependencies of 'package' are already in the graph, add 'package' in the graph
380 if reduce(lambda x, y: x and y in graph, deps, True):
381 if not package in current:
385 current.remove(package)
386 graph.addNode(package, deps)
387 node = Node(package, graph)
389 for kind in ('init', 'demo', 'update'):
390 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
391 setattr(node, kind, True)
394 packages.append((package, deps, data))
397 graph.update_from_db(cr)
399 for package in later:
400 unmet_deps = filter(lambda p: p not in graph, dependencies[package])
401 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
403 result = len(graph) - len_graph
404 if result != len(module_list):
405 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
409 def init_module_objects(cr, module_name, obj_list):
410 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
414 result = obj._auto_init(cr, {'module': module_name})
419 if hasattr(obj, 'init'):
428 def register_class(m):
430 Register module named m, if not already registered
434 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
435 msg = "Couldn't load %smodule %s" % (mt, m)
436 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
437 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
442 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
443 mod_path = get_module_path(m)
446 zip_mod_path = mod_path + '.zip'
447 if not os.path.isfile(zip_mod_path):
448 fm = imp.find_module(m, ad_paths)
450 imp.load_module(m, *fm)
455 zimp = zipimport.zipimporter(zip_mod_path)
464 class MigrationManager(object):
466 This class manage the migration of modules
467 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
468 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
469 folder by version. Version can be 'module' version or 'server.module' version (in this case,
470 the files will only be processed by this version of the server). Python file names must start
471 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
477 | |-- pre-update_table_x.py
478 | |-- pre-update_table_y.py
479 | |-- post-clean-data.py
480 | `-- README.txt # not processed
481 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
482 | |-- pre-delete_table_z.py
483 | `-- post-clean-data.py
484 `-- foo.py # not processed
486 This similar structure is generated by the maintenance module with the migrations files get by
487 the maintenance contract
490 def __init__(self, cr, graph):
496 def _get_files(self):
499 import addons.base.maintenance.utils as maintenance_utils
500 maintenance_utils.update_migrations_files(self.cr)
503 for pkg in self.graph:
504 self.migrations[pkg.name] = {}
505 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
508 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
509 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
511 def migrate_module(self, pkg, stage):
512 assert stage in ('pre', 'post')
513 stageformat = {'pre': '[>%s]',
517 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
520 def convert_version(version):
521 if version.startswith(release.major_version) and version != release.major_version:
522 return version # the version number already containt the server version
523 return "%s.%s" % (release.major_version, version)
525 def _get_migration_versions(pkg):
527 return [d for d in tree if tree[d] is not None]
530 __get_dir(self.migrations[pkg.name]['module']) +
531 __get_dir(self.migrations[pkg.name]['maintenance'])
533 versions.sort(key=lambda k: parse_version(convert_version(k)))
536 def _get_migration_files(pkg, version, stage):
537 """ return a list of tuple (module, file)
539 m = self.migrations[pkg.name]
542 mapping = {'module': opj(pkg.name, 'migrations'),
543 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
546 for x in mapping.keys():
548 for f in m[x][version]:
549 if m[x][version][f] is not None:
551 if not f.startswith(stage + '-'):
553 lst.append(opj(mapping[x], version, f))
562 from tools.parse_version import parse_version
564 parsed_installed_version = parse_version(pkg.installed_version or '')
565 current_version = parse_version(convert_version(pkg.data.get('version', '0')))
567 versions = _get_migration_versions(pkg)
569 for version in versions:
570 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
572 strfmt = {'addon': pkg.name,
574 'version': stageformat[stage] % version,
577 for pyfile in _get_migration_files(pkg, version, stage):
578 name, ext = os.path.splitext(os.path.basename(pyfile))
579 if ext.lower() != '.py':
581 mod = fp = fp2 = None
583 fp = tools.file_open(pyfile)
585 # imp.load_source need a real file object, so we create
586 # one from the file-like object we get from file_open
591 mod = imp.load_source(name, pyfile, fp2)
592 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
593 mod.migrate(self.cr, pkg.installed_version)
595 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
597 except AttributeError:
598 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
609 log = logging.getLogger('init')
611 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, **kwargs):
612 """Migrates+Updates or Installs all module nodes from ``graph``
613 :param graph: graph of module nodes to load
614 :param status: status dictionary for keeping track of progress
615 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
616 for same cases, and even raise osv_except if certificate is invalid)
617 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
618 :return: list of modules that were installed or updated
620 def process_sql_file(cr, fp):
621 queries = fp.read().split(';')
622 for query in queries:
623 new_query = ' '.join(query.split())
625 cr.execute(new_query)
627 def load_init_update_xml(cr, m, idref, mode, kind):
628 for filename in package.data.get('%s_xml' % kind, []):
629 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
630 _, ext = os.path.splitext(filename)
631 fp = tools.file_open(opj(m, filename))
634 noupdate = (kind == 'init')
635 tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
637 process_sql_file(cr, fp)
639 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
641 tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
645 def load_demo_xml(cr, m, idref, mode):
646 for xml in package.data.get('demo_xml', []):
647 name, ext = os.path.splitext(xml)
648 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
649 fp = tools.file_open(opj(m, xml))
652 tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
654 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
656 tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
660 def load_data(cr, module_name, id_map, mode):
661 _load_data(cr, module_name, id_map, mode, 'data')
663 def load_demo(cr, module_name, id_map, mode):
664 _load_data(cr, module_name, id_map, mode, 'demo')
666 def load_test(cr, module_name, id_map, mode):
668 if not tools.config.options['test_disable']:
670 _load_data(cr, module_name, id_map, mode, 'test')
672 logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
674 if tools.config.options['test_commit']:
679 def _load_data(cr, module_name, id_map, mode, kind):
680 for filename in package.data.get(kind, []):
681 noupdate = (kind == 'demo')
682 _, ext = os.path.splitext(filename)
683 log.info("module %s: loading %s", module_name, filename)
684 pathname = os.path.join(module_name, filename)
685 file = tools.file_open(pathname)
688 process_sql_file(cr, file)
690 noupdate = (kind == 'init')
691 tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
693 tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
695 tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
699 # **kwargs is passed directly to convert_xml_import
703 status = status.copy()
704 processed_modules = []
706 pool = pooler.get_pool(cr.dbname)
707 migrations = MigrationManager(cr, graph)
709 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
711 for package in graph:
712 if skip_modules and package.name in skip_modules:
714 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
715 migrations.migrate_module(package, 'pre')
716 register_class(package.name)
717 modules = pool.instanciate(package.name, cr)
718 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
719 init_module_objects(cr, package.name, modules)
722 for package in graph:
723 status['progress'] = (float(statusi)+0.1) / len(graph)
727 if skip_modules and m in skip_modules:
731 modobj = pool.get('ir.module.module')
733 if modobj and perform_checks:
734 modobj.check(cr, 1, [mid])
737 status['progress'] = (float(statusi)+0.4) / len(graph)
740 if hasattr(package, 'init') or package.state == 'to install':
743 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
744 for kind in ('init', 'update'):
745 if package.state=='to upgrade':
746 # upgrading the module information
747 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
748 load_init_update_xml(cr, m, idref, mode, kind)
749 load_data(cr, m, idref, mode)
750 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
751 status['progress'] = (float(statusi)+0.75) / len(graph)
752 load_demo_xml(cr, m, idref, mode)
753 load_demo(cr, m, idref, mode)
754 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
756 # launch tests only in demo mode, as most tests will depend
757 # on demo data. Other tests can be added into the regular
758 # 'data' section, but should probably not alter the data,
759 # as there is no rollback.
760 load_test(cr, m, idref, mode)
762 processed_modules.append(package.name)
764 migrations.migrate_module(package, 'post')
767 ver = release.major_version + '.' + package.data.get('version', '1.0')
768 # Set new modules and dependencies
769 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
771 # Update translations for all installed languages
772 modobj.update_translations(cr, 1, [mid], None, {'overwrite': True})
775 package.state = 'installed'
776 for kind in ('init', 'demo', 'update'):
777 if hasattr(package, kind):
778 delattr(package, kind)
784 return processed_modules
786 def _check_module_names(cr, module_names):
787 mod_names = set(module_names)
788 if 'base' in mod_names:
789 # ignore dummy 'all' module
790 if 'all' in mod_names:
791 mod_names.remove('all')
793 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
794 if cr.dictfetchone()['count'] != len(mod_names):
795 # find out what module name(s) are incorrect:
796 cr.execute("SELECT name FROM ir_module_module")
797 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
798 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
800 def load_modules(db, force_demo=False, status=None, update_module=False):
805 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
806 if len(cr.fetchall())==0:
807 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
809 tools.config["init"]["all"] = 1
810 tools.config['update']['all'] = 1
811 if not tools.config['without_demo']:
812 tools.config["demo"]['all'] = 1
817 # This is a brand new pool, just created in pooler.get_db_and_pool()
818 pool = pooler.get_pool(cr.dbname)
821 processed_modules = []
822 report = tools.assertion_report()
823 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
824 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
825 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
826 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
828 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
829 graph = create_graph(cr, ['base'], force)
831 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
832 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
833 processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
835 if tools.config['load_language']:
836 for lang in tools.config['load_language'].split(','):
837 tools.load_language(cr, lang)
839 # STEP 2: Mark other modules to be loaded/updated
841 modobj = pool.get('ir.module.module')
842 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
843 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
844 modobj.update_list(cr, 1)
846 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
848 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
850 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
852 modobj.button_install(cr, 1, ids)
854 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
856 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
858 modobj.button_upgrade(cr, 1, ids)
860 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
862 STATES_TO_LOAD += ['to install']
865 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
869 if loop_guardrail > 100:
870 raise ValueError('Possible recursive module tree detected, aborting.')
871 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
873 module_list = [name for (name,) in cr.fetchall() if name not in graph]
877 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
878 if new_modules_in_graph == 0:
882 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
883 processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
886 cr.execute('select model from ir_model where state=%s', ('manual',))
887 for model in cr.dictfetchall():
888 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
890 # STEP 4: Finish and cleanup
891 if processed_modules:
892 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
893 for (model, name) in cr.fetchall():
894 model_obj = pool.get(model)
895 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
896 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
898 # Temporary warning while we remove access rights on osv_memory objects, as they have
899 # been replaced by owner-only access rights
900 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
901 for (model, name) in cr.fetchall():
902 model_obj = pool.get(model)
903 if isinstance(model_obj, osv.osv.osv_memory):
904 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
906 cr.execute("SELECT model from ir_model")
907 for (model,) in cr.fetchall():
908 obj = pool.get(model)
910 obj._check_removed_columns(cr, log=True)
912 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
914 # Cleanup orphan records
915 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
917 if report.get_report():
918 logger.notifyChannel('init', netsvc.LOG_INFO, report)
920 for kind in ('init', 'demo', 'update'):
921 tools.config[kind] = {}
925 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
926 for mod_id, mod_name in cr.fetchall():
927 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
928 for rmod, rid in cr.fetchall():
930 rmod_module= pool.get(rmod)
932 rmod_module.unlink(cr, uid, [rid])
934 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
935 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
938 # TODO: remove menu without actions of children
941 cr.execute('''delete from
944 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
946 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
948 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
953 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
955 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
961 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: