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_models(cr, module_name, obj_list):
451 """ Initialize a list of models.
453 Call _auto_init and init on each model to create or update the
454 database tables supporting the models.
456 TODO better explanation of _auto_init and init.
460 logger.notifyChannel('init', netsvc.LOG_INFO,
461 'module %s: creating or updating database tables' % module_name)
462 # TODO _auto_init doesn't seem to return anything
463 # so this todo list would be useless.
467 # TODO the module in the context doesn't seem usefull:
468 # it is available (at least) in the class' _module attribute.
469 # (So module_name would be useless too.)
470 result = obj._auto_init(cr, {'module': module_name})
475 if hasattr(obj, 'init'):
484 def load_module(module_name):
485 """ Load a Python module found on the addons paths."""
486 fm = imp.find_module(module_name, ad_paths)
488 imp.load_module(module_name, *fm)
494 def register_class(m):
496 Register module named m, if not already registered
500 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
501 msg = "Couldn't load %smodule %s" % (mt, m)
502 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
503 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
508 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
509 mod_path = get_module_path(m)
511 initialize_sys_path()
513 zip_mod_path = mod_path + '.zip'
514 if not os.path.isfile(zip_mod_path):
517 zimp = zipimport.zipimporter(zip_mod_path)
526 class MigrationManager(object):
528 This class manage the migration of modules
529 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
530 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
531 folder by version. Version can be 'module' version or 'server.module' version (in this case,
532 the files will only be processed by this version of the server). Python file names must start
533 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
539 | |-- pre-update_table_x.py
540 | |-- pre-update_table_y.py
541 | |-- post-clean-data.py
542 | `-- README.txt # not processed
543 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
544 | |-- pre-delete_table_z.py
545 | `-- post-clean-data.py
546 `-- foo.py # not processed
548 This similar structure is generated by the maintenance module with the migrations files get by
549 the maintenance contract
552 def __init__(self, cr, graph):
558 def _get_files(self):
561 import addons.base.maintenance.utils as maintenance_utils
562 maintenance_utils.update_migrations_files(self.cr)
565 for pkg in self.graph:
566 self.migrations[pkg.name] = {}
567 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
570 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
571 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
573 def migrate_module(self, pkg, stage):
574 assert stage in ('pre', 'post')
575 stageformat = {'pre': '[>%s]',
579 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
582 def convert_version(version):
583 if version.startswith(release.major_version) and version != release.major_version:
584 return version # the version number already containt the server version
585 return "%s.%s" % (release.major_version, version)
587 def _get_migration_versions(pkg):
589 return [d for d in tree if tree[d] is not None]
592 __get_dir(self.migrations[pkg.name]['module']) +
593 __get_dir(self.migrations[pkg.name]['maintenance'])
595 versions.sort(key=lambda k: parse_version(convert_version(k)))
598 def _get_migration_files(pkg, version, stage):
599 """ return a list of tuple (module, file)
601 m = self.migrations[pkg.name]
604 mapping = {'module': opj(pkg.name, 'migrations'),
605 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
608 for x in mapping.keys():
610 for f in m[x][version]:
611 if m[x][version][f] is not None:
613 if not f.startswith(stage + '-'):
615 lst.append(opj(mapping[x], version, f))
624 from openerp.tools.parse_version import parse_version
626 parsed_installed_version = parse_version(pkg.installed_version or '')
627 current_version = parse_version(convert_version(pkg.data.get('version', '0')))
629 versions = _get_migration_versions(pkg)
631 for version in versions:
632 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
634 strfmt = {'addon': pkg.name,
636 'version': stageformat[stage] % version,
639 for pyfile in _get_migration_files(pkg, version, stage):
640 name, ext = os.path.splitext(os.path.basename(pyfile))
641 if ext.lower() != '.py':
643 mod = fp = fp2 = None
645 fp = tools.file_open(pyfile)
647 # imp.load_source need a real file object, so we create
648 # one from the file-like object we get from file_open
653 mod = imp.load_source(name, pyfile, fp2)
654 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
655 mod.migrate(self.cr, pkg.installed_version)
657 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
659 except AttributeError:
660 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
672 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
673 """Migrates+Updates or Installs all module nodes from ``graph``
674 :param graph: graph of module nodes to load
675 :param status: status dictionary for keeping track of progress
676 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
677 for same cases, and even raise osv_except if certificate is invalid)
678 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
679 :return: list of modules that were installed or updated
681 def process_sql_file(cr, fp):
682 queries = fp.read().split(';')
683 for query in queries:
684 new_query = ' '.join(query.split())
686 cr.execute(new_query)
688 def load_init_xml(cr, m, idref, mode):
689 _load_xml(cr, m, idref, mode, 'init')
691 def load_update_xml(cr, m, idref, mode):
692 _load_xml(cr, m, idref, mode, 'update')
694 def load_demo_xml(cr, m, idref, mode):
695 _load_xml(cr, m, idref, mode, 'demo')
697 def load_data(cr, module_name, id_map, mode):
698 _load_data(cr, module_name, id_map, mode, 'data')
700 def load_demo(cr, module_name, id_map, mode):
701 _load_data(cr, module_name, id_map, mode, 'demo')
703 def load_test(cr, module_name, id_map, mode):
705 if not tools.config.options['test_disable']:
707 _load_data(cr, module_name, id_map, mode, 'test')
709 logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
711 if tools.config.options['test_commit']:
716 def _load_xml(cr, m, idref, mode, kind):
717 for filename in package.data.get('%s_xml' % kind, []):
718 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
719 _, ext = os.path.splitext(filename)
720 fp = tools.file_open(opj(m, filename))
728 # i.e. noupdate == False when kind == 'update'
729 tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
730 elif kind != 'demo' and ext == '.sql':
731 process_sql_file(cr, fp)
733 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=noupdate)
735 tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=noupdate, report=report)
739 def _load_data(cr, module_name, id_map, mode, kind):
740 for filename in package.data.get(kind, []):
741 noupdate = (kind == 'demo')
742 _, ext = os.path.splitext(filename)
743 log = logging.getLogger('init')
744 log.info("module %s: loading %s", module_name, filename)
745 pathname = os.path.join(module_name, filename)
746 file = tools.file_open(pathname)
749 process_sql_file(cr, file)
751 noupdate = (kind == 'init')
752 tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
754 tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
756 tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
760 # **kwargs is passed directly to convert_xml_import
764 status = status.copy()
765 processed_modules = []
767 pool = pooler.get_pool(cr.dbname)
768 migrations = MigrationManager(cr, graph)
770 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
772 for package in graph:
773 if skip_modules and package.name in skip_modules:
775 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
776 migrations.migrate_module(package, 'pre')
777 register_class(package.name)
778 models = pool.instanciate(package.name, cr)
779 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
780 init_module_models(cr, package.name, models)
783 for package in graph:
784 status['progress'] = (float(statusi)+0.1) / len(graph)
788 if skip_modules and m in skip_modules:
792 modobj = pool.get('ir.module.module')
794 if modobj and perform_checks:
795 modobj.check(cr, 1, [mid])
798 status['progress'] = (float(statusi)+0.4) / len(graph)
801 if hasattr(package, 'init') or package.state == 'to install':
804 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
805 if package.state=='to upgrade':
806 # upgrading the module information
807 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
808 load_init_xml(cr, m, idref, mode)
809 load_update_xml(cr, m, idref, mode)
810 load_data(cr, m, idref, mode)
811 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
812 status['progress'] = (float(statusi)+0.75) / len(graph)
813 load_demo_xml(cr, m, idref, mode)
814 load_demo(cr, m, idref, mode)
815 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
817 # launch tests only in demo mode, as most tests will depend
818 # on demo data. Other tests can be added into the regular
819 # 'data' section, but should probably not alter the data,
820 # as there is no rollback.
821 load_test(cr, m, idref, mode)
823 processed_modules.append(package.name)
825 migrations.migrate_module(package, 'post')
828 ver = release.major_version + '.' + package.data.get('version', '1.0')
829 # Set new modules and dependencies
830 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
832 # Update translations for all installed languages
833 modobj.update_translations(cr, 1, [mid], None, {'overwrite': tools.config['overwrite_existing_translations']})
836 package.state = 'installed'
837 for kind in ('init', 'demo', 'update'):
838 if hasattr(package, kind):
839 delattr(package, kind)
845 return processed_modules
847 def _check_module_names(cr, module_names):
848 mod_names = set(module_names)
849 if 'base' in mod_names:
850 # ignore dummy 'all' module
851 if 'all' in mod_names:
852 mod_names.remove('all')
854 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
855 if cr.dictfetchone()['count'] != len(mod_names):
856 # find out what module name(s) are incorrect:
857 cr.execute("SELECT name FROM ir_module_module")
858 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
859 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
861 def load_modules(db, force_demo=False, status=None, update_module=False):
863 initialize_sys_path()
865 open_openerp_namespace()
871 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
872 if len(cr.fetchall())==0:
873 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
875 tools.config["init"]["all"] = 1
876 tools.config['update']['all'] = 1
877 if not tools.config['without_demo']:
878 tools.config["demo"]['all'] = 1
883 # This is a brand new pool, just created in pooler.get_db_and_pool()
884 pool = pooler.get_pool(cr.dbname)
887 processed_modules = []
888 report = tools.assertion_report()
889 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
890 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
891 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
892 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
894 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
895 graph = create_graph(cr, ['base'], force)
897 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
898 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
899 processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
901 if tools.config['load_language']:
902 for lang in tools.config['load_language'].split(','):
903 tools.load_language(cr, lang)
905 # STEP 2: Mark other modules to be loaded/updated
907 modobj = pool.get('ir.module.module')
908 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
909 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
910 modobj.update_list(cr, 1)
912 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
914 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
916 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
918 modobj.button_install(cr, 1, ids)
920 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
922 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
924 modobj.button_upgrade(cr, 1, ids)
926 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
928 STATES_TO_LOAD += ['to install']
931 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
935 if loop_guardrail > 100:
936 raise ValueError('Possible recursive module tree detected, aborting.')
937 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
939 module_list = [name for (name,) in cr.fetchall() if name not in graph]
943 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
944 if new_modules_in_graph == 0:
948 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
949 processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
952 cr.execute('select model from ir_model where state=%s', ('manual',))
953 for model in cr.dictfetchall():
954 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
956 # STEP 4: Finish and cleanup
957 if processed_modules:
958 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
959 for (model, name) in cr.fetchall():
960 model_obj = pool.get(model)
961 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
962 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
964 # Temporary warning while we remove access rights on osv_memory objects, as they have
965 # been replaced by owner-only access rights
966 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
967 for (model, name) in cr.fetchall():
968 model_obj = pool.get(model)
969 if isinstance(model_obj, osv.osv.osv_memory):
970 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
972 cr.execute("SELECT model from ir_model")
973 for (model,) in cr.fetchall():
974 obj = pool.get(model)
976 obj._check_removed_columns(cr, log=True)
978 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
980 # Cleanup orphan records
981 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
983 if report.get_report():
984 logger.notifyChannel('init', netsvc.LOG_INFO, report)
986 for kind in ('init', 'demo', 'update'):
987 tools.config[kind] = {}
991 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
992 for mod_id, mod_name in cr.fetchall():
993 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
994 for rmod, rid in cr.fetchall():
996 rmod_module= pool.get(rmod)
998 rmod_module.unlink(cr, uid, [rid])
1000 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
1001 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
1004 # TODO: remove menu without actions of children
1007 cr.execute('''delete from
1010 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
1012 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
1014 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
1019 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
1021 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
1027 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: