Merge lp:~therp-nl/openupgrade-server/6.0-API_and_loading_improvements into lp:openupgrade-server/6.0
- 6.0-API_and_loading_improvements
- Merge into 6.0
Proposed by
Stefan Rijnhart (Opener)
Status: | Merged |
---|---|
Merged at revision: | 3489 |
Proposed branch: | lp:~therp-nl/openupgrade-server/6.0-API_and_loading_improvements |
Merge into: | lp:openupgrade-server/6.0 |
Diff against target: |
580 lines (+268/-102) 5 files modified
bin/addons/__init__.py (+12/-15) bin/addons/base/migrations/6.0.1.3/post-migration.py (+88/-37) bin/addons/base/migrations/6.0.1.3/pre-migration.py (+25/-15) bin/addons/openupgrade_records/lib/apriori.py (+3/-0) bin/openupgrade/openupgrade.py (+140/-35) |
To merge this branch: | bzr merge lp:~therp-nl/openupgrade-server/6.0-API_and_loading_improvements |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenUpgrade Committers | Pending | ||
Review via email: mp+109162@code.launchpad.net |
Commit message
Description of the change
This branch ensures that pre and post scripts are only called once. This means that developers do not have to bother checking whether they are being rerun in the scripts themselves. It is now also ensured that new dependencies are installed automatically. Modules that are known to be obsolete will now be removed automatically.
The API is augmented with a decorator that can be used in migration scripts to log any errors that may occur. A new function for renaming XML IDs is provided, and setting defaults provided by a function is faster.
Some bugs in the base module migration scripts are solved.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/addons/__init__.py' | |||
2 | --- bin/addons/__init__.py 2012-05-27 12:34:50 +0000 | |||
3 | +++ bin/addons/__init__.py 2012-06-07 14:50:24 +0000 | |||
4 | @@ -50,14 +50,7 @@ | |||
5 | 50 | 50 | ||
6 | 51 | logger = netsvc.Logger() | 51 | logger = netsvc.Logger() |
7 | 52 | 52 | ||
16 | 53 | ### OpenUpgrade | 53 | from openupgrade import openupgrade |
9 | 54 | def table_exists(cr, table): | ||
10 | 55 | """ Check whether a certain table or view exists """ | ||
11 | 56 | cr.execute( | ||
12 | 57 | 'SELECT count(relname) FROM pg_class WHERE relname = %s', | ||
13 | 58 | (table,)) | ||
14 | 59 | return cr.fetchone()[0] == 1 | ||
15 | 60 | ### End of OpenUpgrade | ||
17 | 61 | 54 | ||
18 | 62 | _ad = os.path.abspath(opj(tools.ustr(tools.config['root_path']), u'addons')) # default addons path (base) | 55 | _ad = os.path.abspath(opj(tools.ustr(tools.config['root_path']), u'addons')) # default addons path (base) |
19 | 63 | ad_paths= map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(',')) | 56 | ad_paths= map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(',')) |
20 | @@ -808,7 +801,7 @@ | |||
21 | 808 | log any differences and merge the local registry with | 801 | log any differences and merge the local registry with |
22 | 809 | the global one. | 802 | the global one. |
23 | 810 | """ | 803 | """ |
25 | 811 | if not table_exists(cr, 'openupgrade_record'): | 804 | if not openupgrade.table_exists(cr, 'openupgrade_record'): |
26 | 812 | return | 805 | return |
27 | 813 | for model, fields in local_registry.items(): | 806 | for model, fields in local_registry.items(): |
28 | 814 | registry.setdefault(model, {}) | 807 | registry.setdefault(model, {}) |
29 | @@ -848,10 +841,9 @@ | |||
30 | 848 | logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph)) | 841 | logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph)) |
31 | 849 | 842 | ||
32 | 850 | for package in graph: | 843 | for package in graph: |
33 | 851 | if skip_modules and package.name in skip_modules: | ||
34 | 852 | continue | ||
35 | 853 | logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name) | 844 | logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name) |
37 | 854 | migrations.migrate_module(package, 'pre') | 845 | if package.name not in skip_modules['pre']: |
38 | 846 | migrations.migrate_module(package, 'pre') | ||
39 | 855 | register_class(package.name) | 847 | register_class(package.name) |
40 | 856 | modules = pool.instanciate(package.name, cr) | 848 | modules = pool.instanciate(package.name, cr) |
41 | 857 | 849 | ||
42 | @@ -864,13 +856,14 @@ | |||
43 | 864 | 856 | ||
44 | 865 | init_module_objects(cr, package.name, modules) | 857 | init_module_objects(cr, package.name, modules) |
45 | 866 | cr.commit() | 858 | cr.commit() |
46 | 859 | skip_modules['pre'].append(package.name) | ||
47 | 867 | 860 | ||
48 | 868 | for package in graph: | 861 | for package in graph: |
49 | 869 | status['progress'] = (float(statusi)+0.1) / len(graph) | 862 | status['progress'] = (float(statusi)+0.1) / len(graph) |
50 | 870 | m = package.name | 863 | m = package.name |
51 | 871 | mid = package.id | 864 | mid = package.id |
52 | 872 | 865 | ||
54 | 873 | if skip_modules and m in skip_modules: | 866 | if package.name in skip_modules['post']: |
55 | 874 | continue | 867 | continue |
56 | 875 | 868 | ||
57 | 876 | if modobj is None: | 869 | if modobj is None: |
58 | @@ -923,6 +916,7 @@ | |||
59 | 923 | if hasattr(package, kind): | 916 | if hasattr(package, kind): |
60 | 924 | delattr(package, kind) | 917 | delattr(package, kind) |
61 | 925 | 918 | ||
62 | 919 | skip_modules['post'].append(package.name) | ||
63 | 926 | statusi += 1 | 920 | statusi += 1 |
64 | 927 | cr.commit() | 921 | cr.commit() |
65 | 928 | 922 | ||
66 | @@ -964,6 +958,7 @@ | |||
67 | 964 | 958 | ||
68 | 965 | registry = {} | 959 | registry = {} |
69 | 966 | 960 | ||
70 | 961 | skip_modules = {'pre': [], 'post': []} | ||
71 | 967 | try: | 962 | try: |
72 | 968 | processed_modules = [] | 963 | processed_modules = [] |
73 | 969 | report = tools.assertion_report() | 964 | report = tools.assertion_report() |
74 | @@ -977,7 +972,7 @@ | |||
75 | 977 | if not graph: | 972 | if not graph: |
76 | 978 | logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)') | 973 | logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)') |
77 | 979 | raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) | 974 | raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) |
79 | 980 | processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), registry=registry, report=report)) | 975 | processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), registry=registry, report=report, skip_modules=skip_modules)) |
80 | 981 | 976 | ||
81 | 982 | if tools.config['load_language']: | 977 | if tools.config['load_language']: |
82 | 983 | for lang in tools.config['load_language'].split(','): | 978 | for lang in tools.config['load_language'].split(','): |
83 | @@ -1021,13 +1016,15 @@ | |||
84 | 1021 | if not module_list: | 1016 | if not module_list: |
85 | 1022 | break | 1017 | break |
86 | 1023 | 1018 | ||
87 | 1019 | # OpenUpgrade: forcefeeding module dependencies into the graph | ||
88 | 1020 | module_list = openupgrade.add_module_dependencies(cr, module_list) | ||
89 | 1024 | new_modules_in_graph = upgrade_graph(graph, cr, module_list, force) | 1021 | new_modules_in_graph = upgrade_graph(graph, cr, module_list, force) |
90 | 1025 | if new_modules_in_graph == 0: | 1022 | if new_modules_in_graph == 0: |
91 | 1026 | # nothing to load | 1023 | # nothing to load |
92 | 1027 | break | 1024 | break |
93 | 1028 | 1025 | ||
94 | 1029 | logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list))) | 1026 | logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list))) |
96 | 1030 | processed_modules.extend(load_module_graph(cr, graph, status, registry=registry, report=report, skip_modules=processed_modules)) | 1027 | processed_modules.extend(load_module_graph(cr, graph, status, registry=registry, report=report, skip_modules=skip_modules)) |
97 | 1031 | 1028 | ||
98 | 1032 | # load custom models | 1029 | # load custom models |
99 | 1033 | cr.execute('select model from ir_model where state=%s', ('manual',)) | 1030 | cr.execute('select model from ir_model where state=%s', ('manual',)) |
100 | 1034 | 1031 | ||
101 | === modified file 'bin/addons/base/migrations/6.0.1.3/post-migration.py' | |||
102 | --- bin/addons/base/migrations/6.0.1.3/post-migration.py 2011-11-30 22:51:16 +0000 | |||
103 | +++ bin/addons/base/migrations/6.0.1.3/post-migration.py 2012-06-07 14:50:24 +0000 | |||
104 | @@ -1,18 +1,58 @@ | |||
105 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
106 | 2 | 2 | ||
107 | 3 | from osv import osv | ||
108 | 4 | import pooler, logging | 3 | import pooler, logging |
109 | 5 | from openupgrade import openupgrade | 4 | from openupgrade import openupgrade |
111 | 6 | log = logging.getLogger('migrate') | 5 | logger = logging.getLogger('migrate') |
112 | 6 | MODULE="base" | ||
113 | 7 | |||
114 | 8 | obsolete_modules = [ | ||
115 | 9 | 'account_report', | ||
116 | 10 | 'account_reporting', | ||
117 | 11 | 'account_tax_include', | ||
118 | 12 | 'board_account', | ||
119 | 13 | 'board_sale', | ||
120 | 14 | 'report_account', | ||
121 | 15 | 'report_analytic', | ||
122 | 16 | 'report_analytic_line', | ||
123 | 17 | 'report_crm', | ||
124 | 18 | 'report_purchase', | ||
125 | 19 | 'report_sale', | ||
126 | 20 | 'pxgo_bank_statement_analytic', | ||
127 | 21 | ] | ||
128 | 22 | |||
129 | 23 | def set_defaults_on_act_window(cr): | ||
130 | 24 | """ The act window model has a constraint | ||
131 | 25 | that checks the validity of the model on it. | ||
132 | 26 | At migration time, this is inconvenient. | ||
133 | 27 | Therefore, we set the defaults through SQL | ||
134 | 28 | |||
135 | 29 | Replaces setting the following defaults | ||
136 | 30 | #'ir.actions.act_window': [ | ||
137 | 31 | # ('auto_search', True), | ||
138 | 32 | # ('context', '{}'), | ||
139 | 33 | # ('multi', False), | ||
140 | 34 | # ], | ||
141 | 35 | |||
142 | 36 | """ | ||
143 | 37 | cr.execute(""" | ||
144 | 38 | UPDATE ir_act_window | ||
145 | 39 | SET auto_search = true, | ||
146 | 40 | multi = false | ||
147 | 41 | """) | ||
148 | 42 | cr.execute(""" | ||
149 | 43 | UPDATE ir_act_window | ||
150 | 44 | SET context = '{}' | ||
151 | 45 | WHERE context is NULL | ||
152 | 46 | """) | ||
153 | 47 | cr.execute(""" | ||
154 | 48 | UPDATE ir_act_window | ||
155 | 49 | SET context = '{}' | ||
156 | 50 | WHERE context is NULL | ||
157 | 51 | """) | ||
158 | 7 | 52 | ||
159 | 8 | defaults = { | 53 | defaults = { |
160 | 9 | # False results in column value NULL | 54 | # False results in column value NULL |
161 | 10 | # None value triggers a call to the model's default function | 55 | # None value triggers a call to the model's default function |
162 | 11 | 'ir.actions.act_window': [ | ||
163 | 12 | ('auto_search', True), | ||
164 | 13 | ('context', '{}'), | ||
165 | 14 | ('multi', False), | ||
166 | 15 | ], | ||
167 | 16 | 'ir.actions.todo': [ | 56 | 'ir.actions.todo': [ |
168 | 17 | ('restart', 'onskip'), | 57 | ('restart', 'onskip'), |
169 | 18 | ], | 58 | ], |
170 | @@ -96,13 +136,14 @@ | |||
171 | 96 | # In the pre script, we renamed the old function column, a many2one | 136 | # In the pre script, we renamed the old function column, a many2one |
172 | 97 | # to res.partner.function | 137 | # to res.partner.function |
173 | 98 | cr.execute( | 138 | cr.execute( |
181 | 99 | "UPDATE res_partner_address SET function = res_partner_function.name " + | 139 | "UPDATE res_partner_address " |
182 | 100 | "FROM res_partner_function " + | 140 | "SET function = openupgrade_legacy_res_partner_function.name " |
183 | 101 | "WHERE res_partner_function.id = res_partner_address.tmp_mgr_function" | 141 | "FROM openupgrade_legacy_res_partner_function " |
184 | 102 | ) | 142 | "WHERE openupgrade_legacy_res_partner_function.id " |
185 | 103 | cr.execute("ALTER TABLE res_partner_address DROP COLUMN tmp_mgr_function CASCADE") | 143 | "= res_partner_address.tmp_mgr_function") |
186 | 104 | cr.execute("DROP TABLE res_partner_function") | 144 | cr.execute( |
187 | 105 | 145 | "ALTER TABLE res_partner_address " | |
188 | 146 | "DROP COLUMN tmp_mgr_function CASCADE") | ||
189 | 106 | # and the reverse: 'title' is now a many2one on res_partner_title | 147 | # and the reverse: 'title' is now a many2one on res_partner_title |
190 | 107 | cr.execute( | 148 | cr.execute( |
191 | 108 | "SELECT id, tmp_mgr_title FROM res_partner_address WHERE title IS NULL " + | 149 | "SELECT id, tmp_mgr_title FROM res_partner_address WHERE title IS NULL " + |
192 | @@ -139,10 +180,9 @@ | |||
193 | 139 | if bank_ids: | 180 | if bank_ids: |
194 | 140 | bank_id = bank_ids[0] | 181 | bank_id = bank_ids[0] |
195 | 141 | else: | 182 | else: |
200 | 142 | bank_id = bank_obj.create(cursor, uid, dict( | 183 | bank_id = bank_obj.create(cr, 1, dict( |
201 | 143 | code = info.code or 'UNKNOW', | 184 | code = 'UNKNOW', |
202 | 144 | name = info.name or _('Unknown Bank'), | 185 | name = 'Unknown Bank')), |
199 | 145 | )) | ||
203 | 146 | partner_bank_obj.write(cr, 1, partner_bank_ids, {'bank': bank_id}) | 186 | partner_bank_obj.write(cr, 1, partner_bank_ids, {'bank': bank_id}) |
204 | 147 | 187 | ||
205 | 148 | def mgr_roles_to_groups(cr, pool): | 188 | def mgr_roles_to_groups(cr, pool): |
206 | @@ -208,24 +248,35 @@ | |||
207 | 208 | "AND ir_actions.usage = 'menu'" | 248 | "AND ir_actions.usage = 'menu'" |
208 | 209 | ) | 249 | ) |
209 | 210 | 250 | ||
210 | 251 | def mark_obsolete_modules(cr): | ||
211 | 252 | """ | ||
212 | 253 | Remove modules that are known to be obsolete | ||
213 | 254 | in this version of the OpenERP server. | ||
214 | 255 | """ | ||
215 | 256 | openupgrade.logged_query( | ||
216 | 257 | cr, """ | ||
217 | 258 | UPDATE | ||
218 | 259 | ir_module_module | ||
219 | 260 | SET | ||
220 | 261 | state='to remove' | ||
221 | 262 | WHERE | ||
222 | 263 | state='installed' | ||
223 | 264 | AND name in %s | ||
224 | 265 | """, | ||
225 | 266 | (tuple(obsolete_modules),)) | ||
226 | 267 | |||
227 | 268 | @openupgrade.migrate() | ||
228 | 211 | def migrate(cr, version): | 269 | def migrate(cr, version): |
248 | 212 | try: | 270 | pool = pooler.get_pool(cr.dbname) |
249 | 213 | 271 | set_defaults_on_act_window(cr) | |
250 | 214 | log.info("post-set-defaults.py now called") | 272 | openupgrade.set_defaults(cr, pool, defaults) |
251 | 215 | # this method called in a try block too | 273 | mgr_ir_rule(cr, pool) |
252 | 216 | pool = pooler.get_pool(cr.dbname) | 274 | mgr_res_partner_address(cr, pool) |
253 | 217 | 275 | mgr_res_partner(cr, pool) | |
254 | 218 | openupgrade.set_defaults(cr, pool, defaults) | 276 | mgr_default_bank(cr, pool) |
255 | 219 | 277 | mgr_roles_to_groups(cr, pool) | |
256 | 220 | mgr_ir_rule(cr, pool) | 278 | mgr_res_currency(cr, pool) |
257 | 221 | mgr_res_partner_address(cr, pool) | 279 | mgr_ir_module_module(cr) |
258 | 222 | mgr_res_partner(cr, pool) | 280 | mgr_res_users(cr) |
259 | 223 | mgr_default_bank(cr, pool) | 281 | mark_obsolete_modules(cr) |
241 | 224 | mgr_roles_to_groups(cr, pool) | ||
242 | 225 | mgr_res_currency(cr, pool) | ||
243 | 226 | mgr_ir_module_module(cr) | ||
244 | 227 | mgr_res_users(cr) | ||
245 | 228 | except Exception, e: | ||
246 | 229 | log.info("Migration: error in post-set-defaults.py: %s" % e) | ||
247 | 230 | raise | ||
260 | 231 | 282 | ||
261 | 232 | 283 | ||
262 | === modified file 'bin/addons/base/migrations/6.0.1.3/pre-migration.py' | |||
263 | --- bin/addons/base/migrations/6.0.1.3/pre-migration.py 2012-01-15 11:33:42 +0000 | |||
264 | +++ bin/addons/base/migrations/6.0.1.3/pre-migration.py 2012-06-07 14:50:24 +0000 | |||
265 | @@ -3,11 +3,18 @@ | |||
266 | 3 | # Removal of modules that are deprecated | 3 | # Removal of modules that are deprecated |
267 | 4 | # e.g. report_analytic_line (incorporated in hr_timesheet_invoice) | 4 | # e.g. report_analytic_line (incorporated in hr_timesheet_invoice) |
268 | 5 | 5 | ||
269 | 6 | from osv import osv | ||
270 | 7 | import pooler | 6 | import pooler |
271 | 8 | import logging | 7 | import logging |
272 | 9 | from openupgrade import openupgrade | 8 | from openupgrade import openupgrade |
273 | 9 | |||
274 | 10 | log = logging.getLogger('migrate') | 10 | log = logging.getLogger('migrate') |
275 | 11 | MODULE = 'base' | ||
276 | 12 | |||
277 | 13 | module_namespec = [ | ||
278 | 14 | # This is a list of tuples (old module name, new module name) | ||
279 | 15 | ('profile_association', 'association'), | ||
280 | 16 | ('report_analytic_planning', 'project_planning'), | ||
281 | 17 | ] | ||
282 | 11 | 18 | ||
283 | 12 | renames = { | 19 | renames = { |
284 | 13 | # this is a mapping per table from old column name | 20 | # this is a mapping per table from old column name |
285 | @@ -27,6 +34,11 @@ | |||
286 | 27 | ], | 34 | ], |
287 | 28 | } | 35 | } |
288 | 29 | 36 | ||
289 | 37 | renamed_xmlids = [ | ||
290 | 38 | ('sale.group_sale_manager', 'base.group_sale_manager'), | ||
291 | 39 | ('sale.group_sale_user', 'base.group_sale_salesman'), | ||
292 | 40 | ] | ||
293 | 41 | |||
294 | 30 | def mgr_ir_model_fields(cr): | 42 | def mgr_ir_model_fields(cr): |
295 | 31 | cr.execute('ALTER TABLE ir_model_fields ADD COLUMN selectable BOOLEAN') | 43 | cr.execute('ALTER TABLE ir_model_fields ADD COLUMN selectable BOOLEAN') |
296 | 32 | cr.execute('UPDATE ir_model_fields SET selectable = FALSE') | 44 | cr.execute('UPDATE ir_model_fields SET selectable = FALSE') |
297 | @@ -54,20 +66,18 @@ | |||
298 | 54 | "AND ir_model_data.model = 'res.currency.rate' " + | 66 | "AND ir_model_data.model = 'res.currency.rate' " + |
299 | 55 | "AND ir_model_data.name = 'rateINR'") | 67 | "AND ir_model_data.name = 'rateINR'") |
300 | 56 | if not cr.rowcount: | 68 | if not cr.rowcount: |
301 | 57 | import pdb | ||
302 | 58 | pdb.set_trace() | ||
303 | 59 | raise osv.except_osv("Migration: error setting INR rate in demo data, no row found", "") | 69 | raise osv.except_osv("Migration: error setting INR rate in demo data, no row found", "") |
304 | 60 | 70 | ||
305 | 71 | @openupgrade.migrate() | ||
306 | 61 | def migrate(cr, version): | 72 | def migrate(cr, version): |
319 | 62 | try: | 73 | openupgrade.update_module_names( |
320 | 63 | # this method called in a try block too | 74 | cr, module_namespec |
321 | 64 | log.info("base:pre.py now called") | 75 | ) |
322 | 65 | pool = pooler.get_pool(cr.dbname) | 76 | openupgrade.rename_columns(cr, renames) |
323 | 66 | openupgrade.rename_columns(cr, renames) | 77 | openupgrade.rename_xmlids(cr, renamed_xmlids) |
324 | 67 | mgr_ir_model_fields(cr) | 78 | mgr_ir_model_fields(cr) |
325 | 68 | mgr_company_id(cr) | 79 | mgr_company_id(cr) |
326 | 69 | mgr_fix_test_results(cr) | 80 | mgr_fix_test_results(cr) |
327 | 70 | except Exception, e: | 81 | openupgrade.rename_tables( |
328 | 71 | log.info("Migration: error in pre-convert-fields.py: %s", e) | 82 | cr, [('res_partner_function', |
329 | 72 | osv.except_osv("Migration: error in pre-convert-fields.py: %s" % e, "") | 83 | 'openupgrade_legacy_res_partner_function')]) |
318 | 73 | raise | ||
330 | 74 | 84 | ||
331 | === modified file 'bin/addons/openupgrade_records/lib/apriori.py' | |||
332 | --- bin/addons/openupgrade_records/lib/apriori.py 2012-04-18 20:20:03 +0000 | |||
333 | +++ bin/addons/openupgrade_records/lib/apriori.py 2012-06-07 14:50:24 +0000 | |||
334 | @@ -3,7 +3,10 @@ | |||
335 | 3 | """ | 3 | """ |
336 | 4 | 4 | ||
337 | 5 | renamed_modules = { | 5 | renamed_modules = { |
338 | 6 | 'association_profile': 'association', | ||
339 | 7 | 'report_analytic_planning': 'project_planning', | ||
340 | 6 | } | 8 | } |
341 | 7 | 9 | ||
342 | 8 | renamed_models = { | 10 | renamed_models = { |
343 | 11 | 'mrp.procurement': 'procurement.order', | ||
344 | 9 | } | 12 | } |
345 | 10 | 13 | ||
346 | === modified file 'bin/openupgrade/openupgrade.py' | |||
347 | --- bin/openupgrade/openupgrade.py 2012-05-06 18:56:17 +0000 | |||
348 | +++ bin/openupgrade/openupgrade.py 2012-06-07 14:50:24 +0000 | |||
349 | @@ -1,5 +1,26 @@ | |||
350 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
351 | 2 | ############################################################################## | ||
352 | 3 | # | ||
353 | 4 | # OpenERP, Open Source Management Solution | ||
354 | 5 | # This module copyright (C) 2011-2012 Therp BV (<http://therp.nl>) | ||
355 | 6 | # | ||
356 | 7 | # This program is free software: you can redistribute it and/or modify | ||
357 | 8 | # it under the terms of the GNU Affero General Public License as | ||
358 | 9 | # published by the Free Software Foundation, either version 3 of the | ||
359 | 10 | # License, or (at your option) any later version. | ||
360 | 11 | # | ||
361 | 12 | # This program is distributed in the hope that it will be useful, | ||
362 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
363 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
364 | 15 | # GNU Affero General Public License for more details. | ||
365 | 16 | # | ||
366 | 17 | # You should have received a copy of the GNU Affero General Public License | ||
367 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
368 | 19 | # | ||
369 | 20 | ############################################################################## | ||
370 | 21 | |||
371 | 2 | import os | 22 | import os |
372 | 23 | import inspect | ||
373 | 3 | from osv import osv | 24 | from osv import osv |
374 | 4 | import pooler | 25 | import pooler |
375 | 5 | import logging | 26 | import logging |
376 | @@ -9,16 +30,20 @@ | |||
377 | 9 | logger = logging.getLogger('OpenUpgrade') | 30 | logger = logging.getLogger('OpenUpgrade') |
378 | 10 | 31 | ||
379 | 11 | __all__ = [ | 32 | __all__ = [ |
380 | 33 | 'migrate', | ||
381 | 12 | 'load_data', | 34 | 'load_data', |
382 | 13 | 'rename_columns', | 35 | 'rename_columns', |
383 | 14 | 'rename_tables', | 36 | 'rename_tables', |
384 | 15 | 'drop_columns', | 37 | 'drop_columns', |
385 | 16 | 'table_exists', | 38 | 'table_exists', |
386 | 17 | 'column_exists', | 39 | 'column_exists', |
387 | 40 | 'logged_query', | ||
388 | 18 | 'delete_model_workflow', | 41 | 'delete_model_workflow', |
389 | 19 | 'set_defaults', | 42 | 'set_defaults', |
390 | 20 | 'update_module_names', | 43 | 'update_module_names', |
391 | 21 | 'add_ir_model_fields', | 44 | 'add_ir_model_fields', |
392 | 45 | 'rename_models', | ||
393 | 46 | 'rename_xmlids', | ||
394 | 22 | ] | 47 | ] |
395 | 23 | 48 | ||
396 | 24 | def load_data(cr, module_name, filename, idref=None, mode='init'): | 49 | def load_data(cr, module_name, filename, idref=None, mode='init'): |
397 | @@ -94,7 +119,7 @@ | |||
398 | 94 | def rename_models(cr, model_spec): | 119 | def rename_models(cr, model_spec): |
399 | 95 | """ | 120 | """ |
400 | 96 | Rename models. Typically called in the pre script. | 121 | Rename models. Typically called in the pre script. |
402 | 97 | :param column_spec: a list of tuples (old table name, new table name). | 122 | :param column_spec: a list of tuples (old model name, new model name). |
403 | 98 | 123 | ||
404 | 99 | Use case: if a model changes name, but still implements equivalent | 124 | Use case: if a model changes name, but still implements equivalent |
405 | 100 | functionality you will want to update references in for instance | 125 | functionality you will want to update references in for instance |
406 | @@ -106,6 +131,24 @@ | |||
407 | 106 | old, new) | 131 | old, new) |
408 | 107 | cr.execute('UPDATE ir_model_fields SET relation = %s ' | 132 | cr.execute('UPDATE ir_model_fields SET relation = %s ' |
409 | 108 | 'WHERE relation = %s', (new, old,)) | 133 | 'WHERE relation = %s', (new, old,)) |
410 | 134 | # TODO: signal where the model occurs in references to ir_model | ||
411 | 135 | |||
412 | 136 | def rename_xmlids(cr, xmlids_spec): | ||
413 | 137 | """ | ||
414 | 138 | Rename XML IDs. Typically called in the pre script. | ||
415 | 139 | One usage example is when an ID changes module. In OpenERP 6 for example, | ||
416 | 140 | a number of res_groups IDs moved to module base from other modules ( | ||
417 | 141 | although they were still being defined in their respective module). | ||
418 | 142 | """ | ||
419 | 143 | for (old, new) in xmlids_spec: | ||
420 | 144 | if not old.split('.') or not new.split('.'): | ||
421 | 145 | logger.error( | ||
422 | 146 | 'Cannot rename XMLID %s to %s: need the module ' | ||
423 | 147 | 'reference to be specified in the IDs' % (old, new)) | ||
424 | 148 | else: | ||
425 | 149 | query = ("UPDATE ir_model_data SET module = %s, name = %s " | ||
426 | 150 | "WHERE module = %s and name = %s") | ||
427 | 151 | logged_query(cr, query, tuple(new.split('.') + old.split('.'))) | ||
428 | 109 | 152 | ||
429 | 110 | def drop_columns(cr, column_spec): | 153 | def drop_columns(cr, column_spec): |
430 | 111 | """ | 154 | """ |
431 | @@ -161,8 +204,8 @@ | |||
432 | 161 | """ | 204 | """ |
433 | 162 | 205 | ||
434 | 163 | def write_value(ids, field, value): | 206 | def write_value(ids, field, value): |
437 | 164 | logger.info("model %s, field %s: setting default value of %d resources to %s", | 207 | logger.debug("model %s, field %s: setting default value of resources %s to %s", |
438 | 165 | model, field, len(ids), unicode(value)) | 208 | model, field, ids, unicode(value)) |
439 | 166 | obj.write(cr, 1, ids, {field: value}) | 209 | obj.write(cr, 1, ids, {field: value}) |
440 | 167 | 210 | ||
441 | 168 | for model in default_spec.keys(): | 211 | for model in default_spec.keys(): |
442 | @@ -170,40 +213,42 @@ | |||
443 | 170 | if not obj: | 213 | if not obj: |
444 | 171 | raise osv.except_osv("Migration: error setting default, no such model: %s" % model, "") | 214 | raise osv.except_osv("Migration: error setting default, no such model: %s" % model, "") |
445 | 172 | 215 | ||
460 | 173 | for field, value in default_spec[model]: | 216 | for field, value in default_spec[model]: |
461 | 174 | domain = not force and [(field, '=', False)] or [] | 217 | domain = not force and [(field, '=', False)] or [] |
462 | 175 | ids = obj.search(cr, 1, domain) | 218 | ids = obj.search(cr, 1, domain) |
463 | 176 | if not ids: | 219 | if not ids: |
464 | 177 | continue | 220 | continue |
465 | 178 | if value is None: | 221 | if value is None: |
466 | 179 | # Set the value by calling the _defaults of the object. | 222 | # Set the value by calling the _defaults of the object. |
467 | 180 | # Typically used for company_id on various models, and in that | 223 | # Typically used for company_id on various models, and in that |
468 | 181 | # case the result depends on the user associated with the object. | 224 | # case the result depends on the user associated with the object. |
469 | 182 | # We retrieve create_uid for this purpose and need to call the _defaults | 225 | # We retrieve create_uid for this purpose and need to call the _defaults |
470 | 183 | # function per resource. Otherwise, write all resources at once. | 226 | # function per resource. Otherwise, write all resources at once. |
471 | 184 | if field in obj._defaults: | 227 | if field in obj._defaults: |
472 | 185 | if not callable(obj._defaults[field]): | 228 | if not callable(obj._defaults[field]): |
473 | 186 | write_value(ids, field, obj._defaults[field]) | 229 | write_value(ids, field, obj._defaults[field]) |
474 | 230 | else: | ||
475 | 231 | # existence users is covered by foreign keys, so this is not needed | ||
476 | 232 | # 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" % | ||
477 | 233 | # (obj._table, obj._table, obj._table, obj._table, tuple(ids),)) | ||
478 | 234 | cr.execute("SELECT id, COALESCE(create_uid, 1) FROM %s " % obj._table + "WHERE id in %s", (tuple(ids),)) | ||
479 | 235 | # Execute the function once per user_id | ||
480 | 236 | user_id_map = {} | ||
481 | 237 | for row in cr.fetchall(): | ||
482 | 238 | user_id_map.setdefault(row[1], []).append(row[0]) | ||
483 | 239 | for user_id in user_id_map: | ||
484 | 240 | write_value( | ||
485 | 241 | user_id_map[user_id], field, | ||
486 | 242 | obj._defaults[field](obj, cr, user_id, None)) | ||
487 | 187 | else: | 243 | else: |
498 | 188 | # existence users is covered by foreign keys, so this is not needed | 244 | error = ("OpenUpgrade: error setting default, field %s with " |
499 | 189 | # 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" % | 245 | "None default value not in %s' _defaults" % ( |
500 | 190 | # (obj._table, obj._table, obj._table, obj._table, tuple(ids),)) | 246 | field, model)) |
501 | 191 | cr.execute("SELECT id, COALESCE(create_uid, 1) FROM %s " % obj._table + "WHERE id in %s", (tuple(ids),)) | 247 | logger.error(error) |
502 | 192 | fetchdict = dict(cr.fetchall()) | 248 | # this exeption seems to get lost in a higher up try block |
503 | 193 | for id in ids: | 249 | osv.except_osv("OpenUpgrade", error) |
494 | 194 | write_value([id], field, obj._defaults[field](obj, cr, fetchdict.get(id, 1), None)) | ||
495 | 195 | if id not in fetchdict: | ||
496 | 196 | logger.info("model %s, field %s, id %d: no create_uid defined or user does not exist anymore", | ||
497 | 197 | model, field, id) | ||
504 | 198 | else: | 250 | else: |
513 | 199 | error = ("OpenUpgrade: error setting default, field %s with " | 251 | write_value(ids, field, value) |
506 | 200 | "None default value not in %s' _defaults" % ( | ||
507 | 201 | field, model)) | ||
508 | 202 | logger.error(error) | ||
509 | 203 | # this exeption seems to get lost in a higher up try block | ||
510 | 204 | osv.except_osv("OpenUpgrade", error) | ||
511 | 205 | else: | ||
512 | 206 | write_value(ids, field, value) | ||
514 | 207 | 252 | ||
515 | 208 | def logged_query(cr, query, args=None): | 253 | def logged_query(cr, query, args=None): |
516 | 209 | if args is None: | 254 | if args is None: |
517 | @@ -257,3 +302,63 @@ | |||
518 | 257 | query = 'ALTER TABLE ir_model_fields ADD COLUMN %s %s' % ( | 302 | query = 'ALTER TABLE ir_model_fields ADD COLUMN %s %s' % ( |
519 | 258 | column) | 303 | column) |
520 | 259 | logged_query(cr, query, []) | 304 | logged_query(cr, query, []) |
521 | 305 | |||
522 | 306 | def add_module_dependencies(cr, module_list): | ||
523 | 307 | """ | ||
524 | 308 | Select (new) dependencies from the modules in the list | ||
525 | 309 | so that we can inject them into the graph at upgrade | ||
526 | 310 | time. Used in the modified OpenUpgrade Server, | ||
527 | 311 | not to be used in migration scripts | ||
528 | 312 | """ | ||
529 | 313 | if not module_list: | ||
530 | 314 | return module_list | ||
531 | 315 | cr.execute(""" | ||
532 | 316 | SELECT ir_module_module_dependency.name | ||
533 | 317 | FROM | ||
534 | 318 | ir_module_module, | ||
535 | 319 | ir_module_module_dependency | ||
536 | 320 | WHERE | ||
537 | 321 | module_id = ir_module_module.id | ||
538 | 322 | AND ir_module_module.name in %s | ||
539 | 323 | """, (tuple(module_list),)) | ||
540 | 324 | dependencies = [x[0] for x in cr.fetchall()] | ||
541 | 325 | return list(set(module_list + dependencies)) | ||
542 | 326 | |||
543 | 327 | def migrate(): | ||
544 | 328 | """ | ||
545 | 329 | This is the decorator for the migrate() function | ||
546 | 330 | in migration scripts. | ||
547 | 331 | Return when the 'version' argument is not defined, | ||
548 | 332 | and log execeptions. | ||
549 | 333 | Retrieve debug context data from the frame above for | ||
550 | 334 | logging purposes. | ||
551 | 335 | """ | ||
552 | 336 | def wrap(func): | ||
553 | 337 | def wrapped_function(cr, version): | ||
554 | 338 | stage = 'unknown' | ||
555 | 339 | module = 'unknown' | ||
556 | 340 | filename = 'unknown' | ||
557 | 341 | try: | ||
558 | 342 | frame = inspect.getargvalues(inspect.stack()[1][0]) | ||
559 | 343 | stage = frame.locals['stage'] | ||
560 | 344 | module = frame.locals['pkg'].name | ||
561 | 345 | filename = frame.locals['fp'].name | ||
562 | 346 | except Exception, e: | ||
563 | 347 | logger.error( | ||
564 | 348 | "'migrate' decorator: failed to inspect " | ||
565 | 349 | "the frame above: %s" % e) | ||
566 | 350 | pass | ||
567 | 351 | if not version: | ||
568 | 352 | return | ||
569 | 353 | logger.info( | ||
570 | 354 | "%s: %s-migration script called with version %s" % | ||
571 | 355 | (module, stage, version)) | ||
572 | 356 | try: | ||
573 | 357 | # The actual function is called here | ||
574 | 358 | func(cr, version) | ||
575 | 359 | except Exception, e: | ||
576 | 360 | logger.error( | ||
577 | 361 | "%s: error in migration script %s: %s" % | ||
578 | 362 | (module, filename, e)) | ||
579 | 363 | return wrapped_function | ||
580 | 364 | return wrap |