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