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_data(cr, m, idref, mode, 'init_xml')
691 def load_update_xml(cr, m, idref, mode):
692 _load_data(cr, m, idref, mode, 'update_xml')
694 def load_demo_xml(cr, m, idref, mode):
695 _load_data(cr, m, idref, mode, 'demo_xml')
697 def load_data(cr, module_name, idref, mode):
698 _load_data(cr, module_name, idref, mode, 'data')
700 def load_demo(cr, module_name, idref, mode):
701 _load_data(cr, module_name, idref, mode, 'demo')
703 def load_test(cr, module_name, idref, mode):
705 if not tools.config.options['test_disable']:
707 _load_data(cr, module_name, idref, 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_data(cr, module_name, idref, mode, kind):
719 kind: data, demo, test, init_xml, update_xml, demo_xml.
721 noupdate is False, unless it is demo data or it is csv data in
725 for filename in package.data.get(kind, []):
726 log = logging.getLogger('init')
727 log.info("module %s: loading %s", module_name, filename)
728 _, ext = os.path.splitext(filename)
729 pathname = os.path.join(module_name, filename)
730 fp = tools.file_open(pathname)
732 if kind in ('demo', 'demo_xml'):
736 if kind in ('init', 'init_xml'):
738 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
740 process_sql_file(cr, fp)
742 tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
744 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
751 status = status.copy()
752 processed_modules = []
754 pool = pooler.get_pool(cr.dbname)
755 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 models = 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_models(cr, package.name, models)
769 modobj = pool.get('ir.module.module')
770 for package in graph:
771 status['progress'] = (float(statusi)+0.1) / len(graph)
775 if skip_modules and m in skip_modules:
779 modobj.check(cr, 1, [mid])
782 status['progress'] = (float(statusi)+0.4) / len(graph)
785 if hasattr(package, 'init') or package.state == 'to install':
788 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
789 if package.state=='to upgrade':
790 # upgrading the module information
791 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
792 load_init_xml(cr, m, idref, mode)
793 load_update_xml(cr, m, idref, mode)
794 load_data(cr, m, idref, mode)
795 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
796 status['progress'] = (float(statusi)+0.75) / len(graph)
797 load_demo_xml(cr, m, idref, mode)
798 load_demo(cr, m, idref, mode)
799 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
801 # launch tests only in demo mode, as most tests will depend
802 # on demo data. Other tests can be added into the regular
803 # 'data' section, but should probably not alter the data,
804 # as there is no rollback.
805 load_test(cr, m, idref, mode)
807 processed_modules.append(package.name)
809 migrations.migrate_module(package, 'post')
811 ver = release.major_version + '.' + package.data.get('version', '1.0')
812 # Set new modules and dependencies
813 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
815 # Update translations for all installed languages
816 modobj.update_translations(cr, 1, [mid], None)
819 package.state = 'installed'
820 for kind in ('init', 'demo', 'update'):
821 if hasattr(package, kind):
822 delattr(package, kind)
828 return processed_modules
830 def _check_module_names(cr, module_names):
831 mod_names = set(module_names)
832 if 'base' in mod_names:
833 # ignore dummy 'all' module
834 if 'all' in mod_names:
835 mod_names.remove('all')
837 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
838 if cr.dictfetchone()['count'] != len(mod_names):
839 # find out what module name(s) are incorrect:
840 cr.execute("SELECT name FROM ir_module_module")
841 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
842 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
844 def load_modules(db, force_demo=False, status=None, update_module=False):
846 initialize_sys_path()
848 open_openerp_namespace()
854 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
855 if len(cr.fetchall())==0:
856 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
858 tools.config["init"]["all"] = 1
859 tools.config['update']['all'] = 1
860 if not tools.config['without_demo']:
861 tools.config["demo"]['all'] = 1
866 # This is a brand new pool, just created in pooler.get_db_and_pool()
867 pool = pooler.get_pool(cr.dbname)
870 processed_modules = []
871 report = tools.assertion_report()
872 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
873 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
874 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
875 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
877 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
878 graph = create_graph(cr, ['base'], force)
880 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
881 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
882 processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
884 if tools.config['load_language']:
885 for lang in tools.config['load_language'].split(','):
886 tools.load_language(cr, lang)
888 # STEP 2: Mark other modules to be loaded/updated
890 modobj = pool.get('ir.module.module')
891 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
892 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
893 modobj.update_list(cr, 1)
895 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
897 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
899 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
901 modobj.button_install(cr, 1, ids)
903 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
905 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
907 modobj.button_upgrade(cr, 1, ids)
909 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
911 STATES_TO_LOAD += ['to install']
914 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
918 if loop_guardrail > 100:
919 raise ValueError('Possible recursive module tree detected, aborting.')
920 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
922 module_list = [name for (name,) in cr.fetchall() if name not in graph]
926 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
927 if new_modules_in_graph == 0:
931 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
932 processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
935 cr.execute('select model from ir_model where state=%s', ('manual',))
936 for model in cr.dictfetchall():
937 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
939 # STEP 4: Finish and cleanup
940 if processed_modules:
941 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
942 for (model, name) in cr.fetchall():
943 model_obj = pool.get(model)
944 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
945 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
947 # Temporary warning while we remove access rights on osv_memory objects, as they have
948 # been replaced by owner-only access rights
949 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
950 for (model, name) in cr.fetchall():
951 model_obj = pool.get(model)
952 if isinstance(model_obj, osv.osv.osv_memory):
953 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
955 cr.execute("SELECT model from ir_model")
956 for (model,) in cr.fetchall():
957 obj = pool.get(model)
959 obj._check_removed_columns(cr, log=True)
961 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
963 # Cleanup orphan records
964 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
966 if report.get_report():
967 logger.notifyChannel('init', netsvc.LOG_INFO, report)
969 for kind in ('init', 'demo', 'update'):
970 tools.config[kind] = {}
974 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
975 for mod_id, mod_name in cr.fetchall():
976 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
977 for rmod, rid in cr.fetchall():
979 rmod_module= pool.get(rmod)
981 rmod_module.unlink(cr, uid, [rid])
983 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
984 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
987 # TODO: remove menu without actions of children
990 cr.execute('''delete from
993 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
995 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
997 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
1002 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
1004 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
1010 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: