[IMP] Added YAML for demo data.
[odoo/odoo.git] / bin / addons / __init__.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
19 #
20 ##############################################################################
21
22 import os, sys, imp
23 from os.path import join as opj
24 import itertools
25 import zipimport
26
27 import osv
28 import tools
29 import tools.osutil
30 import pooler
31
32
33 import netsvc
34 from osv import fields
35
36 import zipfile
37 import release
38
39 import re
40 import base64
41 from zipfile import PyZipFile, ZIP_DEFLATED
42 from cStringIO import StringIO
43
44
45 logger = netsvc.Logger()
46
47 _ad = os.path.abspath(opj(tools.config['root_path'], 'addons'))     # default addons path (base)
48 ad_paths= map(lambda m: os.path.abspath(m.strip()),tools.config['addons_path'].split(','))
49
50 sys.path.insert(1, _ad)
51
52 ad_cnt=1
53 for adp in ad_paths:
54     if adp != _ad:
55         sys.path.insert(ad_cnt, adp)
56         ad_cnt+=1
57
58 ad_paths.append(_ad)    # for get_module_path
59
60 # Modules already loaded
61 loaded = []
62
63 #Modules whch raised error
64 not_loaded = []
65
66 class Graph(dict):
67
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:
72                 father = n
73                 max_depth = n.depth
74         if father:
75             father.addChild(name)
76         else:
77             Node(name, self)
78
79     def update_from_db(self, cr):
80         # update the graph with values from the database (if exist)
81         ## First, we set the default values for each package in graph
82         additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
83         ## Then we get the values from the database
84         cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
85                    '  FROM ir_module_module'
86                    ' WHERE name in (%s)' % (','.join(['%s'] * len(self))),
87                     additional_data.keys()
88                    )
89
90         ## and we update the default values with values from the database
91         additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
92         
93         for package in self.values():
94             for k, v in additional_data[package.name].items():
95                 setattr(package, k, v)
96
97
98
99     def __iter__(self):
100         level = 0
101         done = set(self.keys())
102         while done:
103             level_modules = [(name, module) for name, module in self.items() if module.depth==level]
104             for name, module in level_modules:
105                 done.remove(name)
106                 yield module
107             level += 1
108
109 class Singleton(object):
110     def __new__(cls, name, graph):
111         if name in graph:
112             inst = graph[name]
113         else:
114             inst = object.__new__(cls)
115             inst.name = name
116             graph[name] = inst
117         return inst
118
119
120 class Node(Singleton):
121
122     def __init__(self, name, graph):
123         self.graph = graph
124         if not hasattr(self, 'children'):
125             self.children = []
126         if not hasattr(self, 'depth'):
127             self.depth = 0
128
129     def addChild(self, name):
130         node = Node(name, self.graph)
131         node.depth = self.depth + 1
132         if node not in self.children:
133             self.children.append(node)
134         for attr in ('init', 'update', 'demo'):
135             if hasattr(self, attr):
136                 setattr(node, attr, True)
137         self.children.sort(lambda x, y: cmp(x.name, y.name))
138
139     def __setattr__(self, name, value):
140         super(Singleton, self).__setattr__(name, value)
141         if name in ('init', 'update', 'demo'):
142             tools.config[name][self.name] = 1
143             for child in self.children:
144                 setattr(child, name, value)
145         if name == 'depth':
146             for child in self.children:
147                 setattr(child, name, value + 1)
148
149     def __iter__(self):
150         return itertools.chain(iter(self.children), *map(iter, self.children))
151
152     def __str__(self):
153         return self._pprint()
154
155     def _pprint(self, depth=0):
156         s = '%s\n' % self.name
157         for c in self.children:
158             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
159         return s
160
161
162 def get_module_path(module, downloaded=False):
163     """Return the path of the given module."""
164     for adp in ad_paths:
165         if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
166             return opj(adp, module)
167
168     if downloaded:
169         return opj(_ad, module)
170     logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,))
171     return False
172
173
174 def get_module_filetree(module, dir='.'):
175     path = get_module_path(module)
176     if not path:
177         return False
178
179     dir = os.path.normpath(dir)
180     if dir == '.':
181         dir = ''
182     if dir.startswith('..') or (dir and dir[0] == '/'):
183         raise Exception('Cannot access file outside the module')
184
185     if not os.path.isdir(path):
186         # zipmodule
187         zip = zipfile.ZipFile(path + ".zip")
188         files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()]
189     else:
190         files = tools.osutil.listdir(path, True)
191
192     tree = {}
193     for f in files:
194         if not f.startswith(dir):
195             continue
196
197         if dir:
198             f = f[len(dir)+int(not dir.endswith('/')):]
199         lst = f.split(os.sep)
200         current = tree
201         while len(lst) != 1:
202             current = current.setdefault(lst.pop(0), {})
203         current[lst.pop(0)] = None
204
205     return tree
206
207 def get_module_as_zip_from_module_directory(module_directory, b64enc=True, src=True):
208     """Compress a module directory
209
210     @param module_directory: The module directory
211     @param base64enc: if True the function will encode the zip file with base64
212     @param src: Integrate the source files
213
214     @return: a stream to store in a file-like object
215     """
216
217     RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I)
218
219     def _zippy(archive, path, src=True):
220         path = os.path.abspath(path)
221         base = os.path.basename(path)
222         for f in tools.osutil.listdir(path, True):
223             bf = os.path.basename(f)
224             if not RE_exclude.search(bf) and (src or bf == '__terp__.py' or not bf.endswith('.py')):
225                 archive.write(os.path.join(path, f), os.path.join(base, f))
226
227     archname = StringIO()
228     archive = PyZipFile(archname, "w", ZIP_DEFLATED)
229     archive.writepy(module_directory)
230     _zippy(archive, module_directory, src=src)
231     archive.close()
232     val = archname.getvalue()
233     archname.close()
234
235     if b64enc:
236         val = base64.encodestring(val)
237
238     return val
239
240 def get_module_as_zip(modulename, b64enc=True, src=True):
241     """Generate a module as zip file with the source or not and can do a base64 encoding
242
243     @param modulename: The module name
244     @param b64enc: if True the function will encode the zip file with base64
245     @param src: Integrate the source files
246
247     @return: a stream to store in a file-like object
248     """
249
250     ap = get_module_path(str(modulename))
251     if not ap:
252         raise Exception('Unable to find path for module %s' % modulename)
253
254     ap = ap.encode('utf8')
255     if os.path.isfile(ap + '.zip'):
256         val = file(ap + '.zip', 'rb').read()
257         if b64enc:
258             val = base64.encodestring(val)
259     else:
260         val = get_module_as_zip_from_module_directory(ap, b64enc, src)
261
262     return val
263
264
265 def get_module_resource(module, *args):
266     """Return the full path of a resource of the given module.
267
268     @param module: the module
269     @param args: the resource path components
270
271     @return: absolute path to the resource
272     """
273     a = get_module_path(module)
274     return a and opj(a, *args) or False
275
276
277 def get_modules():
278     """Returns the list of module names
279     """
280     def listdir(dir):
281         def clean(name):
282             name = os.path.basename(name)
283             if name[-4:] == '.zip':
284                 name = name[:-4]
285             return name
286
287         def is_really_module(name):
288             name = opj(dir, name)
289             return os.path.isdir(name) or zipfile.is_zipfile(name)
290         return map(clean, filter(is_really_module, os.listdir(dir)))
291
292     plist = []
293     for ad in ad_paths:
294         plist.extend(listdir(ad))
295     return list(set(plist))
296
297 def get_modules_with_version():
298     modules = get_modules()
299     res = {}
300     for module in modules:
301         terp = get_module_resource(module, '__terp__.py')
302         try:
303             info = eval(tools.file_open(terp).read())
304             res[module] = "%s.%s" % (release.major_version, info['version'])
305         except Exception, e:
306             continue
307     return res
308
309 def create_graph(cr, module_list, force=None):
310     graph = Graph()
311     upgrade_graph(graph, cr, module_list, force)
312     return graph
313
314 def upgrade_graph(graph, cr, module_list, force=None):
315     if force is None:
316         force = []
317     packages = []
318     len_graph = len(graph)
319     for module in module_list:
320         mod_path = get_module_path(module)
321         terp_file = get_module_resource(module, '__terp__.py')
322         if not mod_path or not terp_file:
323             global not_loaded
324             not_loaded.append(module)
325             logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable' % (module))
326             raise osv.osv.except_osv('Error!',"Module '%s' was not found" % (module,))
327
328         if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'):
329             try:
330                 info = eval(tools.file_open(terp_file).read())
331             except:
332                 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: eval file %s' % (module, terp_file))
333                 raise
334             if info.get('installable', True):
335                 packages.append((module, info.get('depends', []), info))
336
337
338     dependencies = dict([(p, deps) for p, deps, data in packages])
339     current, later = set([p for p, dep, data in packages]), set()
340     
341     while packages and current > later:
342         package, deps, data = packages[0]
343
344         # if all dependencies of 'package' are already in the graph, add 'package' in the graph
345         if reduce(lambda x, y: x and y in graph, deps, True):
346             if not package in current:
347                 packages.pop(0)
348                 continue
349             later.clear()
350             current.remove(package)
351             graph.addNode(package, deps)
352             node = Node(package, graph)
353             node.data = data
354             for kind in ('init', 'demo', 'update'):
355                 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
356                     setattr(node, kind, True)
357         else:
358             later.add(package)
359             packages.append((package, deps, data))
360         packages.pop(0)
361
362     graph.update_from_db(cr)
363
364     for package in later:
365         unmet_deps = filter(lambda p: p not in graph, dependencies[package])
366         logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
367
368     result = len(graph) - len_graph
369     if result != len(module_list):
370         logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
371     return result
372
373
374 def init_module_objects(cr, module_name, obj_list):
375     logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
376     todo = []
377     for obj in obj_list:
378         try:
379             result = obj._auto_init(cr, {'module': module_name})
380         except Exception, e:
381             raise 
382         if result:
383             todo += result
384         if hasattr(obj, 'init'):
385             obj.init(cr)
386         cr.commit()
387     todo.sort()
388     for t in todo:
389         t[1](cr, *t[2])
390     cr.commit()
391
392
393 def register_class(m):
394     """
395     Register module named m, if not already registered
396     """
397
398     def log(e):
399         mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or ''
400         msg = "Couldn't load %smodule %s" % (mt, m)
401         logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
402         logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
403
404     global loaded
405     if m in loaded:
406         return
407     logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: registering objects' % m)
408     mod_path = get_module_path(m)
409
410     try:
411         zip_mod_path = mod_path + '.zip'
412         if not os.path.isfile(zip_mod_path):
413             fm = imp.find_module(m, ad_paths)
414             try:
415                 imp.load_module(m, *fm)
416             finally:
417                 if fm[0]:
418                     fm[0].close()
419         else:
420             zimp = zipimport.zipimporter(zip_mod_path)
421             zimp.load_module(m)
422     except Exception, e:
423         log(e)
424         raise
425     else:
426         loaded.append(m)
427
428
429 class MigrationManager(object):
430     """
431         This class manage the migration of modules
432         Migrations files must be python files containing a "migrate(cr, installed_version)" function.
433         Theses files must respect a directory tree structure: A 'migrations' folder which containt a
434         folder by version. Version can be 'module' version or 'server.module' version (in this case,
435         the files will only be processed by this version of the server). Python file names must start
436         by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
437         Example:
438
439             <moduledir>
440             `-- migrations
441                 |-- 1.0
442                 |   |-- pre-update_table_x.py
443                 |   |-- pre-update_table_y.py
444                 |   |-- post-clean-data.py
445                 |   `-- README.txt              # not processed
446                 |-- 5.0.1.1                     # files in this folder will be executed only on a 5.0 server
447                 |   |-- pre-delete_table_z.py
448                 |   `-- post-clean-data.py
449                 `-- foo.py                      # not processed
450
451         This similar structure is generated by the maintenance module with the migrations files get by
452         the maintenance contract
453
454     """
455     def __init__(self, cr, graph):
456         self.cr = cr
457         self.graph = graph
458         self.migrations = {}
459         self._get_files()
460
461     def _get_files(self):
462
463         """
464         import addons.base.maintenance.utils as maintenance_utils
465         maintenance_utils.update_migrations_files(self.cr)
466         #"""
467
468         for pkg in self.graph:
469             self.migrations[pkg.name] = {}
470             if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
471                 continue
472
473             self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
474             self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
475
476     def migrate_module(self, pkg, stage):
477         assert stage in ('pre', 'post')
478         stageformat = {'pre': '[>%s]',
479                        'post': '[%s>]',
480                       }
481
482         if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
483             return
484
485         def convert_version(version):
486             if version.startswith(release.major_version) and version != release.major_version:
487                 return version  # the version number already containt the server version
488             return "%s.%s" % (release.major_version, version)
489
490         def _get_migration_versions(pkg):
491             def __get_dir(tree):
492                 return [d for d in tree if tree[d] is not None]
493
494             versions = list(set(
495                 __get_dir(self.migrations[pkg.name]['module']) +
496                 __get_dir(self.migrations[pkg.name]['maintenance'])
497             ))
498             versions.sort(key=lambda k: parse_version(convert_version(k)))
499             return versions
500
501         def _get_migration_files(pkg, version, stage):
502             """ return a list of tuple (module, file)
503             """
504             m = self.migrations[pkg.name]
505             lst = []
506
507             mapping = {'module': opj(pkg.name, 'migrations'),
508                        'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
509                       }
510
511             for x in mapping.keys():
512                 if version in m[x]:
513                     for f in m[x][version]:
514                         if m[x][version][f] is not None:
515                             continue
516                         if not f.startswith(stage + '-'):
517                             continue
518                         lst.append(opj(mapping[x], version, f))
519             lst.sort()
520             return lst
521
522         def mergedict(a, b):
523             a = a.copy()
524             a.update(b)
525             return a
526
527         from tools.parse_version import parse_version
528
529         parsed_installed_version = parse_version(pkg.installed_version or '')
530         current_version = parse_version(convert_version(pkg.data.get('version', '0')))
531
532         versions = _get_migration_versions(pkg)
533
534         for version in versions:
535             if parsed_installed_version < parse_version(convert_version(version)) <= current_version:
536
537                 strfmt = {'addon': pkg.name,
538                           'stage': stage,
539                           'version': stageformat[stage] % version,
540                           }
541
542                 for pyfile in _get_migration_files(pkg, version, stage):
543                     name, ext = os.path.splitext(os.path.basename(pyfile))
544                     if ext.lower() != '.py':
545                         continue
546                     mod = fp = fp2 = None
547                     try:
548                         fp = tools.file_open(pyfile)
549
550                         # imp.load_source need a real file object, so we create
551                         # one from the file-like object we get from file_open
552                         fp2 = os.tmpfile()
553                         fp2.write(fp.read())
554                         fp2.seek(0)
555                         try:
556                             mod = imp.load_source(name, pyfile, fp2)
557                             logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt))
558                             mod.migrate(self.cr, pkg.installed_version)
559                         except ImportError:
560                             logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt))
561                             raise
562                         except AttributeError:
563                             logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt)
564                         except:
565                             raise
566                     finally:
567                         if fp:
568                             fp.close()
569                         if fp2:
570                             fp2.close()
571                         if mod:
572                             del mod
573
574
575 def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
576     # **kwargs is passed directly to convert_xml_import
577     if not status:
578         status = {}
579
580     status = status.copy()
581     package_todo = []
582     statusi = 0
583     pool = pooler.get_pool(cr.dbname)
584
585     migrations = MigrationManager(cr, graph)
586
587     has_updates = False
588     modobj = None
589
590     logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
591     
592     for package in graph:
593         logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
594         migrations.migrate_module(package, 'pre')
595         register_class(package.name)
596         modules = pool.instanciate(package.name, cr)
597         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
598             init_module_objects(cr, package.name, modules)
599         cr.commit()
600         
601     for package in graph:
602         status['progress'] = (float(statusi)+0.1) / len(graph)
603         m = package.name
604         mid = package.id
605
606         if modobj is None:
607             modobj = pool.get('ir.module.module')
608
609         if modobj and perform_checks:
610             modobj.check(cr, 1, [mid])
611
612         idref = {}
613         status['progress'] = (float(statusi)+0.4) / len(graph)
614
615         mode = 'update'
616         if hasattr(package, 'init') or package.state == 'to install':
617             mode = 'init'
618
619         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
620             has_updates = True
621             for kind in ('init', 'update'):
622                 if package.state=='to upgrade':
623                     # upgrading the module information
624                     modobj.write(cr, 1, [mid], {
625                     'description': package.data.get('description', ''),
626                     'shortdesc': package.data.get('name', ''),
627                     'author': package.data.get('author', 'Unknown'),
628                     'website': package.data.get('website', ''),
629                     'license': package.data.get('license', 'GPL-2'),
630                     'certificate': package.data.get('certificate') or None,
631                     })
632                 for filename in package.data.get('%s_xml' % kind, []):
633                     logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
634                     name, ext = os.path.splitext(filename)
635                     fp = tools.file_open(opj(m, filename))
636                     if ext == '.csv':
637                         noupdate=False
638                         if kind == 'init':
639                             noupdate=True
640                         tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate)
641                     elif ext == '.sql':
642                         queries = fp.read().split(';')
643                         for query in queries:
644                             new_query = ' '.join(query.split())
645                             if new_query:
646                                 cr.execute(new_query)
647                     elif ext == '.yml':
648                         tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
649                     else:
650                         tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs)
651                     fp.close()
652             if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
653                 status['progress'] = (float(statusi)+0.75) / len(graph)
654                 for xml in package.data.get('demo_xml', []):
655                     name, ext = os.path.splitext(xml)
656                     logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml))
657                     fp = tools.file_open(opj(m, xml))
658                     if ext == '.csv':
659                         tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True)
660                     elif ext == '.yml':
661                         tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs)
662                     else:
663                         tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
664                     fp.close()
665                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
666             package_todo.append(package.name)
667
668             migrations.migrate_module(package, 'post')
669
670             if modobj:
671                 ver = release.major_version + '.' + package.data.get('version', '1.0')
672                 # Set new modules and dependencies
673                 modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
674                 cr.commit()
675                 # Update translations for all installed languages
676                 modobj.update_translations(cr, 1, [mid], None)
677                 cr.commit()
678
679             package.state = 'installed'
680             for kind in ('init', 'demo', 'update'):
681                 if hasattr(package, kind):
682                     delattr(package, kind)
683
684         statusi += 1
685
686     cr.execute('select model from ir_model where state=%s', ('manual',))
687     for model in cr.dictfetchall():
688         pool.get('ir.model').instanciate(cr, 1, model['model'], {})
689
690     pool.get('ir.model.data')._process_end(cr, 1, package_todo)
691     cr.commit()
692
693     return has_updates
694
695 def load_modules(db, force_demo=False, status=None, update_module=False):
696     if not status:
697         status = {}
698     cr = db.cursor()
699     if cr:
700        cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
701        if len(cr.fetchall())==0:    
702            logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
703            tools.init_db(cr)
704 #           cr.execute("update res_users set password=%s where id=%s",('admin',1))
705            # in that case, force --init=all
706            tools.config["init"]["all"] = 1
707            tools.config['update']['all'] = 1
708            if not tools.config['without_demo']:
709                tools.config["demo"]['all'] = 1 
710     force = []
711     if force_demo:
712         force.append('demo')
713     pool = pooler.get_pool(cr.dbname)
714     try:
715         report = tools.assertion_report()
716         # NOTE: Try to also load the modules that have been marked as uninstallable previously...
717         STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
718         graph = create_graph(cr, ['base'], force)
719
720         has_updates = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
721
722         global not_loaded
723         if not_loaded:
724             #If some module is not loaded don't proceed further
725             not_loaded = []
726             return
727         if update_module:
728             modobj = pool.get('ir.module.module')
729             logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
730             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
731                 modobj.update_list(cr, 1)
732
733             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
734             if mods:
735                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
736                 if ids:
737                     modobj.button_install(cr, 1, ids)
738
739             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
740             if mods:
741                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
742                 if ids:
743                     modobj.button_upgrade(cr, 1, ids)
744
745             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
746
747             STATES_TO_LOAD += ['to install']
748
749         loop_guardrail = 0
750         while True:
751             loop_guardrail += 1
752             if loop_guardrail > 100:
753                 raise ProgrammingError()
754             cr.execute("SELECT name from ir_module_module WHERE state in (%s)" % ','.join(['%s']*len(STATES_TO_LOAD)), STATES_TO_LOAD)
755
756             module_list = [name for (name,) in cr.fetchall() if name not in graph]
757             if not module_list:
758                 break
759
760             new_modules_in_graph = upgrade_graph(graph, cr, module_list, force)
761             if new_modules_in_graph == 0:
762                 # nothing to load
763                 break
764             logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
765             r = load_module_graph(cr, graph, status, report=report)
766             has_updates = has_updates or r
767
768         if has_updates:
769             cr.execute("""select model,name from ir_model where id not in (select model_id from ir_model_access)""")
770             for (model, name) in cr.fetchall():
771                 logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
772
773             cr.execute("SELECT model from ir_model")
774             for (model,) in cr.fetchall():
775                 obj = pool.get(model)
776                 if obj:
777                     obj._check_removed_columns(cr, log=True)
778
779         if report.get_report():
780             logger.notifyChannel('init', netsvc.LOG_INFO, report)
781
782         for kind in ('init', 'demo', 'update'):
783             tools.config[kind] = {}
784
785         cr.commit()
786         if update_module:
787             cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
788             for mod_id, mod_name in cr.fetchall():
789                 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
790                 for rmod, rid in cr.fetchall():
791                     uid = 1
792                     rmod_module= pool.get(rmod)
793                     if rmod_module:
794                         rmod_module.unlink(cr, uid, [rid])
795                     else:
796                         logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
797                 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
798                 cr.commit()
799             #
800             # TODO: remove menu without actions of children
801             #
802             while True:
803                 cr.execute('''delete from
804                         ir_ui_menu
805                     where
806                         (id not in (select parent_id from ir_ui_menu where parent_id is not null))
807                     and
808                         (id not in (select res_id from ir_values where model='ir.ui.menu'))
809                     and
810                         (id not in (select res_id from ir_model_data where model='ir.ui.menu'))''')
811                 cr.commit()
812                 if not cr.rowcount:
813                     break
814                 else:
815                     logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
816
817             cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
818             cr.commit()
819     finally:
820         cr.close()
821
822
823 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: