Merge lp:~0k.io/openupgrade-server/8.0-openupgrade-base-2 into lp:~openupgrade-committers/openupgrade-server/8.0
- 8.0-openupgrade-base-2
- Merge into 8.0
Proposed by
Valentin Lab
Status: | Merged |
---|---|
Merged at revision: | 5144 |
Proposed branch: | lp:~0k.io/openupgrade-server/8.0-openupgrade-base-2 |
Merge into: | lp:~openupgrade-committers/openupgrade-server/8.0 |
Diff against target: |
1605 lines (+1301/-22) 13 files modified
openerp/addons/base/ir/ir_model.py (+28/-1) openerp/addons/base/res/res_currency.py (+11/-3) openerp/modules/graph.py (+11/-0) openerp/modules/loading.py (+37/-10) openerp/modules/migration.py (+6/-4) openerp/openupgrade/openupgrade.py (+479/-0) openerp/openupgrade/openupgrade_loading.py (+185/-0) openerp/openupgrade/openupgrade_log.py (+56/-0) openerp/openupgrade/openupgrade_tools.py (+8/-0) openerp/osv/orm.py (+15/-4) openerp/tools/convert.py (+3/-0) scripts/compare_noupdate_xml_records.py (+201/-0) scripts/migrate.py (+261/-0) |
To merge this branch: | bzr merge lp:~0k.io/openupgrade-server/8.0-openupgrade-base-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stefan Rijnhart (Opener) | Approve | ||
Review via email: mp+214770@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
review:
Needs Fixing
Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
To answer my own question (1): Olivier has adopted a similar change further down the openobject-server 7.0 lifecycle:
http://
I've merged this, resolving the conflict. My suggestions (2) and (3) are now in the following proposal: https:/
Thanks again!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openerp/addons/base/ir/ir_model.py' |
2 | --- openerp/addons/base/ir/ir_model.py 2014-03-12 18:06:14 +0000 |
3 | +++ openerp/addons/base/ir/ir_model.py 2014-04-08 14:07:20 +0000 |
4 | @@ -35,6 +35,8 @@ |
5 | from openerp.tools.translate import _ |
6 | from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS |
7 | |
8 | +from openerp.openupgrade import openupgrade_log, openupgrade |
9 | + |
10 | _logger = logging.getLogger(__name__) |
11 | |
12 | MODULE_UNINSTALL_FLAG = '_force_unlink' |
13 | @@ -145,6 +147,11 @@ |
14 | |
15 | def _drop_table(self, cr, uid, ids, context=None): |
16 | for model in self.browse(cr, uid, ids, context): |
17 | + # OpenUpgrade: do not run the new table cleanup |
18 | + openupgrade.message( |
19 | + cr, 'Unknown', False, False, |
20 | + "Not dropping the table or view of model %s", model.model) |
21 | + continue |
22 | model_pool = self.pool[model.model] |
23 | cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,)) |
24 | result = cr.fetchone() |
25 | @@ -304,6 +311,12 @@ |
26 | for field in self.browse(cr, uid, ids, context): |
27 | if field.name in MAGIC_COLUMNS: |
28 | continue |
29 | + # OpenUpgrade: do not run the new column cleanup |
30 | + openupgrade.message( |
31 | + cr, 'Unknown', False, False, |
32 | + "Not dropping the column of field %s of model %s", field.name, field.model) |
33 | + continue |
34 | + |
35 | model = self.pool[field.model] |
36 | cr.execute('select relkind from pg_class where relname=%s', (model._table,)) |
37 | result = cr.fetchone() |
38 | @@ -951,6 +964,10 @@ |
39 | return super(ir_model_data,self).unlink(cr, uid, ids, context=context) |
40 | |
41 | def _update(self,cr, uid, model, module, values, xml_id=False, store=True, noupdate=False, mode='init', res_id=False, context=None): |
42 | + #OpenUpgrade: log entry (used in csv import) |
43 | + if xml_id: |
44 | + openupgrade_log.log_xml_id(cr, module, xml_id) |
45 | + |
46 | model_obj = self.pool[model] |
47 | if not context: |
48 | context = {} |
49 | @@ -1177,7 +1194,17 @@ |
50 | for (model, res_id) in to_unlink: |
51 | if model in self.pool: |
52 | _logger.info('Deleting %s@%s', res_id, model) |
53 | - self.pool[model].unlink(cr, uid, [res_id]) |
54 | + try: |
55 | + cr.execute('SAVEPOINT ir_model_data_delete'); |
56 | + self.pool[model].unlink(cr, uid, [res_id]) |
57 | + cr.execute('RELEASE SAVEPOINT ir_model_data_delete') |
58 | + except Exception: |
59 | + cr.execute('ROLLBACK TO SAVEPOINT ir_model_data_delete'); |
60 | + _logger.warning( |
61 | + 'Could not delete obsolete record with id: %d of model %s\n' |
62 | + 'Please refer to the log message right above', |
63 | + res_id, model) |
64 | + |
65 | |
66 | class wizard_model_menu(osv.osv_memory): |
67 | _name = 'wizard.ir.model.menu.create' |
68 | |
69 | === modified file 'openerp/addons/base/res/res_currency.py' |
70 | --- openerp/addons/base/res/res_currency.py 2013-11-15 13:25:53 +0000 |
71 | +++ openerp/addons/base/res/res_currency.py 2014-04-08 14:07:20 +0000 |
72 | @@ -106,9 +106,17 @@ |
73 | # we would allow duplicate "global" currencies (all having company_id == NULL) |
74 | cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'res_currency_unique_name_company_id_idx'""") |
75 | if not cr.fetchone(): |
76 | - cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx |
77 | - ON res_currency |
78 | - (name, (COALESCE(company_id,-1)))""") |
79 | + try: |
80 | + cr.execute('SAVEPOINT index_currency'); |
81 | + cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx |
82 | + ON res_currency |
83 | + (name, (COALESCE(company_id,-1)))""") |
84 | + cr.execute('RELEASE SAVEPOINT index_currency'); |
85 | + except Exception, e: |
86 | + cr.execute('ROLLBACK TO SAVEPOINT index_currency'); |
87 | + import logging |
88 | + logging.getLogger('OpenUpgrade').debug( |
89 | + 'Could not create currency unique index: %s', e) |
90 | |
91 | def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): |
92 | res = super(res_currency, self).read(cr, user, ids, fields, context, load) |
93 | |
94 | === modified file 'openerp/modules/graph.py' |
95 | --- openerp/modules/graph.py 2013-03-27 17:06:39 +0000 |
96 | +++ openerp/modules/graph.py 2014-04-08 14:07:20 +0000 |
97 | @@ -92,12 +92,23 @@ |
98 | force = [] |
99 | packages = [] |
100 | len_graph = len(self) |
101 | + |
102 | + # force additional dependencies for the upgrade process if given |
103 | + # in config file |
104 | + forced_deps = tools.config.get_misc('openupgrade', 'force_deps', '{}') |
105 | + forced_deps = tools.config.get_misc('openupgrade', |
106 | + 'force_deps_' + release.version, |
107 | + forced_deps) |
108 | + forced_deps = tools.safe_eval.safe_eval(forced_deps) |
109 | + |
110 | for module in module_list: |
111 | # This will raise an exception if no/unreadable descriptor file. |
112 | # NOTE The call to load_information_from_description_file is already |
113 | # done by db.initialize, so it is possible to not do it again here. |
114 | info = openerp.modules.module.load_information_from_description_file(module) |
115 | + |
116 | if info and info['installable']: |
117 | + info['depends'].extend(forced_deps.get(module, [])) |
118 | packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version |
119 | else: |
120 | _logger.warning('module %s: not installable, skipped', module) |
121 | |
122 | === modified file 'openerp/modules/loading.py' |
123 | --- openerp/modules/loading.py 2014-03-17 15:18:10 +0000 |
124 | +++ openerp/modules/loading.py 2014-04-08 14:07:20 +0000 |
125 | @@ -43,11 +43,13 @@ |
126 | from openerp.modules.module import initialize_sys_path, \ |
127 | load_openerp_module, init_module_models, adapt_version |
128 | |
129 | +from openerp.openupgrade import openupgrade_loading |
130 | + |
131 | _logger = logging.getLogger(__name__) |
132 | _test_logger = logging.getLogger('openerp.tests') |
133 | |
134 | |
135 | -def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None): |
136 | +def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None, upg_registry=None): |
137 | """Migrates+Updates or Installs all module nodes from ``graph`` |
138 | :param graph: graph of module nodes to load |
139 | :param status: status dictionary for keeping track of progress |
140 | @@ -121,6 +123,9 @@ |
141 | if status is None: |
142 | status = {} |
143 | |
144 | + if skip_modules is None: |
145 | + skip_modules = [] |
146 | + |
147 | processed_modules = [] |
148 | loaded_modules = [] |
149 | registry = openerp.registry(cr.dbname) |
150 | @@ -135,12 +140,18 @@ |
151 | for field in cr.dictfetchall(): |
152 | registry.fields_by_model.setdefault(field['model'], []).append(field) |
153 | |
154 | + #suppress commits to have the upgrade of one module in just one transation |
155 | + cr.commit_org = cr.commit |
156 | + cr.commit = lambda *args: None |
157 | + cr.rollback_org = cr.rollback |
158 | + cr.rollback = lambda *args: None |
159 | + |
160 | # register, instantiate and initialize models for each modules |
161 | for index, package in enumerate(graph): |
162 | module_name = package.name |
163 | module_id = package.id |
164 | |
165 | - if skip_modules and module_name in skip_modules: |
166 | + if module_name in skip_modules or module_name in loaded_modules: |
167 | continue |
168 | |
169 | _logger.debug('module %s: loading objects', package.name) |
170 | @@ -151,6 +162,13 @@ |
171 | |
172 | loaded_modules.append(package.name) |
173 | if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): |
174 | + # OpenUpgrade: add this module's models to the registry |
175 | + local_registry = {} |
176 | + for model in models: |
177 | + openupgrade_loading.log_model(model, local_registry) |
178 | + openupgrade_loading.compare_registries( |
179 | + cr, package.name, upg_registry, local_registry) |
180 | + |
181 | init_module_models(cr, package.name, models) |
182 | registry._init_modules.add(package.name) |
183 | status['progress'] = float(index) / len(graph) |
184 | @@ -179,7 +197,13 @@ |
185 | _load_data(cr, module_name, idref, mode, kind='demo') |
186 | cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id)) |
187 | |
188 | - migrations.migrate_module(package, 'post') |
189 | + # OpenUpgrade: add 'try' block for logging exceptions |
190 | + # as errors in post scripts seem to be dropped |
191 | + try: |
192 | + migrations.migrate_module(package, 'post') |
193 | + except Exception, e: |
194 | + _logger.error('Error executing post migration script for module %s: %s', package, e) |
195 | + raise |
196 | |
197 | if has_demo: |
198 | # launch tests only in demo mode, allowing tests to use demo data. |
199 | @@ -206,12 +230,13 @@ |
200 | if hasattr(package, kind): |
201 | delattr(package, kind) |
202 | |
203 | - cr.commit() |
204 | + cr.commit_org() |
205 | |
206 | # The query won't be valid for models created later (i.e. custom model |
207 | # created after the registry has been loaded), so empty its result. |
208 | registry.fields_by_model = None |
209 | - |
210 | + |
211 | + cr.commit = cr.commit_org |
212 | cr.commit() |
213 | |
214 | return loaded_modules, processed_modules |
215 | @@ -230,16 +255,17 @@ |
216 | incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()]) |
217 | _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names)) |
218 | |
219 | -def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks): |
220 | +def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks, upg_registry): |
221 | """Loads modules marked with ``states``, adding them to ``graph`` and |
222 | ``loaded_modules`` and returns a list of installed/upgraded modules.""" |
223 | processed_modules = [] |
224 | while True: |
225 | cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) |
226 | module_list = [name for (name,) in cr.fetchall() if name not in graph] |
227 | + module_list = openupgrade_loading.add_module_dependencies(cr, module_list) |
228 | graph.add_modules(cr, module_list, force) |
229 | _logger.debug('Updating graph with %d more modules', len(module_list)) |
230 | - loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks) |
231 | + loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks, upg_registry=upg_registry) |
232 | processed_modules.extend(processed) |
233 | loaded_modules.extend(loaded) |
234 | if not processed: break |
235 | @@ -255,6 +281,7 @@ |
236 | if force_demo: |
237 | force.append('demo') |
238 | |
239 | + upg_registry = {} |
240 | cr = db.cursor() |
241 | try: |
242 | if not openerp.modules.db.is_initialized(cr): |
243 | @@ -282,7 +309,7 @@ |
244 | # processed_modules: for cleanup step after install |
245 | # loaded_modules: to avoid double loading |
246 | report = registry._assertion_report |
247 | - loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report) |
248 | + loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report, upg_registry=upg_registry) |
249 | |
250 | if tools.config['load_language']: |
251 | for lang in tools.config['load_language'].split(','): |
252 | @@ -331,11 +358,11 @@ |
253 | previously_processed = len(processed_modules) |
254 | processed_modules += load_marked_modules(cr, graph, |
255 | ['installed', 'to upgrade', 'to remove'], |
256 | - force, status, report, loaded_modules, update_module) |
257 | + force, status, report, loaded_modules, update_module, upg_registry) |
258 | if update_module: |
259 | processed_modules += load_marked_modules(cr, graph, |
260 | ['to install'], force, status, report, |
261 | - loaded_modules, update_module) |
262 | + loaded_modules, update_module, upg_registry) |
263 | |
264 | # load custom models |
265 | cr.execute('select model from ir_model where state=%s', ('manual',)) |
266 | |
267 | === modified file 'openerp/modules/migration.py' |
268 | --- openerp/modules/migration.py 2014-01-10 16:27:05 +0000 |
269 | +++ openerp/modules/migration.py 2014-04-08 14:07:20 +0000 |
270 | @@ -165,14 +165,16 @@ |
271 | try: |
272 | mod = imp.load_source(name, pyfile, fp2) |
273 | _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt)) |
274 | - migrate = mod.migrate |
275 | except ImportError: |
276 | _logger.exception('module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': pyfile}, strfmt)) |
277 | raise |
278 | - except AttributeError: |
279 | + |
280 | + _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % mergedict({'name': mod.__name__}, strfmt)) |
281 | + |
282 | + if hasattr(mod, 'migrate'): |
283 | + mod.migrate(self.cr, pkg.installed_version) |
284 | + else: |
285 | _logger.error('module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt) |
286 | - else: |
287 | - migrate(self.cr, pkg.installed_version) |
288 | finally: |
289 | if fp: |
290 | fp.close() |
291 | |
292 | === added directory 'openerp/openupgrade' |
293 | === added file 'openerp/openupgrade/__init__.py' |
294 | === added file 'openerp/openupgrade/openupgrade.py' |
295 | --- openerp/openupgrade/openupgrade.py 1970-01-01 00:00:00 +0000 |
296 | +++ openerp/openupgrade/openupgrade.py 2014-04-08 14:07:20 +0000 |
297 | @@ -0,0 +1,479 @@ |
298 | +# -*- coding: utf-8 -*- |
299 | +############################################################################## |
300 | +# |
301 | +# OpenERP, Open Source Management Solution |
302 | +# This module copyright (C) 2011-2013 Therp BV (<http://therp.nl>) |
303 | +# |
304 | +# This program is free software: you can redistribute it and/or modify |
305 | +# it under the terms of the GNU Affero General Public License as |
306 | +# published by the Free Software Foundation, either version 3 of the |
307 | +# License, or (at your option) any later version. |
308 | +# |
309 | +# This program is distributed in the hope that it will be useful, |
310 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
311 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
312 | +# GNU Affero General Public License for more details. |
313 | +# |
314 | +# You should have received a copy of the GNU Affero General Public License |
315 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
316 | +# |
317 | +############################################################################## |
318 | + |
319 | +import os |
320 | +import inspect |
321 | +import logging |
322 | +from openerp import release, osv, pooler, tools, SUPERUSER_ID |
323 | +import openupgrade_tools |
324 | + |
325 | +# The server log level has not been set at this point |
326 | +# so to log at loglevel debug we need to set it |
327 | +# manually here. As a consequence, DEBUG messages from |
328 | +# this file are always logged |
329 | +logger = logging.getLogger('OpenUpgrade') |
330 | +logger.setLevel(logging.DEBUG) |
331 | + |
332 | +__all__ = [ |
333 | + 'migrate', |
334 | + 'load_data', |
335 | + 'rename_columns', |
336 | + 'rename_tables', |
337 | + 'rename_models', |
338 | + 'rename_xmlids', |
339 | + 'drop_columns', |
340 | + 'delete_model_workflow', |
341 | + 'warn_possible_dataloss', |
342 | + 'set_defaults', |
343 | + 'logged_query', |
344 | + 'column_exists', |
345 | + 'table_exists', |
346 | + 'update_module_names', |
347 | + 'add_ir_model_fields', |
348 | + 'get_legacy_name', |
349 | + 'm2o_to_m2m', |
350 | + 'message', |
351 | +] |
352 | + |
353 | +def load_data(cr, module_name, filename, idref=None, mode='init'): |
354 | + """ |
355 | + Load an xml or csv data file from your post script. The usual case for this is the |
356 | + occurrence of newly added essential or useful data in the module that is |
357 | + marked with "noupdate='1'" and without "forcecreate='1'" so that it will |
358 | + not be loaded by the usual upgrade mechanism. Leaving the 'mode' argument to |
359 | + its default 'init' will load the data from your migration script. |
360 | + |
361 | + Theoretically, you could simply load a stock file from the module, but be |
362 | + careful not to reinitialize any data that could have been customized. |
363 | + Preferably, select only the newly added items. Copy these to a file |
364 | + in your migrations directory and load that file. |
365 | + Leave it to the user to actually delete existing resources that are |
366 | + marked with 'noupdate' (other named items will be deleted |
367 | + automatically). |
368 | + |
369 | + |
370 | + :param module_name: the name of the module |
371 | + :param filename: the path to the filename, relative to the module \ |
372 | + directory. |
373 | + :param idref: optional hash with ?id mapping cache? |
374 | + :param mode: one of 'init', 'update', 'demo'. Always use 'init' for adding new items \ |
375 | + from files that are marked with 'noupdate'. Defaults to 'init'. |
376 | + |
377 | + """ |
378 | + |
379 | + if idref is None: |
380 | + idref = {} |
381 | + logger.info('%s: loading %s' % (module_name, filename)) |
382 | + _, ext = os.path.splitext(filename) |
383 | + pathname = os.path.join(module_name, filename) |
384 | + fp = tools.file_open(pathname) |
385 | + try: |
386 | + if ext == '.csv': |
387 | + noupdate = True |
388 | + tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate) |
389 | + else: |
390 | + tools.convert_xml_import(cr, module_name, fp, idref, mode=mode) |
391 | + finally: |
392 | + fp.close() |
393 | + |
394 | +# for backwards compatibility |
395 | +load_xml = load_data |
396 | +table_exists = openupgrade_tools.table_exists |
397 | + |
398 | +def rename_columns(cr, column_spec): |
399 | + """ |
400 | + Rename table columns. Typically called in the pre script. |
401 | + |
402 | + :param column_spec: a hash with table keys, with lists of tuples as values. \ |
403 | + Tuples consist of (old_name, new_name). Use None for new_name to trigger a \ |
404 | + conversion of old_name using get_legacy_name() |
405 | + """ |
406 | + for table in column_spec.keys(): |
407 | + for (old, new) in column_spec[table]: |
408 | + if new is None: |
409 | + new = get_legacy_name(old) |
410 | + logger.info("table %s, column %s: renaming to %s", |
411 | + table, old, new) |
412 | + cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (table, old, new,)) |
413 | + cr.execute('DROP INDEX IF EXISTS "%s_%s_index"' % (table, old)) |
414 | + |
415 | +def rename_tables(cr, table_spec): |
416 | + """ |
417 | + Rename tables. Typically called in the pre script. |
418 | + This function also renames the id sequence if it exists and if it is |
419 | + not modified in the same run. |
420 | + |
421 | + :param table_spec: a list of tuples (old table name, new table name). |
422 | + |
423 | + """ |
424 | + # Append id sequences |
425 | + to_rename = [x[0] for x in table_spec] |
426 | + for old, new in list(table_spec): |
427 | + if (table_exists(cr, old + '_id_seq') and |
428 | + old + '_id_seq' not in to_rename): |
429 | + table_spec.append((old + '_id_seq', new + '_id_seq')) |
430 | + for (old, new) in table_spec: |
431 | + logger.info("table %s: renaming to %s", |
432 | + old, new) |
433 | + cr.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old, new,)) |
434 | + |
435 | +def rename_models(cr, model_spec): |
436 | + """ |
437 | + Rename models. Typically called in the pre script. |
438 | + :param model_spec: a list of tuples (old model name, new model name). |
439 | + |
440 | + Use case: if a model changes name, but still implements equivalent |
441 | + functionality you will want to update references in for instance |
442 | + relation fields. |
443 | + |
444 | + """ |
445 | + for (old, new) in model_spec: |
446 | + logger.info("model %s: renaming to %s", |
447 | + old, new) |
448 | + cr.execute('UPDATE ir_model SET model = %s ' |
449 | + 'WHERE model = %s', (new, old,)) |
450 | + cr.execute('UPDATE ir_model_fields SET relation = %s ' |
451 | + 'WHERE relation = %s', (new, old,)) |
452 | + # TODO: signal where the model occurs in references to ir_model |
453 | + |
454 | +def rename_xmlids(cr, xmlids_spec): |
455 | + """ |
456 | + Rename XML IDs. Typically called in the pre script. |
457 | + One usage example is when an ID changes module. In OpenERP 6 for example, |
458 | + a number of res_groups IDs moved to module base from other modules ( |
459 | + although they were still being defined in their respective module). |
460 | + """ |
461 | + for (old, new) in xmlids_spec: |
462 | + if not old.split('.') or not new.split('.'): |
463 | + logger.error( |
464 | + 'Cannot rename XMLID %s to %s: need the module ' |
465 | + 'reference to be specified in the IDs' % (old, new)) |
466 | + else: |
467 | + query = ("UPDATE ir_model_data SET module = %s, name = %s " |
468 | + "WHERE module = %s and name = %s") |
469 | + logged_query(cr, query, tuple(new.split('.') + old.split('.'))) |
470 | + |
471 | +def drop_columns(cr, column_spec): |
472 | + """ |
473 | + Drop columns but perform an additional check if a column exists. |
474 | + This covers the case of function fields that may or may not be stored. |
475 | + Consider that this may not be obvious: an additional module can govern |
476 | + a function fields' store properties. |
477 | + |
478 | + :param column_spec: a list of (table, column) tuples |
479 | + """ |
480 | + for (table, column) in column_spec: |
481 | + logger.info("table %s: drop column %s", |
482 | + table, column) |
483 | + if column_exists(cr, table, column): |
484 | + cr.execute('ALTER TABLE "%s" DROP COLUMN "%s"' % |
485 | + (table, column)) |
486 | + else: |
487 | + logger.warn("table %s: column %s did not exist", |
488 | + table, column) |
489 | + |
490 | +def delete_model_workflow(cr, model): |
491 | + """ |
492 | + Forcefully remove active workflows for obsolete models, |
493 | + to prevent foreign key issues when the orm deletes the model. |
494 | + """ |
495 | + logged_query( |
496 | + cr, |
497 | + "DELETE FROM wkf_workitem WHERE act_id in " |
498 | + "( SELECT wkf_activity.id " |
499 | + " FROM wkf_activity, wkf " |
500 | + " WHERE wkf_id = wkf.id AND " |
501 | + " wkf.osv = %s" |
502 | + ")", (model,)) |
503 | + logged_query( |
504 | + cr, |
505 | + "DELETE FROM wkf WHERE osv = %s", (model,)) |
506 | + |
507 | +def warn_possible_dataloss(cr, pool, old_module, fields): |
508 | + """ |
509 | + Use that function in the following case : |
510 | + if a field of a model was moved from a 'A' module to a 'B' module. |
511 | + ('B' depend on 'A'), |
512 | + This function will test if 'B' is installed. |
513 | + If not, count the number of different value and possibly warn the user. |
514 | + Use orm, so call from the post script. |
515 | + |
516 | + :param old_module: name of the old module |
517 | + :param fields: list of dictionary with the following keys : |
518 | + 'table' : name of the table where the field is. |
519 | + 'field' : name of the field that are moving. |
520 | + 'new_module' : name of the new module |
521 | + |
522 | + .. versionadded:: 7.0 |
523 | + """ |
524 | + module_obj = pool.get('ir.module.module') |
525 | + for field in fields: |
526 | + module_ids = module_obj.search(cr, SUPERUSER_ID, [ |
527 | + ('name', '=', field['new_module']), |
528 | + ('state', 'in', ['installed', 'to upgrade', 'to install']) |
529 | + ]) |
530 | + if not module_ids: |
531 | + cr.execute( |
532 | + "SELECT count(*) FROM (SELECT %s from %s group by %s) " |
533 | + "as tmp" % ( |
534 | + field['field'], field['table'], field['field'])) |
535 | + row = cr.fetchone() |
536 | + if row[0] == 1: |
537 | + # not a problem, that field wasn't used. |
538 | + # Just a loss of functionality |
539 | + logger.info( |
540 | + "Field '%s' from module '%s' was moved to module " |
541 | + "'%s' which is not installed: " |
542 | + "No dataloss detected, only loss of functionality" |
543 | + %(field['field'], old_module, field['new_module'])) |
544 | + else: |
545 | + # there is data loss after the migration. |
546 | + message( |
547 | + cr, old_module, |
548 | + "Field '%s' was moved to module " |
549 | + "'%s' which is not installed: " |
550 | + "There were %s distinct values in this field.", |
551 | + field['field'], field['new_module'], row[0]) |
552 | + |
553 | +def set_defaults(cr, pool, default_spec, force=False): |
554 | + """ |
555 | + Set default value. Useful for fields that are newly required. Uses orm, so |
556 | + call from the post script. |
557 | + |
558 | + :param default_spec: a hash with model names as keys. Values are lists of \ |
559 | + tuples (field, value). None as a value has a special meaning: it assigns \ |
560 | + the default value. If this value is provided by a function, the function is \ |
561 | + called as the user that created the resource. |
562 | + :param force: overwrite existing values. To be used for assigning a non- \ |
563 | + default value (presumably in the case of a new column). The ORM assigns \ |
564 | + the default value as declared in the model in an earlier stage of the \ |
565 | + process. Beware of issues with resources loaded from new data that \ |
566 | + actually do require the model's default, in combination with the post \ |
567 | + script possible being run multiple times. |
568 | + """ |
569 | + |
570 | + def write_value(ids, field, value): |
571 | + logger.debug("model %s, field %s: setting default value of resources %s to %s", |
572 | + model, field, ids, unicode(value)) |
573 | + for res_id in ids: |
574 | + # Iterating over ids here as a workaround for lp:1131653 |
575 | + obj.write(cr, SUPERUSER_ID, [res_id], {field: value}) |
576 | + |
577 | + for model in default_spec.keys(): |
578 | + obj = pool.get(model) |
579 | + if not obj: |
580 | + raise osv.except_osv("Migration: error setting default, no such model: %s" % model, "") |
581 | + |
582 | + for field, value in default_spec[model]: |
583 | + domain = not force and [(field, '=', False)] or [] |
584 | + ids = obj.search(cr, SUPERUSER_ID, domain) |
585 | + if not ids: |
586 | + continue |
587 | + if value is None: |
588 | + # Set the value by calling the _defaults of the object. |
589 | + # Typically used for company_id on various models, and in that |
590 | + # case the result depends on the user associated with the object. |
591 | + # We retrieve create_uid for this purpose and need to call the _defaults |
592 | + # function per resource. Otherwise, write all resources at once. |
593 | + if field in obj._defaults: |
594 | + if not callable(obj._defaults[field]): |
595 | + write_value(ids, field, obj._defaults[field]) |
596 | + else: |
597 | + # existence users is covered by foreign keys, so this is not needed |
598 | + # cr.execute("SELECT %s.id, res_users.id FROM %s LEFT OUTER JOIN res_users ON (%s.create_uid = res_users.id) WHERE %s.id IN %s" % |
599 | + # (obj._table, obj._table, obj._table, obj._table, tuple(ids),)) |
600 | + cr.execute("SELECT id, COALESCE(create_uid, 1) FROM %s " % obj._table + "WHERE id in %s", (tuple(ids),)) |
601 | + # Execute the function once per user_id |
602 | + user_id_map = {} |
603 | + for row in cr.fetchall(): |
604 | + user_id_map.setdefault(row[1], []).append(row[0]) |
605 | + for user_id in user_id_map: |
606 | + write_value( |
607 | + user_id_map[user_id], field, |
608 | + obj._defaults[field](obj, cr, user_id, None)) |
609 | + else: |
610 | + error = ("OpenUpgrade: error setting default, field %s with " |
611 | + "None default value not in %s' _defaults" % ( |
612 | + field, model)) |
613 | + logger.error(error) |
614 | + # this exeption seems to get lost in a higher up try block |
615 | + osv.except_osv("OpenUpgrade", error) |
616 | + else: |
617 | + write_value(ids, field, value) |
618 | + |
619 | +def logged_query(cr, query, args=None): |
620 | + """ |
621 | + Logs query and affected rows at level DEBUG |
622 | + """ |
623 | + if args is None: |
624 | + args = [] |
625 | + res = cr.execute(query, args) |
626 | + logger.debug('Running %s', query % tuple(args)) |
627 | + logger.debug('%s rows affected', cr.rowcount) |
628 | + return cr.rowcount |
629 | + |
630 | +def column_exists(cr, table, column): |
631 | + """ Check whether a certain column exists """ |
632 | + cr.execute( |
633 | + 'SELECT count(attname) FROM pg_attribute ' |
634 | + 'WHERE attrelid = ' |
635 | + '( SELECT oid FROM pg_class WHERE relname = %s ) ' |
636 | + 'AND attname = %s', |
637 | + (table, column)); |
638 | + return cr.fetchone()[0] == 1 |
639 | + |
640 | +def update_module_names(cr, namespec): |
641 | + """ |
642 | + Deal with changed module names of certified modules |
643 | + in order to prevent 'certificate not unique' error, |
644 | + as well as updating the module reference in the |
645 | + XML id. |
646 | + |
647 | + :param namespec: tuple of (old name, new name) |
648 | + """ |
649 | + for (old_name, new_name) in namespec: |
650 | + query = ("UPDATE ir_module_module SET name = %s " |
651 | + "WHERE name = %s") |
652 | + logged_query(cr, query, (new_name, old_name)) |
653 | + query = ("UPDATE ir_model_data SET module = %s " |
654 | + "WHERE module = %s ") |
655 | + logged_query(cr, query, (new_name, old_name)) |
656 | + query = ("UPDATE ir_module_module_dependency SET name = %s " |
657 | + "WHERE name = %s") |
658 | + logged_query(cr, query, (new_name, old_name)) |
659 | + |
660 | +def add_ir_model_fields(cr, columnspec): |
661 | + """ |
662 | + Typically, new columns on ir_model_fields need to be added in a very |
663 | + early stage in the upgrade process of the base module, in raw sql |
664 | + as they need to be in place before any model gets initialized. |
665 | + Do not use for fields with additional SQL constraints, such as a |
666 | + reference to another table or the cascade constraint, but craft your |
667 | + own statement taking them into account. |
668 | + |
669 | + :param columnspec: tuple of (column name, column type) |
670 | + """ |
671 | + for column in columnspec: |
672 | + query = 'ALTER TABLE ir_model_fields ADD COLUMN %s %s' % ( |
673 | + column) |
674 | + logged_query(cr, query, []) |
675 | + |
676 | +def get_legacy_name(original_name): |
677 | + """ |
678 | + Returns a versioned name for legacy tables/columns/etc |
679 | + Use this function instead of some custom name to avoid |
680 | + collisions with future or past legacy tables/columns/etc |
681 | + |
682 | + :param original_name: the original name of the column |
683 | + :param version: current version as passed to migrate() |
684 | + """ |
685 | + return 'openupgrade_legacy_'+('_').join( |
686 | + map(str, release.version_info[0:2]))+'_'+original_name |
687 | + |
688 | +def m2o_to_m2m(cr, model, table, field, source_field): |
689 | + """ |
690 | + Recreate relations in many2many fields that were formerly |
691 | + many2one fields. Use rename_columns in your pre-migrate |
692 | + script to retain the column's old value, then call m2o_to_m2m |
693 | + in your post-migrate script. |
694 | + |
695 | + :param model: The target model pool object |
696 | + :param table: The source table |
697 | + :param field: The field name of the target model |
698 | + :param source_field: the many2one column on the source table. |
699 | + |
700 | + .. versionadded:: 7.0 |
701 | + """ |
702 | + cr.execute('SELECT id, %(field)s ' |
703 | + 'FROM %(table)s ' |
704 | + 'WHERE %(field)s is not null' % { |
705 | + 'table': table, |
706 | + 'field': source_field, |
707 | + }) |
708 | + for row in cr.fetchall(): |
709 | + model.write(cr, SUPERUSER_ID, row[0], {field: [(4, row[1])]}) |
710 | + |
711 | +def message(cr, module, table, column, |
712 | + message, *args, **kwargs): |
713 | + """ |
714 | + Log handler for non-critical notifications about the upgrade. |
715 | + To be extended with logging to a table for reporting purposes. |
716 | + |
717 | + :param module: the module name that the message concerns |
718 | + :param table: the model that this message concerns (may be False, \ |
719 | + but preferably not if 'column' is defined) |
720 | + :param column: the column that this message concerns (may be False) |
721 | + |
722 | + .. versionadded:: 7.0 |
723 | + """ |
724 | + argslist = list(args or []) |
725 | + prefix = ': ' |
726 | + if column: |
727 | + argslist.insert(0, column) |
728 | + prefix = ', column %s' + prefix |
729 | + if table: |
730 | + argslist.insert(0, table) |
731 | + prefix = ', table %s' + prefix |
732 | + argslist.insert(0, module) |
733 | + prefix = 'Module %s' + prefix |
734 | + |
735 | + logger.warn(prefix + message, *argslist, **kwargs) |
736 | + |
737 | +def migrate(): |
738 | + """ |
739 | + This is the decorator for the migrate() function |
740 | + in migration scripts. |
741 | + Return when the 'version' argument is not defined, |
742 | + and log execeptions. |
743 | + Retrieve debug context data from the frame above for |
744 | + logging purposes. |
745 | + """ |
746 | + def wrap(func): |
747 | + def wrapped_function(cr, version): |
748 | + stage = 'unknown' |
749 | + module = 'unknown' |
750 | + filename = 'unknown' |
751 | + try: |
752 | + frame = inspect.getargvalues(inspect.stack()[1][0]) |
753 | + stage = frame.locals['stage'] |
754 | + module = frame.locals['pkg'].name |
755 | + filename = frame.locals['fp'].name |
756 | + except Exception, e: |
757 | + logger.error( |
758 | + "'migrate' decorator: failed to inspect " |
759 | + "the frame above: %s" % e) |
760 | + pass |
761 | + if not version: |
762 | + return |
763 | + logger.info( |
764 | + "%s: %s-migration script called with version %s" % |
765 | + (module, stage, version)) |
766 | + try: |
767 | + # The actual function is called here |
768 | + func(cr, version) |
769 | + except Exception, e: |
770 | + logger.error( |
771 | + "%s: error in migration script %s: %s" % |
772 | + (module, filename, str(e).decode('utf8'))) |
773 | + logger.exception(e) |
774 | + raise |
775 | + return wrapped_function |
776 | + return wrap |
777 | |
778 | === added file 'openerp/openupgrade/openupgrade_loading.py' |
779 | --- openerp/openupgrade/openupgrade_loading.py 1970-01-01 00:00:00 +0000 |
780 | +++ openerp/openupgrade/openupgrade_loading.py 2014-04-08 14:07:20 +0000 |
781 | @@ -0,0 +1,185 @@ |
782 | +# -*- coding: utf-8 -*- |
783 | +############################################################################## |
784 | +# |
785 | +# OpenERP, Open Source Management Solution |
786 | +# This module copyright (C) 2011-2012 Therp BV (<http://therp.nl>) |
787 | +# |
788 | +# This program is free software: you can redistribute it and/or modify |
789 | +# it under the terms of the GNU Affero General Public License as |
790 | +# published by the Free Software Foundation, either version 3 of the |
791 | +# License, or (at your option) any later version. |
792 | +# |
793 | +# This program is distributed in the hope that it will be useful, |
794 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
795 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
796 | +# GNU Affero General Public License for more details. |
797 | +# |
798 | +# You should have received a copy of the GNU Affero General Public License |
799 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
800 | +# |
801 | +############################################################################## |
802 | + |
803 | +import types |
804 | +from openerp import release |
805 | +from openerp.osv.orm import TransientModel |
806 | +from openerp.osv import fields |
807 | +from openerp.openupgrade.openupgrade import table_exists |
808 | +from openerp.tools import config, safe_eval |
809 | + |
810 | +# A collection of functions used in |
811 | +# openerp/modules/loading.py |
812 | + |
813 | +def add_module_dependencies(cr, module_list): |
814 | + """ |
815 | + Select (new) dependencies from the modules in the list |
816 | + so that we can inject them into the graph at upgrade |
817 | + time. Used in the modified OpenUpgrade Server, |
818 | + not to be called from migration scripts |
819 | + |
820 | + Also take the OpenUpgrade configuration directives 'forced_deps' |
821 | + and 'autoinstall' into account. From any additional modules |
822 | + that these directives can add, the dependencies are added as |
823 | + well (but these directives are not checked for the occurrence |
824 | + of any of the dependencies). |
825 | + """ |
826 | + if not module_list: |
827 | + return module_list |
828 | + |
829 | + forced_deps = safe_eval.safe_eval( |
830 | + config.get_misc( |
831 | + 'openupgrade', 'forced_deps_' + release.version, |
832 | + config.get_misc('openupgrade', 'forced_deps', '{}'))) |
833 | + |
834 | + autoinstall = safe_eval.safe_eval( |
835 | + config.get_misc( |
836 | + 'openupgrade', 'autoinstall_' + release.version, |
837 | + config.get_misc('openupgrade', 'autoinstall', '{}'))) |
838 | + |
839 | + for module in list(module_list): |
840 | + module_list += forced_deps.get(module, []) |
841 | + module_list += autoinstall.get(module, []) |
842 | + |
843 | + cr.execute(""" |
844 | + SELECT ir_module_module_dependency.name |
845 | + FROM |
846 | + ir_module_module, |
847 | + ir_module_module_dependency |
848 | + WHERE |
849 | + module_id = ir_module_module.id |
850 | + AND ir_module_module.name in %s |
851 | + """, (tuple(module_list),)) |
852 | + |
853 | + return list(set( |
854 | + module_list + [x[0] for x in cr.fetchall()] |
855 | + )) |
856 | + |
857 | +def log_model(model, local_registry): |
858 | + """ |
859 | + OpenUpgrade: Store the characteristics of the BaseModel and its fields |
860 | + in the local registry, so that we can compare changes with the |
861 | + main registry |
862 | + """ |
863 | + |
864 | + if not model._name: # new in 6.1 |
865 | + return |
866 | + |
867 | + # persistent models only |
868 | + if isinstance(model, TransientModel): |
869 | + return |
870 | + |
871 | + model_registry = local_registry.setdefault( |
872 | + model._name, {}) |
873 | + if model._inherits: |
874 | + model_registry['_inherits'] = {'_inherits': unicode(model._inherits)} |
875 | + for k, v in model._columns.items(): |
876 | + properties = { |
877 | + 'type': v._type, |
878 | + 'isfunction': ( |
879 | + isinstance(v, fields.function) and 'function' or ''), |
880 | + 'relation': ( |
881 | + v._type in ('many2many', 'many2one','one2many') |
882 | + and v._obj or '' |
883 | + ), |
884 | + 'required': v.required and 'required' or '', |
885 | + 'selection_keys': '', |
886 | + 'req_default': '', |
887 | + 'inherits': '', |
888 | + } |
889 | + if v._type == 'selection': |
890 | + if hasattr(v.selection, "__iter__"): |
891 | + properties['selection_keys'] = unicode( |
892 | + sorted([x[0] for x in v.selection])) |
893 | + else: |
894 | + properties['selection_keys'] = 'function' |
895 | + if v.required and k in model._defaults: |
896 | + if isinstance(model._defaults[k], types.FunctionType): |
897 | + # todo: in OpenERP 5 (and in 6 as well), |
898 | + # literals are wrapped in a lambda function |
899 | + properties['req_default'] = 'function' |
900 | + else: |
901 | + properties['req_default'] = unicode( |
902 | + model._defaults[k]) |
903 | + for key, value in properties.items(): |
904 | + if value: |
905 | + model_registry.setdefault(k, {})[key] = value |
906 | + |
907 | +def get_record_id(cr, module, model, field, mode): |
908 | + """ |
909 | + OpenUpgrade: get or create the id from the record table matching |
910 | + the key parameter values |
911 | + """ |
912 | + cr.execute( |
913 | + "SELECT id FROM openupgrade_record " |
914 | + "WHERE module = %s AND model = %s AND " |
915 | + "field = %s AND mode = %s AND type = %s", |
916 | + (module, model, field, mode, 'field') |
917 | + ) |
918 | + record = cr.fetchone() |
919 | + if record: |
920 | + return record[0] |
921 | + cr.execute( |
922 | + "INSERT INTO openupgrade_record " |
923 | + "(module, model, field, mode, type) " |
924 | + "VALUES (%s, %s, %s, %s, %s)", |
925 | + (module, model, field, mode, 'field') |
926 | + ) |
927 | + cr.execute( |
928 | + "SELECT id FROM openupgrade_record " |
929 | + "WHERE module = %s AND model = %s AND " |
930 | + "field = %s AND mode = %s AND type = %s", |
931 | + (module, model, field, mode, 'field') |
932 | + ) |
933 | + return cr.fetchone()[0] |
934 | + |
935 | +def compare_registries(cr, module, registry, local_registry): |
936 | + """ |
937 | + OpenUpgrade: Compare the local registry with the global registry, |
938 | + log any differences and merge the local registry with |
939 | + the global one. |
940 | + """ |
941 | + if not table_exists(cr, 'openupgrade_record'): |
942 | + return |
943 | + for model, fields in local_registry.items(): |
944 | + registry.setdefault(model, {}) |
945 | + for field, attributes in fields.items(): |
946 | + old_field = registry[model].setdefault(field, {}) |
947 | + mode = old_field and 'modify' or 'create' |
948 | + record_id = False |
949 | + for key, value in attributes.items(): |
950 | + if key not in old_field or old_field[key] != value: |
951 | + if not record_id: |
952 | + record_id = get_record_id( |
953 | + cr, module, model, field, mode) |
954 | + cr.execute( |
955 | + "SELECT id FROM openupgrade_attribute " |
956 | + "WHERE name = %s AND value = %s AND " |
957 | + "record_id = %s", |
958 | + (key, value, record_id) |
959 | + ) |
960 | + if not cr.fetchone(): |
961 | + cr.execute( |
962 | + "INSERT INTO openupgrade_attribute " |
963 | + "(name, value, record_id) VALUES (%s, %s, %s)", |
964 | + (key, value, record_id) |
965 | + ) |
966 | + old_field[key] = value |
967 | |
968 | === added file 'openerp/openupgrade/openupgrade_log.py' |
969 | --- openerp/openupgrade/openupgrade_log.py 1970-01-01 00:00:00 +0000 |
970 | +++ openerp/openupgrade/openupgrade_log.py 2014-04-08 14:07:20 +0000 |
971 | @@ -0,0 +1,56 @@ |
972 | +# -*- coding: utf-8 -*- |
973 | +from openupgrade_tools import table_exists |
974 | + |
975 | +def log_xml_id(cr, module, xml_id): |
976 | + """ |
977 | + Log xml_ids at load time in the records table. |
978 | + Called from openerp/tools/convert.py:xml_import._test_xml_id() |
979 | + |
980 | + # Catcha's |
981 | + - The module needs to be loaded with 'init', or the calling method |
982 | + won't be called. This can be brought about by installing the |
983 | + module or updating the 'state' field of the module to 'to install' |
984 | + or call the server with '--init <module>' and the database argument. |
985 | + |
986 | + - Do you get the right results immediately when installing the module? |
987 | + No, sorry. This method retrieves the model from the ir_model_table, but when |
988 | + the xml id is encountered for the first time, this method is called |
989 | + before the item is present in this table. Therefore, you will not |
990 | + get any meaningful results until the *second* time that you 'init' |
991 | + the module. |
992 | + |
993 | + - The good news is that the openupgrade_records module that comes |
994 | + with this distribution allows you to deal with all of this with |
995 | + one click on the menu item Settings -> Customizations -> |
996 | + Database Structure -> OpenUpgrade -> Generate Records |
997 | + |
998 | + - You cannot reinitialize the modules in your production database |
999 | + and expect to keep working on it happily ever after. Do not perform |
1000 | + this routine on your production database. |
1001 | + |
1002 | + :param module: The module that contains the xml_id |
1003 | + :param xml_id: the xml_id, with or without 'module.' prefix |
1004 | + """ |
1005 | + if not table_exists(cr, 'openupgrade_record'): |
1006 | + return |
1007 | + if not '.' in xml_id: |
1008 | + xml_id = '%s.%s' % (module, xml_id) |
1009 | + cr.execute( |
1010 | + "SELECT model FROM ir_model_data " |
1011 | + "WHERE module = %s AND name = %s", |
1012 | + xml_id.split('.')) |
1013 | + record = cr.fetchone() |
1014 | + if not record: |
1015 | + print "Cannot find xml_id %s" % xml_id |
1016 | + return |
1017 | + else: |
1018 | + cr.execute( |
1019 | + "SELECT id FROM openupgrade_record " |
1020 | + "WHERE module=%s AND model=%s AND name=%s AND type=%s", |
1021 | + (module, record[0], xml_id, 'xmlid')) |
1022 | + if not cr.fetchone(): |
1023 | + cr.execute( |
1024 | + "INSERT INTO openupgrade_record " |
1025 | + "(module, model, name, type) values(%s, %s, %s, %s)", |
1026 | + (module, record[0], xml_id, 'xmlid')) |
1027 | + |
1028 | |
1029 | === added file 'openerp/openupgrade/openupgrade_tools.py' |
1030 | --- openerp/openupgrade/openupgrade_tools.py 1970-01-01 00:00:00 +0000 |
1031 | +++ openerp/openupgrade/openupgrade_tools.py 2014-04-08 14:07:20 +0000 |
1032 | @@ -0,0 +1,8 @@ |
1033 | +# -*- coding: utf-8 -*- |
1034 | +def table_exists(cr, table): |
1035 | + """ Check whether a certain table or view exists """ |
1036 | + cr.execute( |
1037 | + 'SELECT count(relname) FROM pg_class WHERE relname = %s', |
1038 | + (table,)) |
1039 | + return cr.fetchone()[0] == 1 |
1040 | + |
1041 | |
1042 | === modified file 'openerp/osv/orm.py' |
1043 | --- openerp/osv/orm.py 2014-03-18 12:41:12 +0000 |
1044 | +++ openerp/osv/orm.py 2014-04-08 14:07:20 +0000 |
1045 | @@ -1306,6 +1306,7 @@ |
1046 | |
1047 | position = 0 |
1048 | try: |
1049 | + cr.execute('SAVEPOINT convert_records') |
1050 | for res_id, xml_id, res, info in self._convert_records(cr, uid, |
1051 | self._extract_records(cr, uid, fields, datas, |
1052 | context=context, log=log), |
1053 | @@ -1323,8 +1324,9 @@ |
1054 | if context.get('defer_parent_store_computation'): |
1055 | self._parent_store_compute(cr) |
1056 | cr.commit() |
1057 | + cr.execute('RELEASE SAVEPOINT convert_records') |
1058 | except Exception, e: |
1059 | - cr.rollback() |
1060 | + cr.execute('ROLLBACK TO SAVEPOINT convert_records') |
1061 | return -1, {}, 'Line %d : %s' % (position + 1, tools.ustr(e)), '' |
1062 | |
1063 | if context.get('defer_parent_store_computation'): |
1064 | @@ -2827,11 +2829,15 @@ |
1065 | # add the NOT NULL constraint |
1066 | cr.commit() |
1067 | try: |
1068 | + #use savepoints for openupgrade instead of transactions |
1069 | + cr.execute('SAVEPOINT add_constraint'); |
1070 | cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False) |
1071 | + cr.execute('RELEASE SAVEPOINT add_constraint'); |
1072 | cr.commit() |
1073 | _schema.debug("Table '%s': column '%s': added NOT NULL constraint", |
1074 | self._table, k) |
1075 | except Exception: |
1076 | + cr.execute('ROLLBACK TO SAVEPOINT add_constraint'); |
1077 | msg = "Table '%s': unable to set a NOT NULL constraint on column '%s' !\n"\ |
1078 | "If you want to have it, you should update the records and execute manually:\n"\ |
1079 | "ALTER TABLE %s ALTER COLUMN %s SET NOT NULL" |
1080 | @@ -2909,11 +2915,14 @@ |
1081 | cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k)) |
1082 | if f.required: |
1083 | try: |
1084 | - cr.commit() |
1085 | + #use savepoints for openupgrade instead of transactions |
1086 | + cr.execute('SAVEPOINT add_constraint'); |
1087 | cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False) |
1088 | _schema.debug("Table '%s': column '%s': added a NOT NULL constraint", |
1089 | self._table, k) |
1090 | + cr.execute('RELEASE SAVEPOINT add_constraint'); |
1091 | except Exception: |
1092 | + cr.execute('ROLLBACK TO SAVEPOINT add_constraint'); |
1093 | msg = "WARNING: unable to set column %s of table %s not null !\n"\ |
1094 | "Try to re-run: openerp-server --update=module\n"\ |
1095 | "If it doesn't work, update records and execute manually:\n"\ |
1096 | @@ -3104,12 +3113,14 @@ |
1097 | sql_actions.sort(key=lambda x: x['order']) |
1098 | for sql_action in [action for action in sql_actions if action['execute']]: |
1099 | try: |
1100 | + #use savepoints for openupgrade instead of transactions |
1101 | + cr.execute('SAVEPOINT add_constraint2'); |
1102 | cr.execute(sql_action['query']) |
1103 | - cr.commit() |
1104 | + cr.execute('RELEASE SAVEPOINT add_constraint2'); |
1105 | _schema.debug(sql_action['msg_ok']) |
1106 | except: |
1107 | _schema.warning(sql_action['msg_err']) |
1108 | - cr.rollback() |
1109 | + cr.execute('ROLLBACK TO SAVEPOINT add_constraint2'); |
1110 | |
1111 | |
1112 | def _execute_sql(self, cr): |
1113 | |
1114 | === modified file 'openerp/tools/convert.py' |
1115 | --- openerp/tools/convert.py 2014-01-16 09:17:16 +0000 |
1116 | +++ openerp/tools/convert.py 2014-04-08 14:07:20 +0000 |
1117 | @@ -66,6 +66,8 @@ |
1118 | unsafe_eval = eval |
1119 | from safe_eval import safe_eval as eval |
1120 | |
1121 | +from openerp.openupgrade import openupgrade_log |
1122 | + |
1123 | class ParseError(Exception): |
1124 | def __init__(self, msg, text, filename, lineno): |
1125 | self.msg = msg |
1126 | @@ -296,6 +298,7 @@ |
1127 | |
1128 | if len(id) > 64: |
1129 | _logger.error('id: %s is to long (max: 64)', id) |
1130 | + openupgrade_log.log_xml_id(self.cr, self.module, xml_id) |
1131 | |
1132 | def _tag_delete(self, cr, rec, data_node=None): |
1133 | d_model = rec.get("model",'') |
1134 | |
1135 | === added file 'scripts/compare_noupdate_xml_records.py' |
1136 | --- scripts/compare_noupdate_xml_records.py 1970-01-01 00:00:00 +0000 |
1137 | +++ scripts/compare_noupdate_xml_records.py 2014-04-08 14:07:20 +0000 |
1138 | @@ -0,0 +1,201 @@ |
1139 | +#!/usr/bin/python |
1140 | +# -*- coding: utf-8 -*- |
1141 | +############################################################################## |
1142 | +# |
1143 | +# OpenERP, Open Source Management Solution |
1144 | +# This module copyright (C) 2013 Therp BV (<http://therp.nl>). |
1145 | +# |
1146 | +# This program is free software: you can redistribute it and/or modify |
1147 | +# it under the terms of the GNU Affero General Public License as |
1148 | +# published by the Free Software Foundation, either version 3 of the |
1149 | +# License, or (at your option) any later version. |
1150 | +# |
1151 | +# This program is distributed in the hope that it will be useful, |
1152 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1153 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1154 | +# GNU Affero General Public License for more details. |
1155 | +# |
1156 | +# You should have received a copy of the GNU Affero General Public License |
1157 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1158 | +# |
1159 | +############################################################################## |
1160 | + |
1161 | +import sys, os, ast, argparse |
1162 | +from copy import deepcopy |
1163 | +from lxml import etree |
1164 | + |
1165 | +def read_manifest(addon_dir): |
1166 | + with open(os.path.join(addon_dir, '__openerp__.py'), 'r') as f: |
1167 | + manifest_string = f.read() |
1168 | + return ast.literal_eval(manifest_string) |
1169 | + |
1170 | +# from openerp.tools |
1171 | +def nodeattr2bool(node, attr, default=False): |
1172 | + if not node.get(attr): |
1173 | + return default |
1174 | + val = node.get(attr).strip() |
1175 | + if not val: |
1176 | + return default |
1177 | + return val.lower() not in ('0', 'false', 'off') |
1178 | + |
1179 | +def get_node_dict(element): |
1180 | + res = {} |
1181 | + for child in element: |
1182 | + if 'name' in child.attrib: |
1183 | + key = "./%s[@name='%s']" % ( |
1184 | + child.tag, child.attrib['name']) |
1185 | + res[key] = child |
1186 | + return res |
1187 | + |
1188 | +def get_node_value(element): |
1189 | + if 'eval' in element.attrib.keys(): |
1190 | + return element.attrib['eval'] |
1191 | + if 'ref' in element.attrib.keys(): |
1192 | + return element.attrib['ref'] |
1193 | + if not len(element): |
1194 | + return element.text |
1195 | + return etree.tostring(element) |
1196 | + |
1197 | +def update_node(target, source): |
1198 | + for element in source: |
1199 | + if 'name' in element.attrib: |
1200 | + query = "./%s[@name='%s']" % ( |
1201 | + element.tag, element.attrib['name']) |
1202 | + else: |
1203 | + # query = "./%s" % element.tag |
1204 | + continue |
1205 | + for existing in target.xpath(query): |
1206 | + target.remove(existing) |
1207 | + target.append(element) |
1208 | + |
1209 | +def get_records(addon_dir): |
1210 | + addon_dir = addon_dir.rstrip(os.sep) |
1211 | + addon_name = os.path.basename(addon_dir) |
1212 | + manifest = read_manifest(addon_dir) |
1213 | + # The order of the keys are important. |
1214 | + # Load files in the same order as in |
1215 | + # module/loading.py:load_module_graph |
1216 | + keys = ['init_xml', 'update_xml', 'data'] |
1217 | + records_update = {} |
1218 | + records_noupdate = {} |
1219 | + |
1220 | + def process_data_node(data_node): |
1221 | + noupdate = nodeattr2bool(data_node, 'noupdate', False) |
1222 | + record_nodes = data_node.xpath("./record") |
1223 | + for record in record_nodes: |
1224 | + xml_id = record.get("id") |
1225 | + if ('.' in xml_id |
1226 | + and xml_id.startswith(addon_name + '.')): |
1227 | + xml_id = xml_id[len(addon_name) + 1:] |
1228 | + for records in records_noupdate, records_update: |
1229 | + # records can occur multiple times in the same module |
1230 | + # with different noupdate settings |
1231 | + if xml_id in records: |
1232 | + # merge records (overwriting an existing element |
1233 | + # with the same tag). The order processing the |
1234 | + # various directives from the manifest is |
1235 | + # important here |
1236 | + update_node(records[xml_id], record) |
1237 | + break |
1238 | + else: |
1239 | + target_dict = ( |
1240 | + records_noupdate if noupdate else records_update) |
1241 | + target_dict[xml_id] = record |
1242 | + |
1243 | + for key in keys: |
1244 | + if not manifest.get(key): |
1245 | + continue |
1246 | + for xml_file in manifest[key]: |
1247 | + xml_path = xml_file.split('/') |
1248 | + try: |
1249 | + tree = etree.parse(os.path.join(addon_dir, *xml_path)) |
1250 | + except etree.XMLSyntaxError: |
1251 | + continue |
1252 | + for data_node in tree.xpath("/openerp/data"): |
1253 | + process_data_node(data_node) |
1254 | + return records_update, records_noupdate |
1255 | + |
1256 | +def main(argv=None): |
1257 | + """ |
1258 | + Attempt to represent the differences in data records flagged with |
1259 | + 'noupdate' between to different versions of the same OpenERP module. |
1260 | + |
1261 | + Print out a complete XML data file that can be loaded in a post-migration |
1262 | + script using openupgrade::load_xml(). |
1263 | + |
1264 | + Known issues: |
1265 | + - Does not detect if a deleted value belongs to a field |
1266 | + which has been removed. |
1267 | + - Ignores forcecreate=False. This hardly occurs, but you should |
1268 | + check manually for new data records with this tag. Note that |
1269 | + 'True' is the default value for data elements without this tag. |
1270 | + - Does not take csv data into account (obviously) |
1271 | + - Is not able to check cross module data |
1272 | + - etree's pretty_print is not *that* pretty |
1273 | + - Does not take translations into account (e.g. in the case of |
1274 | + email templates) |
1275 | + - Does not handle the shorthand records <menu>, <act_window> etc., |
1276 | + although that could be done using the same expansion logic as |
1277 | + is used in their parsers in openerp/tools/convert.py |
1278 | + """ |
1279 | + parser = argparse.ArgumentParser() |
1280 | + parser.add_argument( |
1281 | + 'olddir', metavar='older_module_directory') |
1282 | + parser.add_argument( |
1283 | + 'newdir', metavar='newer_module_directory') |
1284 | + arguments = parser.parse_args(argv) |
1285 | + |
1286 | + old_update, old_noupdate = get_records(arguments.olddir) |
1287 | + new_update, new_noupdate = get_records(arguments.newdir) |
1288 | + |
1289 | + data = etree.Element("data") |
1290 | + |
1291 | + for xml_id, record_new in new_noupdate.items(): |
1292 | + record_old = None |
1293 | + if xml_id in old_update: |
1294 | + record_old = old_update[xml_id] |
1295 | + elif xml_id in old_noupdate: |
1296 | + record_old = old_noupdate[xml_id] |
1297 | + |
1298 | + if record_old is None: |
1299 | + continue |
1300 | + |
1301 | + element = etree.Element( |
1302 | + "record", id=xml_id, model=record_new.attrib['model']) |
1303 | + record_old_dict = get_node_dict(record_old) |
1304 | + record_new_dict = get_node_dict(record_new) |
1305 | + for key in record_old_dict.keys(): |
1306 | + if not record_new.xpath(key): |
1307 | + # The element is no longer present. |
1308 | + # Overwrite an existing value with an |
1309 | + # empty one. Of course, we do not know |
1310 | + # if this field has actually been removed |
1311 | + attribs = deepcopy(record_old_dict[key]).attrib |
1312 | + for attr in ['eval', 'ref']: |
1313 | + if attr in attribs: |
1314 | + del attribs[attr] |
1315 | + element.append(etree.Element(record_old_dict[key].tag, attribs)) |
1316 | + else: |
1317 | + oldrepr = get_node_value(record_old_dict[key]) |
1318 | + newrepr = get_node_value(record_new_dict[key]) |
1319 | + |
1320 | + if oldrepr != newrepr: |
1321 | + element.append(deepcopy(record_new_dict[key])) |
1322 | + |
1323 | + for key in record_new_dict.keys(): |
1324 | + if not record_old.xpath(key): |
1325 | + element.append(deepcopy(record_new_dict[key])) |
1326 | + |
1327 | + if len(element): |
1328 | + data.append(element) |
1329 | + |
1330 | + openerp = etree.Element("openerp") |
1331 | + openerp.append(data) |
1332 | + document = etree.ElementTree(openerp) |
1333 | + |
1334 | + print etree.tostring( |
1335 | + document, pretty_print=True, xml_declaration=True, encoding='utf-8') |
1336 | + |
1337 | +if __name__ == "__main__": |
1338 | + main() |
1339 | + |
1340 | |
1341 | === added file 'scripts/migrate.py' |
1342 | --- scripts/migrate.py 1970-01-01 00:00:00 +0000 |
1343 | +++ scripts/migrate.py 2014-04-08 14:07:20 +0000 |
1344 | @@ -0,0 +1,261 @@ |
1345 | +#!/usr/bin/python |
1346 | + |
1347 | +import os |
1348 | +import sys |
1349 | +import StringIO |
1350 | +import psycopg2 |
1351 | +import psycopg2.extensions |
1352 | +from optparse import OptionParser |
1353 | +from ConfigParser import SafeConfigParser |
1354 | +from bzrlib.branch import Branch |
1355 | +from bzrlib.repository import Repository |
1356 | +from bzrlib.workingtree import WorkingTree |
1357 | +import bzrlib.plugin |
1358 | +import bzrlib.builtins |
1359 | +import bzrlib.info |
1360 | + |
1361 | +migrations={ |
1362 | + '7.0': { |
1363 | + 'addons': { |
1364 | + 'addons': 'lp:openupgrade-addons/7.0', |
1365 | + 'web': {'url': 'lp:openerp-web/7.0', 'addons_dir': 'addons'}, |
1366 | + }, |
1367 | + 'server': { |
1368 | + 'url': 'lp:openupgrade-server/7.0', |
1369 | + 'addons_dir': os.path.join('openerp','addons'), |
1370 | + 'root_dir': os.path.join(''), |
1371 | + 'cmd': 'openerp-server --update=all --database=%(db)s '+ |
1372 | + '--config=%(config)s --stop-after-init --no-xmlrpc --no-netrpc', |
1373 | + }, |
1374 | + }, |
1375 | + '6.1': { |
1376 | + 'addons': { |
1377 | + 'addons': 'lp:openupgrade-addons/6.1', |
1378 | + 'web': {'url': 'lp:openerp-web/6.1', 'addons_dir': 'addons'}, |
1379 | + }, |
1380 | + 'server': { |
1381 | + 'url': 'lp:openupgrade-server/6.1', |
1382 | + 'addons_dir': os.path.join('openerp','addons'), |
1383 | + 'root_dir': os.path.join(''), |
1384 | + 'cmd': 'openerp-server --update=all --database=%(db)s '+ |
1385 | + '--config=%(config)s --stop-after-init --no-xmlrpc --no-netrpc', |
1386 | + }, |
1387 | + }, |
1388 | + '6.0': { |
1389 | + 'addons': { |
1390 | + 'addons': 'lp:openupgrade-addons/6.0', |
1391 | + }, |
1392 | + 'server': { |
1393 | + 'url': 'lp:openupgrade-server/6.0', |
1394 | + 'addons_dir': os.path.join('bin','addons'), |
1395 | + 'root_dir': os.path.join('bin'), |
1396 | + 'cmd': 'bin/openerp-server.py --update=all --database=%(db)s '+ |
1397 | + '--config=%(config)s --stop-after-init --no-xmlrpc --no-netrpc', |
1398 | + }, |
1399 | + }, |
1400 | +} |
1401 | +config = SafeConfigParser() |
1402 | +parser = OptionParser(description="""Migrate script for the impatient or lazy. |
1403 | +Makes a copy of your database, downloads the files necessary to migrate |
1404 | +it as requested and runs the migration on the copy (so your original |
1405 | +database will not be touched). While the migration is running only errors are |
1406 | +shown, for a detailed log see ${branch-dir}/migration.log |
1407 | +""") |
1408 | +parser.add_option("-C", "--config", action="store", type="string", |
1409 | + dest="config", |
1410 | + help="current openerp config (required)") |
1411 | +parser.add_option("-D", "--database", action="store", type="string", |
1412 | + dest="database", |
1413 | + help="current openerp database (required if not given in config)") |
1414 | +parser.add_option("-B", "--branch-dir", action="store", type="string", |
1415 | + dest="branch_dir", |
1416 | + help="the directory to download openupgrade-server code to [%default]", |
1417 | + default='/var/tmp/openupgrade') |
1418 | +parser.add_option("-R", "--run-migrations", action="store", type="string", |
1419 | + dest="migrations", |
1420 | + help="comma separated list of migrations to run, ie. \""+ |
1421 | + ','.join(sorted([a for a in migrations]))+ |
1422 | + "\" (required)") |
1423 | +parser.add_option("-A", "--add", action="store", type="string", dest="add", |
1424 | + help="load a python module that declares a dict 'migrations' which is "+ |
1425 | + "merged with the one of this script (see the source for details). " |
1426 | + "You also can pass a string that evaluates to a dict. For the banking " |
1427 | + "addons, pass " |
1428 | + "\"{'6.1': {'addons': {'banking': 'lp:banking-addons/6.1'}}}\"") |
1429 | +parser.add_option("-I", "--inplace", action="store_true", dest="inplace", |
1430 | + help="don't copy database before attempting upgrade (dangerous)") |
1431 | +(options, args) = parser.parse_args() |
1432 | + |
1433 | +if (not options.config or not options.migrations |
1434 | + or not reduce(lambda a,b: a and (b in migrations), |
1435 | + options.migrations.split(','), True)): |
1436 | + parser.print_help() |
1437 | + sys.exit() |
1438 | + |
1439 | +config.read(options.config) |
1440 | + |
1441 | +conn_parms = {} |
1442 | +for parm in ('host', 'port', 'user', 'password'): |
1443 | + db_parm = 'db_' + parm |
1444 | + if config.has_option('options', db_parm): |
1445 | + conn_parms[parm] = config.get('options', db_parm) |
1446 | + |
1447 | +if not 'user' in conn_parms: |
1448 | + print 'No user found in configuration' |
1449 | + sys.exit() |
1450 | +db_user = conn_parms['user'] |
1451 | + |
1452 | +db_name=options.database or config.get('options', 'db_name') |
1453 | + |
1454 | +if not db_name or db_name=='' or db_name.isspace() or db_name.lower()=='false': |
1455 | + parser.print_help() |
1456 | + sys.exit() |
1457 | + |
1458 | +conn_parms['database'] = db_name |
1459 | + |
1460 | +if options.inplace: |
1461 | + db=db_name |
1462 | +else: |
1463 | + db=db_name+'_migrated' |
1464 | + |
1465 | +if options.add: |
1466 | + merge_migrations={} |
1467 | + if os.path.isfile(options.add): |
1468 | + import imp |
1469 | + merge_migrations_mod=imp.load_source('merge_migrations_mod', |
1470 | + options.add) |
1471 | + merge_migrations=merge_migrations_mod.migrations |
1472 | + else: |
1473 | + merge_migrations=eval(options.add) |
1474 | + |
1475 | + def deep_update(dict1, dict2): |
1476 | + result={} |
1477 | + for (name,value) in dict1.iteritems(): |
1478 | + if dict2.has_key(name): |
1479 | + if isinstance(dict1[name], dict) and isinstance(dict2[name], |
1480 | + dict): |
1481 | + result[name]=deep_update(dict1[name], dict2[name]) |
1482 | + else: |
1483 | + result[name]=dict2[name] |
1484 | + else: |
1485 | + result[name]=dict1[name] |
1486 | + for (name,value) in dict2.iteritems(): |
1487 | + if name not in dict1: |
1488 | + result[name]=value |
1489 | + return result |
1490 | + |
1491 | + migrations=deep_update(migrations, merge_migrations) |
1492 | + |
1493 | +for version in options.migrations.split(','): |
1494 | + if version not in migrations: |
1495 | + print '%s is not a valid version! (valid verions are %s)' % (version, |
1496 | + ','.join(sorted([a for a in migrations]))) |
1497 | + |
1498 | +bzrlib.plugin.load_plugins() |
1499 | +bzrlib.trace.enable_default_logging() |
1500 | +logfile=os.path.join(options.branch_dir,'migration.log') |
1501 | + |
1502 | +if not os.path.exists(options.branch_dir): |
1503 | + os.mkdir(options.branch_dir) |
1504 | + |
1505 | +for version in options.migrations.split(','): |
1506 | + if not os.path.exists(os.path.join(options.branch_dir,version)): |
1507 | + os.mkdir(os.path.join(options.branch_dir,version)) |
1508 | + for (name,url) in dict(migrations[version]['addons'], |
1509 | + server=migrations[version]['server']['url']).iteritems(): |
1510 | + link=url.get('link', False) if isinstance(url, dict) else False |
1511 | + url=url['url'] if isinstance(url, dict) else url |
1512 | + if os.path.exists(os.path.join(options.branch_dir,version,name)): |
1513 | + if link: |
1514 | + continue |
1515 | + cmd_revno=bzrlib.builtins.cmd_revno() |
1516 | + cmd_revno.outf=StringIO.StringIO() |
1517 | + cmd_revno.run(location=os.path.join(options.branch_dir,version, |
1518 | + name)) |
1519 | + print 'updating %s rev%s' %(os.path.join(version,name), |
1520 | + cmd_revno.outf.getvalue().strip()) |
1521 | + cmd_update=bzrlib.builtins.cmd_update() |
1522 | + cmd_update.outf=StringIO.StringIO() |
1523 | + cmd_update.outf.encoding='utf8' |
1524 | + cmd_update.run(dir=os.path.join(options.branch_dir,version, |
1525 | + name)) |
1526 | + if hasattr(cmd_update, '_operation'): |
1527 | + cmd_update.cleanup_now() |
1528 | + print 'now at rev'+cmd_revno.outf.getvalue().strip() |
1529 | + else: |
1530 | + if link: |
1531 | + print 'linking %s to %s'%(url, |
1532 | + os.path.join(options.branch_dir,version,name)) |
1533 | + os.symlink(url, os.path.join(options.branch_dir,version,name)) |
1534 | + else: |
1535 | + print 'getting '+url |
1536 | + cmd_checkout=bzrlib.builtins.cmd_checkout() |
1537 | + cmd_checkout.outf=StringIO.StringIO() |
1538 | + cmd_checkout.run(url, os.path.join(options.branch_dir,version, |
1539 | + name), lightweight=True) |
1540 | + |
1541 | +if not options.inplace: |
1542 | + print('copying database %(db_name)s to %(db)s...' % {'db_name': db_name, |
1543 | + 'db': db}) |
1544 | + conn = psycopg2.connect(**conn_parms) |
1545 | + conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) |
1546 | + cur=conn.cursor() |
1547 | + cur.execute('drop database if exists "%(db)s"' % {'db': db}) |
1548 | + cur.execute('create database "%(db)s"' % {'db': db}) |
1549 | + cur.close() |
1550 | + |
1551 | + os.environ['PGUSER'] = db_user |
1552 | + if ('host' in conn_parms and conn_parms['host'] |
1553 | + and not os.environ.get('PGHOST')): |
1554 | + os.environ['PGHOST'] = conn_parms['host'] |
1555 | + |
1556 | + if ('port' in conn_parms and conn_parms['port'] |
1557 | + and not os.environ.get('PGPORT')): |
1558 | + os.environ['PGPORT'] = conn_parms['port'] |
1559 | + |
1560 | + password_set = False |
1561 | + if ('password' in conn_parms and conn_parms['password'] |
1562 | + and not os.environ.get('PGPASSWORD')): |
1563 | + os.environ['PGPASSWORD'] = conn_parms['password'] |
1564 | + password_set = True |
1565 | + |
1566 | + os.system( |
1567 | + ('pg_dump --format=custom --no-password %(db_name)s ' + |
1568 | + '| pg_restore --no-password --dbname=%(db)s') % |
1569 | + {'db_name': db_name, 'db': db} |
1570 | + ) |
1571 | + |
1572 | + if password_set: |
1573 | + del os.environ['PGPASSWORD'] |
1574 | + |
1575 | +for version in options.migrations.split(','): |
1576 | + print 'running migration for '+version |
1577 | + config.set('options', 'without_demo', 'True') |
1578 | + config.set('options', 'logfile', logfile) |
1579 | + config.set('options', 'port', 'False') |
1580 | + config.set('options', 'netport', 'False') |
1581 | + config.set('options', 'xmlrpc_port', 'False') |
1582 | + config.set('options', 'netrpc_port', 'False') |
1583 | + config.set('options', 'addons_path', |
1584 | + ','.join([os.path.join(options.branch_dir, |
1585 | + version,'server',migrations[version]['server']['addons_dir'])] + |
1586 | + [ |
1587 | + os.path.join(options.branch_dir,version,name, |
1588 | + url.get('addons_dir', '') if isinstance(url, dict) else '') |
1589 | + for (name,url) in migrations[version]['addons'].iteritems() |
1590 | + ] |
1591 | + ) |
1592 | + ) |
1593 | + config.set('options', 'root_path', os.path.join(options.branch_dir,version, |
1594 | + 'server', migrations[version]['server']['root_dir'])) |
1595 | + config.write(open( |
1596 | + os.path.join(options.branch_dir,version,'server.cfg'), 'w+')) |
1597 | + os.system( |
1598 | + os.path.join(options.branch_dir,version,'server', |
1599 | + migrations[version]['server']['cmd'] % { |
1600 | + 'db': db, |
1601 | + 'config': os.path.join(options.branch_dir,version, |
1602 | + 'server.cfg') |
1603 | + } |
1604 | + ) |
1605 | + ) |
Thanks! This pretty much what I would expect, except for the following:
1) I seem to miss the part in openerp/ modules/ loading. py that is marked in 7.0 with the following comment
# OpenUpgrade: Loop until no modules are processed
Is this change obsolete?
2) Maybe keep the references to deferred_70, change them to deferred_80 and add a void function migrate_deferred() for further use openupgrade/ openupgrade_ 70.py if that is at all possible.
3) I think I mentioned before that we would not need all the 7.0 related stuff, but for the docs we actually need to keep openerp/
4) There is a conflict in adding openerp/openupgrade because that directory is now already there for the docs. Can you solve the conflict by adding the new files in this directory to the existing one?