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
42 from zipfile import PyZipFile, ZIP_DEFLATED
43 from cStringIO import StringIO
48 logger = netsvc.Logger()
50 _ad = os.path.abspath(opj(tools.config['root_path'], 'addons')) # default addons path (base)
51 ad_paths= map(lambda m: os.path.abspath(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)
228 archive.writepy(directory)
229 _zippy(archive, directory, src=src)
231 archive_data = archname.getvalue()
235 return base64.encodestring(archive_data)
239 def get_module_as_zip(modulename, b64enc=True, src=True):
240 """Generate a module as zip file with the source or not and can do a base64 encoding
242 @param modulename: The module name
243 @param b64enc: if True the function will encode the zip file with base64
244 @param src: Integrate the source files
246 @return: a stream to store in a file-like object
249 ap = get_module_path(str(modulename))
251 raise Exception('Unable to find path for module %s' % modulename)
253 ap = ap.encode('utf8')
254 if os.path.isfile(ap + '.zip'):
255 val = file(ap + '.zip', 'rb').read()
257 val = base64.encodestring(val)
259 val = zip_directory(ap, b64enc, src)
264 def get_module_resource(module, *args):
265 """Return the full path of a resource of the given module.
267 @param module: the module
268 @param args: the resource path components
270 @return: absolute path to the resource
272 a = get_module_path(module)
273 res = a and opj(a, *args) or False
274 if zipfile.is_zipfile( a +'.zip') :
275 zip = zipfile.ZipFile( a + ".zip")
276 files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
280 elif os.path.isfile(res):
287 """Returns the list of module names
291 name = os.path.basename(name)
292 if name[-4:] == '.zip':
296 def is_really_module(name):
297 name = opj(dir, name)
298 return os.path.isdir(name) or zipfile.is_zipfile(name)
299 return map(clean, filter(is_really_module, os.listdir(dir)))
303 plist.extend(listdir(ad))
304 return list(set(plist))
306 def load_information_from_description_file(module):
308 :param module: The name of the module (sale, purchase, ...)
311 for filename in ['__openerp__.py', '__terp__.py']:
312 description_file = get_module_resource(module, filename)
313 if description_file :
314 return eval(tools.file_open(description_file).read())
316 #TODO: refactor the logger in this file to follow the logging guidelines
318 logging.getLogger('addons').debug('The module %s does not contain a description file:'\
319 '__openerp__.py or __terp__.py (deprecated)', module)
322 def get_modules_with_version():
323 modules = get_modules()
325 for module in modules:
327 info = load_information_from_description_file(module)
328 res[module] = "%s.%s" % (release.major_version, info['version'])
333 def create_graph(cr, module_list, force=None):
335 upgrade_graph(graph, cr, module_list, force)
338 def upgrade_graph(graph, cr, module_list, force=None):
342 len_graph = len(graph)
343 for module in module_list:
344 mod_path = get_module_path(module)
345 terp_file = get_module_resource(module, '__openerp__.py')
347 terp_file = get_module_resource(module, '__terp__.py')
348 if not mod_path or not terp_file:
349 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not found, skipped' % (module))
352 if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
354 info = eval(tools.file_open(terp_file).read())
356 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
358 if info.get('installable', True):
359 packages.append((module, info.get('depends', []), info))
361 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
363 dependencies = dict([(p, deps) for p, deps, data in packages])
364 current, later = set([p for p, dep, data in packages]), set()
366 while packages and current > later:
367 package, deps, data = packages[0]
369 # if all dependencies of 'package' are already in the graph, add 'package' in the graph
370 if reduce(lambda x, y: x and y in graph, deps, True):
371 if not package in current:
375 current.remove(package)
376 graph.addNode(package, deps)
377 node = Node(package, graph)
379 for kind in ('init', 'demo', 'update'):
380 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
381 setattr(node, kind, True)
384 packages.append((package, deps, data))
387 graph.update_from_db(cr)
389 for package in later:
390 unmet_deps = filter(lambda p: p not in graph, dependencies[package])
391 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
393 result = len(graph) - len_graph
394 if result != len(module_list):
395 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
399 def init_module_objects(cr, module_name, obj_list):
400 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
404 result = obj._auto_init(cr, {'module': module_name})
409 if hasattr(obj, 'init'):
418 def register_class(m):
420 Register module named m, if not already registered
424 mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
425 msg = "Couldn't load %smodule %s" % (mt, m)
426 logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
427 logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
432 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
433 mod_path = get_module_path(m)
436 zip_mod_path = mod_path + '.zip'
437 if not os.path.isfile(zip_mod_path):
438 fm = imp.find_module(m, ad_paths)
440 imp.load_module(m, *fm)
445 zimp = zipimport.zipimporter(zip_mod_path)
454 class MigrationManager(object):
456 This class manage the migration of modules
457 Migrations files must be python files containing a "migrate(cr, installed_version)" function.
458 Theses files must respect a directory tree structure: A 'migrations' folder which containt a
459 folder by version. Version can be 'module' version or 'server.module' version (in this case,
460 the files will only be processed by this version of the server). Python file names must start
461 by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
467 | |-- pre-update_table_x.py
468 | |-- pre-update_table_y.py
469 | |-- post-clean-data.py
470 | `-- README.txt # not processed
471 |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
472 | |-- pre-delete_table_z.py
473 | `-- post-clean-data.py
474 `-- foo.py # not processed
476 This similar structure is generated by the maintenance module with the migrations files get by
477 the maintenance contract
480 def __init__(self, cr, graph):
486 def _get_files(self):
489 import addons.base.maintenance.utils as maintenance_utils
490 maintenance_utils.update_migrations_files(self.cr)
493 for pkg in self.graph:
494 self.migrations[pkg.name] = {}
495 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
498 self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
499 self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
501 def migrate_module(self, pkg, stage):
502 assert stage in ('pre', 'post')
503 stageformat = {'pre': '[>%s]',
507 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
510 def convert_version(version):
511 if version.startswith(release.major_version) and version != release.major_version:
512 return version # the version number already containt the server version
513 return "%s.%s" % (release.major_version, version)
515 def _get_migration_versions(pkg):
517 return [d for d in tree if tree[d] is not None]
520 __get_dir(self.migrations[pkg.name]['module']) +
521 __get_dir(self.migrations[pkg.name]['maintenance'])
523 versions.sort(key=lambda k: parse_version(convert_version(k)))
526 def _get_migration_files(pkg, version, stage):
527 """ return a list of tuple (module, file)
529 m = self.migrations[pkg.name]
532 mapping = {'module': opj(pkg.name, 'migrations'),
533 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
536 for x in mapping.keys():
538 for f in m[x][version]:
539 if m[x][version][f] is not None:
541 if not f.startswith(stage + '-'):
543 lst.append(opj(mapping[x], version, f))
552 from tools.parse_version import parse_version
554 parsed_installed_version = parse_version(pkg.installed_version or '')
555 current_version = parse_version(convert_version(pkg.data.get('version', '0')))
557 versions = _get_migration_versions(pkg)
559 for version in versions:
560 if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
562 strfmt = {'addon': pkg.name,
564 'version': stageformat[stage] % version,
567 for pyfile in _get_migration_files(pkg, version, stage):
568 name, ext = os.path.splitext(os.path.basename(pyfile))
569 if ext.lower() != '.py':
571 mod = fp = fp2 = None
573 fp = tools.file_open(pyfile)
575 # imp.load_source need a real file object, so we create
576 # one from the file-like object we get from file_open
581 mod = imp.load_source(name, pyfile, fp2)
582 logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
583 mod.migrate(self.cr, pkg.installed_version)
585 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
587 except AttributeError:
588 logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
599 log = logging.getLogger('init')
601 def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
603 def process_sql_file(cr, fp):
604 queries = fp.read().split(';')
605 for query in queries:
606 new_query = ' '.join(query.split())
608 cr.execute(new_query)
610 def load_init_update_xml(cr, m, idref, mode, kind):
611 for filename in package.data.get('%s_xml' % kind, []):
612 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
613 _, ext = os.path.splitext(filename)
614 fp = tools.file_open(opj(m, filename))
616 noupdate = (kind == 'init')
617 tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
619 process_sql_file(cr, fp)
621 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
623 tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
626 def load_demo_xml(cr, m, idref, mode):
627 for xml in package.data.get('demo_xml', []):
628 name, ext = os.path.splitext(xml)
629 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
630 fp = tools.file_open(opj(m, xml))
632 tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
634 tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
636 tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
639 def load_data(cr, module_name, id_map, mode):
640 _load_data(cr, module_name, id_map, mode, 'data')
642 def load_demo(cr, module_name, id_map, mode):
643 _load_data(cr, module_name, id_map, mode, 'demo')
645 def load_test(cr, module_name, id_map, mode):
647 if not tools.config.options['test_disable']:
649 _load_data(cr, module_name, id_map, mode, 'test')
651 logger.notifyChannel('ERROR', netsvc.LOG_TEST, e)
654 if tools.config.options['test_commit']:
659 def _load_data(cr, module_name, id_map, mode, kind):
660 noupdate = (kind == 'demo')
661 for filename in package.data.get(kind, []):
662 _, ext = os.path.splitext(filename)
663 log.info("module %s: loading %s", module_name, filename)
664 pathname = os.path.join(module_name, filename)
665 file = tools.file_open(pathname)
666 # TODO manage .csv file with noupdate == (kind == 'init')
668 process_sql_file(cr, file)
670 noupdate = (kind == 'init')
671 tools.convert_csv_import(cr, module_name, pathname, file.read(), id_map, mode, noupdate)
673 tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate)
675 tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
678 # **kwargs is passed directly to convert_xml_import
682 status = status.copy()
685 pool = pooler.get_pool(cr.dbname)
687 migrations = MigrationManager(cr, graph)
692 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
694 for package in graph:
695 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
696 migrations.migrate_module(package, 'pre')
697 register_class(package.name)
698 modules = pool.instanciate(package.name, cr)
699 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
700 init_module_objects(cr, package.name, modules)
703 for package in graph:
704 status['progress'] = (float(statusi)+0.1) / len(graph)
709 modobj = pool.get('ir.module.module')
711 if modobj and perform_checks:
712 modobj.check(cr, 1, [mid])
715 status['progress'] = (float(statusi)+0.4) / len(graph)
718 if hasattr(package, 'init') or package.state == 'to install':
721 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
723 for kind in ('init', 'update'):
724 if package.state=='to upgrade':
725 # upgrading the module information
726 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
727 load_init_update_xml(cr, m, idref, mode, kind)
728 load_data(cr, m, idref, mode)
729 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
730 status['progress'] = (float(statusi)+0.75) / len(graph)
731 load_demo_xml(cr, m, idref, mode)
732 load_demo(cr, m, idref, mode)
733 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
735 # launch tests only in demo mode, as most tests will depend
736 # on demo data. Other tests can be added into the regular
737 # 'data' section, but should probably not alter the data,
738 # as there is no rollback.
739 load_test(cr, m, idref, mode)
741 package_todo.append(package.name)
743 migrations.migrate_module(package, 'post')
746 ver = release.major_version + '.' + package.data.get('version', '1.0')
747 # Set new modules and dependencies
748 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
750 # Update translations for all installed languages
751 modobj.update_translations(cr, 1, [mid], None)
754 package.state = 'installed'
755 for kind in ('init', 'demo', 'update'):
756 if hasattr(package, kind):
757 delattr(package, kind)
761 cr.execute('select model from ir_model where state=%s', ('manual',))
762 for model in cr.dictfetchall():
763 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
765 pool.get('ir.model.data')._process_end(cr, 1, package_todo)
770 def _check_module_names(cr, module_names):
771 mod_names = set(module_names)
772 if 'base' in mod_names:
773 # ignore dummy 'all' module
774 if 'all' in mod_names:
775 mod_names.remove('all')
777 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
778 if cr.dictfetchone()['count'] != len(mod_names):
779 # find out what module name(s) are incorrect:
780 cr.execute("SELECT name FROM ir_module_module")
781 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
782 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
784 def load_modules(db, force_demo=False, status=None, update_module=False):
789 cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
790 if len(cr.fetchall())==0:
791 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
793 tools.config["init"]["all"] = 1
794 tools.config['update']['all'] = 1
795 if not tools.config['without_demo']:
796 tools.config["demo"]['all'] = 1
800 pool = pooler.get_pool(cr.dbname)
802 report = tools.assertion_report()
803 # NOTE: Try to also load the modules that have been marked as uninstallable previously...
804 STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
805 graph = create_graph(cr, ['base'], force)
807 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
808 raise osv.osv.except_osv('Could not load base module', 'module base cannot be loaded! (hint: verify addons-path)')
809 has_updates = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
812 modobj = pool.get('ir.module.module')
813 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
814 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
815 modobj.update_list(cr, 1)
817 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
819 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
821 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
823 modobj.button_install(cr, 1, ids)
825 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
827 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
829 modobj.button_upgrade(cr, 1, ids)
831 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
833 STATES_TO_LOAD += ['to install']
838 if loop_guardrail > 100:
839 raise ValueError('Possible recursive module tree detected, aborting.')
840 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
842 module_list = [name for (name,) in cr.fetchall() if name not in graph]
846 new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
847 if new_modules_in_graph == 0:
851 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
852 r = load_module_graph(cr, graph, status, report=report)
853 has_updates = has_updates or r
856 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
857 for (model, name) in cr.fetchall():
858 model_obj = pool.get(model)
859 if not isinstance(model_obj, osv.osv.osv_memory):
860 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
862 # Temporary warning while we remove access rights on osv_memory objects, as they have
863 # been replaced by owner-only access rights
864 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
865 for (model, name) in cr.fetchall():
866 model_obj = pool.get(model)
867 if isinstance(model_obj, osv.osv.osv_memory):
868 logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
870 cr.execute("SELECT model from ir_model")
871 for (model,) in cr.fetchall():
872 obj = pool.get(model)
874 obj._check_removed_columns(cr, log=True)
876 if report.get_report():
877 logger.notifyChannel('init', netsvc.LOG_INFO, report)
879 for kind in ('init', 'demo', 'update'):
880 tools.config[kind] = {}
884 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
885 for mod_id, mod_name in cr.fetchall():
886 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
887 for rmod, rid in cr.fetchall():
889 rmod_module= pool.get(rmod)
891 rmod_module.unlink(cr, uid, [rid])
893 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
894 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
897 # TODO: remove menu without actions of children
900 cr.execute('''delete from
903 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
905 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
907 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
912 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
914 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
920 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: