Merge lp:~therp-nl/openupgrade-tools/7.0-add-database_cleanup into lp:openupgrade-tools
- 7.0-add-database_cleanup
- Merge into 7.0
Status: | Work in progress |
---|---|
Proposed branch: | lp:~therp-nl/openupgrade-tools/7.0-add-database_cleanup |
Merge into: | lp:openupgrade-tools |
Diff against target: |
1054 lines (+972/-0) 15 files modified
database_cleanup/__init__.py (+1/-0) database_cleanup/__openerp__.py (+55/-0) database_cleanup/model/__init__.py (+6/-0) database_cleanup/model/purge_columns.py (+154/-0) database_cleanup/model/purge_data.py (+106/-0) database_cleanup/model/purge_models.py (+127/-0) database_cleanup/model/purge_modules.py (+91/-0) database_cleanup/model/purge_tables.py (+138/-0) database_cleanup/model/purge_wizard.py (+64/-0) database_cleanup/view/menu.xml (+48/-0) database_cleanup/view/purge_columns.xml (+37/-0) database_cleanup/view/purge_data.xml (+37/-0) database_cleanup/view/purge_models.xml (+36/-0) database_cleanup/view/purge_modules.xml (+36/-0) database_cleanup/view/purge_tables.xml (+36/-0) |
To merge this branch: | bzr merge lp:~therp-nl/openupgrade-tools/7.0-add-database_cleanup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Pedro Manuel Baeza | code review and test | Needs Fixing | |
Review via email: mp+203633@code.launchpad.net |
Commit message
Description of the change
Partial implementation of https:/
This proposal adds a module to clean up after a homebrew migration process such as OpenUpgrade.
- 2. By Stefan Rijnhart (Opener)
-
[FIX] Typo, comment
- 3. By Stefan Rijnhart (Opener)
-
[FIX] Remove unused import, add missing copyright notice
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
>
> Maybe should the purge delete the attachments or drop the relationship
> beforehand?
>
This is already what is done through these lines:
if attachment_ids:
I have to figure out what happens.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
I got it. It happens on models still in ir.model but not in the registry because the python modules are not there.
I replaced the lines
By
And it worked. Is it an acceptable solution?
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
When removing the models from ir.model, shouldn't we remove the ir.model.fields having a relation pointing to them?
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
> When removing the models from ir.model, shouldn't we remove the
> ir.model.fields having a relation pointing to them?
My proposal: https:/
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
> I got it. It happens on models still in ir.model but not in the registry
> because the python modules are not there.
>
> I replaced the lines
>
> attachment_
> cr, uid, attachment_ids, {'res_model': False},
> context=context)
> By
> cr.execute(
> "UPDATE ir_attachment SET res_model = FALSE "
> "WHERE id IN %s",
> (tuple(
>
> And it worked. Is it an acceptable solution?
- 4. By Guewen Baconnier @ Camptocamp
-
[FIX] avoid ''NoneType' object has no attribute 'exists'' error when purging models
- 5. By Guewen Baconnier @ Camptocamp
-
[IMP] Remove the fields having a relation to the purged models.
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Thank you very much, Guewen! I merged both your contributions into this branch.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
Oo I don't know what I did... But my rev5 is obviously wrong (replaced the wrong lines), here is the correct fix https:/
Sorry!
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
> Oo I don't know what I did... But my rev5 is obviously wrong (replaced the
> wrong lines), here is the correct fix https:/
> /openupgrade-
> error2/
>
> Sorry!
The one merged in 4, not 5. Decidely need holidays ;-)
- 6. By Guewen Baconnier @ Camptocamp
-
[FIX] error in previous merge
- 7. By Stefan Rijnhart (Opener)
-
[FIX] Don't remove uid field from wkf_instance, which is written in
raw SQL query (but never read afterwards). Workaround for
lp:1277899
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Apparently, base.sql comes with its own inconsistencies. Fixed in http://
- 8. By Stefan Rijnhart (Opener)
-
[FIX] Preserve dangling workflow table which is in use
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Discovered a workflow table without corresponding model that should not be purged.
- 9. By Stefan Rijnhart (Opener)
-
[RFR] Group models per table when detecting columns to purge
to prevent problems with models sharing the same table
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
After some extensive testing I fixed the case of 'sale_id' not being in stock.picking.in, thus to be purged from stock.picking. The fix allows for multiple models to share the same table in a generic way.
- 10. By Stefan Rijnhart (Opener)
-
[ADD] Allow purging of dangling data entries
- 11. By Stefan Rijnhart (Opener)
-
[FIX] Data purging now working
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
The module now allows to purge entries from ir.model.data that refer to a nonexisting row in its model's table.
- 12. By Stefan Rijnhart (Opener)
-
[IMP] Docstrings
- 13. By Stefan Rijnhart (Opener)
-
[FIX] Label
[FIX] Catch attempt to unlink field from nonexisting model - 14. By Stefan Rijnhart (Opener)
-
[RFR] Flake8
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
Hi, Stefan, finally I have tried your module and it's fantastic. For the most of the sections, it works like intended, but in "Purge obsolete tables", I find some tables that cannot be removed due to foreign keys in other tables. Use DROP CASCADE is too risky to be implemented, but you can enclose removal in a try and warns user afterwards, but continuing with tables that possibly contain the foreign key it complains about.
Also a warn that it's convenient to remove obsolete fields before purging obsolete tables is a good improvements.
One more thing: if I try to click on line button to remove tables one by one, I get line form view, but button cannot be clicked. Maybe readonly attribute?
Thanks for this excellent module.
Regards.
P.S.: Don't you think this module better fits on server-env-tools?
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Thanks for the review, Pedro! Glad you like it. I'll have a look at continuing after a drop table failed.
You can only click on the icons on individual line after you save the wizard form first. There is nothing that I can do about that, I think.
I could propose on server-env-tools, but as OpenERP 7.0 is pretty good in not leaving these remnants after module updates and uninstalls, this module is typically used after a home grown migration. We would need consensus on whether or not to adopt tools for home grown migration within OCA first. I expect such a discussion to pop up after the formalization of the association in the course of this year. Until there is a clear decision on this topic, I think these projects are better of separately.
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
Yeah, you are right about the inclusion on server-env-tools just right now.
About wizard saving before clicking on buttons, maybe you can present it on a popup or go to view mode to avoid this. Can it be done?
Regards.
Stéphane Bidoul (Acsone) (sbi) wrote : | # |
Hi Stefan,
I just stumbled upon this module. This is most useful!
It seems to work fine on 8.0, except the individual purge buttons are disabled in the lists. Not sure why yet.
Is this the latest version, or did it find a home somewhere else?
Thanks!
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
@Stéphane: glad you found this useful. Doesn't saving the wizard form first work to access the list buttons in 8.0 like it does in 7.0?
I never got round to implementing Pedro's suggestion. I could leave it and propose to server-tools 7.0 as is. It can be improved afterwards. What do you think?
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
If you don't have to work on the improvement, the module itself is valuable, so I agree that you should propose to server-tools. Please put a section TODO on the module description with the considerations for being taking into account for other possible contributors.
Stéphane Bidoul (Acsone) (sbi) wrote : | # |
@Stephan,
I indeed found that saving first solved the button issue, shame on me :)
The module is very useful as is, so I'm +1 to have it proposed to OCA/server-tools.
I have a short patch/hack to make it work on 8.0 in case anyone is interested.
Stéphane Bidoul (Acsone) (sbi) wrote : | # |
@Stefan,
My colleague Anthony has made the pull request to OCA/server-tool.
https:/
Commit authorship has been preserved. I hope you take no issue with this.
Of course, feel free to clone the git branch and re-do the PR.
Best regards,
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Hi Stéphane, thanks for picking this up. I see that I am already too late for the review, as it is already merged ;-)
Unmerged revisions
- 14. By Stefan Rijnhart (Opener)
-
[RFR] Flake8
- 13. By Stefan Rijnhart (Opener)
-
[FIX] Label
[FIX] Catch attempt to unlink field from nonexisting model - 12. By Stefan Rijnhart (Opener)
-
[IMP] Docstrings
- 11. By Stefan Rijnhart (Opener)
-
[FIX] Data purging now working
- 10. By Stefan Rijnhart (Opener)
-
[ADD] Allow purging of dangling data entries
- 9. By Stefan Rijnhart (Opener)
-
[RFR] Group models per table when detecting columns to purge
to prevent problems with models sharing the same table - 8. By Stefan Rijnhart (Opener)
-
[FIX] Preserve dangling workflow table which is in use
- 7. By Stefan Rijnhart (Opener)
-
[FIX] Don't remove uid field from wkf_instance, which is written in
raw SQL query (but never read afterwards). Workaround for
lp:1277899 - 6. By Guewen Baconnier @ Camptocamp
-
[FIX] error in previous merge
- 5. By Guewen Baconnier @ Camptocamp
-
[IMP] Remove the fields having a relation to the purged models.
Preview Diff
1 | === added directory 'database_cleanup' |
2 | === added file 'database_cleanup/__init__.py' |
3 | --- database_cleanup/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ database_cleanup/__init__.py 2014-03-14 13:27:36 +0000 |
5 | @@ -0,0 +1,1 @@ |
6 | +from . import model |
7 | |
8 | === added file 'database_cleanup/__openerp__.py' |
9 | --- database_cleanup/__openerp__.py 1970-01-01 00:00:00 +0000 |
10 | +++ database_cleanup/__openerp__.py 2014-03-14 13:27:36 +0000 |
11 | @@ -0,0 +1,55 @@ |
12 | +# -*- coding: utf-8 -*- |
13 | +############################################################################## |
14 | +# |
15 | +# OpenERP, Open Source Management Solution |
16 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
17 | +# |
18 | +# This program is free software: you can redistribute it and/or modify |
19 | +# it under the terms of the GNU Affero General Public License as |
20 | +# published by the Free Software Foundation, either version 3 of the |
21 | +# License, or (at your option) any later version. |
22 | +# |
23 | +# This program is distributed in the hope that it will be useful, |
24 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 | +# GNU Affero General Public License for more details. |
27 | +# |
28 | +# You should have received a copy of the GNU Affero General Public License |
29 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
30 | +# |
31 | +############################################################################## |
32 | + |
33 | +{ |
34 | + 'name': 'Database cleanup', |
35 | + 'version': '0.1', |
36 | + 'author': 'Therp BV', |
37 | + 'depends': ['base'], |
38 | + 'license': 'AGPL-3', |
39 | + 'category': 'Tools', |
40 | + 'data': [ |
41 | + 'view/purge_modules.xml', |
42 | + 'view/purge_models.xml', |
43 | + 'view/purge_columns.xml', |
44 | + 'view/purge_tables.xml', |
45 | + 'view/purge_data.xml', |
46 | + 'view/menu.xml', |
47 | + ], |
48 | + 'description': """\ |
49 | +Clean your OpenERP database from remnants of modules, models, columns and |
50 | +tables left by uninstalled modules (prior to 7.0) or a homebrew database |
51 | +upgrade to a new major version of OpenERP. |
52 | + |
53 | +After installation of this module, go to the Settings menu -> Technical -> |
54 | +Database cleanup. Go through the modules, models, columns and tables |
55 | +entries under this menu (in that order) and find out if there is orphaned data |
56 | +in your database. You can either delete entries by line, or sweep all entries |
57 | +in one big step (if you are *really* confident). |
58 | + |
59 | +Caution! This module is potentially harmful and can *easily* destroy the |
60 | +integrity of your data. Do not use if you are not entirely comfortable |
61 | +with the technical details of the OpenERP data model of *all* the modules |
62 | +that have ever been installed on your database, and do not purge any module, |
63 | +model, column or table if you do not know exactly what you are doing. |
64 | +""", |
65 | + |
66 | +} |
67 | |
68 | === added directory 'database_cleanup/model' |
69 | === added file 'database_cleanup/model/__init__.py' |
70 | --- database_cleanup/model/__init__.py 1970-01-01 00:00:00 +0000 |
71 | +++ database_cleanup/model/__init__.py 2014-03-14 13:27:36 +0000 |
72 | @@ -0,0 +1,6 @@ |
73 | +from . import purge_wizard |
74 | +from . import purge_modules |
75 | +from . import purge_models |
76 | +from . import purge_columns |
77 | +from . import purge_tables |
78 | +from . import purge_data |
79 | |
80 | === added file 'database_cleanup/model/purge_columns.py' |
81 | --- database_cleanup/model/purge_columns.py 1970-01-01 00:00:00 +0000 |
82 | +++ database_cleanup/model/purge_columns.py 2014-03-14 13:27:36 +0000 |
83 | @@ -0,0 +1,154 @@ |
84 | +# -*- coding: utf-8 -*- |
85 | +############################################################################## |
86 | +# |
87 | +# OpenERP, Open Source Management Solution |
88 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
89 | +# |
90 | +# This program is free software: you can redistribute it and/or modify |
91 | +# it under the terms of the GNU Affero General Public License as |
92 | +# published by the Free Software Foundation, either version 3 of the |
93 | +# License, or (at your option) any later version. |
94 | +# |
95 | +# This program is distributed in the hope that it will be useful, |
96 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
97 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
98 | +# GNU Affero General Public License for more details. |
99 | +# |
100 | +# You should have received a copy of the GNU Affero General Public License |
101 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
102 | +# |
103 | +############################################################################## |
104 | + |
105 | +from openerp.osv import orm, fields |
106 | +from openerp.tools.translate import _ |
107 | + |
108 | + |
109 | +class CleanupPurgeLineColumn(orm.TransientModel): |
110 | + _inherit = 'cleanup.purge.line' |
111 | + _name = 'cleanup.purge.line.column' |
112 | + |
113 | + _columns = { |
114 | + 'model_id': fields.many2one( |
115 | + 'ir.model', 'Model', |
116 | + required=True, ondelete='CASCADE'), |
117 | + 'wizard_id': fields.many2one( |
118 | + 'cleanup.purge.wizard.column', 'Purge Wizard', readonly=True), |
119 | + } |
120 | + |
121 | + def purge(self, cr, uid, ids, context=None): |
122 | + """ |
123 | + Unlink columns upon manual confirmation. |
124 | + """ |
125 | + for line in self.browse(cr, uid, ids, context=context): |
126 | + if line.purged: |
127 | + continue |
128 | + |
129 | + model_pool = self.pool[line.model_id.model] |
130 | + |
131 | + # Check whether the column actually still exists. |
132 | + # Inheritance such as stock.picking.in from stock.picking |
133 | + # can lead to double attempts at removal |
134 | + cr.execute( |
135 | + 'SELECT count(attname) FROM pg_attribute ' |
136 | + 'WHERE attrelid = ' |
137 | + '( SELECT oid FROM pg_class WHERE relname = %s ) ' |
138 | + 'AND attname = %s', |
139 | + (model_pool._table, line.name)) |
140 | + if not cr.fetchone()[0]: |
141 | + continue |
142 | + |
143 | + self.logger.info( |
144 | + 'Dropping column %s from table %s', |
145 | + line.name, model_pool._table) |
146 | + cr.execute( |
147 | + """ |
148 | + ALTER TABLE "%s" DROP COLUMN "%s" |
149 | + """ % (model_pool._table, line.name)) |
150 | + line.write({'purged': True}) |
151 | + cr.commit() |
152 | + return True |
153 | + |
154 | + |
155 | +class CleanupPurgeWizardColumn(orm.TransientModel): |
156 | + _inherit = 'cleanup.purge.wizard' |
157 | + _name = 'cleanup.purge.wizard.column' |
158 | + |
159 | + # List of known columns in use without corresponding fields |
160 | + # Format: {table: [fields]} |
161 | + blacklist = { |
162 | + 'wkf_instance': ['uid'], # lp:1277899 |
163 | + } |
164 | + |
165 | + def default_get(self, cr, uid, fields, context=None): |
166 | + res = super(CleanupPurgeWizardColumn, self).default_get( |
167 | + cr, uid, fields, context=context) |
168 | + if 'name' in fields: |
169 | + res['name'] = _('Purge columns') |
170 | + return res |
171 | + |
172 | + def get_orphaned_columns(self, cr, uid, model_pools, context=None): |
173 | + """ |
174 | + From openobject-server/openerp/osv/orm.py |
175 | + Iterate on the database columns to identify columns |
176 | + of fields which have been removed |
177 | + """ |
178 | + |
179 | + columns = list(set([ |
180 | + column for model_pool in model_pools |
181 | + for column in model_pool._columns |
182 | + if not (isinstance(model_pool._columns[column], fields.function) |
183 | + and not model_pool._columns[column].store) |
184 | + ])) |
185 | + columns += orm.MAGIC_COLUMNS |
186 | + columns += self.blacklist.get(model_pools[0]._table, []) |
187 | + |
188 | + cr.execute("SELECT a.attname" |
189 | + " FROM pg_class c, pg_attribute a" |
190 | + " WHERE c.relname=%s" |
191 | + " AND c.oid=a.attrelid" |
192 | + " AND a.attisdropped=%s" |
193 | + " AND pg_catalog.format_type(a.atttypid, a.atttypmod)" |
194 | + " NOT IN ('cid', 'tid', 'oid', 'xid')" |
195 | + " AND a.attname NOT IN %s", |
196 | + (model_pools[0]._table, False, tuple(columns))), |
197 | + return [column[0] for column in cr.fetchall()] |
198 | + |
199 | + def find(self, cr, uid, context=None): |
200 | + """ |
201 | + Search for columns that are not in the corresponding model. |
202 | + |
203 | + Group models by table to prevent false positives for columns |
204 | + that are only in some of the models sharing the same table. |
205 | + Example of this is 'sale_id' not being a field of stock.picking.in |
206 | + """ |
207 | + res = [] |
208 | + model_pool = self.pool['ir.model'] |
209 | + model_ids = model_pool.search(cr, uid, [], context=context) |
210 | + |
211 | + # mapping of tables to tuples (model id, [pool1, pool2, ...]) |
212 | + table2model = {} |
213 | + |
214 | + for model in model_pool.browse(cr, uid, model_ids, context=context): |
215 | + model_pool = self.pool.get(model.model) |
216 | + if not model_pool or not model_pool._auto: |
217 | + continue |
218 | + table2model.setdefault( |
219 | + model_pool._table, (model.id, []))[1].append(model_pool) |
220 | + |
221 | + for table, model_spec in table2model.iteritems(): |
222 | + for column in self.get_orphaned_columns( |
223 | + cr, uid, model_spec[1], context=context): |
224 | + res.append((0, 0, { |
225 | + 'name': column, |
226 | + 'model_id': model_spec[0]})) |
227 | + if not res: |
228 | + raise orm.except_orm( |
229 | + _('Nothing to do'), |
230 | + _('No orphaned columns found')) |
231 | + return res |
232 | + |
233 | + _columns = { |
234 | + 'purge_line_ids': fields.one2many( |
235 | + 'cleanup.purge.line.column', |
236 | + 'wizard_id', 'Columns to purge'), |
237 | + } |
238 | |
239 | === added file 'database_cleanup/model/purge_data.py' |
240 | --- database_cleanup/model/purge_data.py 1970-01-01 00:00:00 +0000 |
241 | +++ database_cleanup/model/purge_data.py 2014-03-14 13:27:36 +0000 |
242 | @@ -0,0 +1,106 @@ |
243 | +# -*- coding: utf-8 -*- |
244 | +############################################################################## |
245 | +# |
246 | +# OpenERP, Open Source Management Solution |
247 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
248 | +# |
249 | +# This program is free software: you can redistribute it and/or modify |
250 | +# it under the terms of the GNU Affero General Public License as |
251 | +# published by the Free Software Foundation, either version 3 of the |
252 | +# License, or (at your option) any later version. |
253 | +# |
254 | +# This program is distributed in the hope that it will be useful, |
255 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
256 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
257 | +# GNU Affero General Public License for more details. |
258 | +# |
259 | +# You should have received a copy of the GNU Affero General Public License |
260 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
261 | +# |
262 | +############################################################################## |
263 | + |
264 | +from openerp.osv import orm, fields |
265 | +from openerp.tools.translate import _ |
266 | + |
267 | + |
268 | +class CleanupPurgeLineData(orm.TransientModel): |
269 | + _inherit = 'cleanup.purge.line' |
270 | + _name = 'cleanup.purge.line.data' |
271 | + |
272 | + _columns = { |
273 | + 'data_id': fields.many2one( |
274 | + 'ir.model.data', 'Data entry', |
275 | + ondelete='SET NULL'), |
276 | + 'wizard_id': fields.many2one( |
277 | + 'cleanup.purge.wizard.data', 'Purge Wizard', readonly=True), |
278 | + } |
279 | + |
280 | + def purge(self, cr, uid, ids, context=None): |
281 | + """ |
282 | + Unlink data entries upon manual confirmation. |
283 | + """ |
284 | + data_ids = [] |
285 | + for line in self.browse(cr, uid, ids, context=context): |
286 | + if line.purged or not line.data_id: |
287 | + continue |
288 | + data_ids.append(line.data_id.id) |
289 | + self.logger.info('Purging data entry: %s', line.name) |
290 | + self.pool['ir.model.data'].unlink(cr, uid, data_ids, context=context) |
291 | + return self.write(cr, uid, ids, {'purged': True}, context=context) |
292 | + |
293 | + |
294 | +class CleanupPurgeWizardData(orm.TransientModel): |
295 | + _inherit = 'cleanup.purge.wizard' |
296 | + _name = 'cleanup.purge.wizard.data' |
297 | + |
298 | + def default_get(self, cr, uid, fields, context=None): |
299 | + res = super(CleanupPurgeWizardData, self).default_get( |
300 | + cr, uid, fields, context=context) |
301 | + if 'name' in fields: |
302 | + res['name'] = _('Purge data') |
303 | + return res |
304 | + |
305 | + def find(self, cr, uid, context=None): |
306 | + """ |
307 | + Collect all rows from ir_model_data that refer |
308 | + to a nonexisting model, or to a nonexisting |
309 | + row in the model's table. |
310 | + """ |
311 | + res = [] |
312 | + data_pool = self.pool['ir.model.data'] |
313 | + data_ids = [] |
314 | + unknown_models = [] |
315 | + cr.execute("""SELECT DISTINCT(model) FROM ir_model_data""") |
316 | + for (model,) in cr.fetchall(): |
317 | + if not model: |
318 | + continue |
319 | + if not self.pool.get(model): |
320 | + unknown_models.append(model) |
321 | + continue |
322 | + cr.execute( |
323 | + """ |
324 | + SELECT id FROM ir_model_data |
325 | + WHERE model = %%s |
326 | + AND res_id IS NOT NULL |
327 | + AND res_id NOT IN ( |
328 | + SELECT id FROM %s) |
329 | + """ % self.pool[model]._table, (model,)) |
330 | + data_ids += [data_row[0] for data_row in cr.fetchall()] |
331 | + data_ids += data_pool.search( |
332 | + cr, uid, [('model', 'in', unknown_models)], context=context) |
333 | + for data in data_pool.browse(cr, uid, data_ids, context=context): |
334 | + res.append((0, 0, { |
335 | + 'data_id': data.id, |
336 | + 'name': "%s.%s, object of type %s" % ( |
337 | + data.module, data.name, data.model)})) |
338 | + if not res: |
339 | + raise orm.except_orm( |
340 | + _('Nothing to do'), |
341 | + _('No orphaned data entries found')) |
342 | + return res |
343 | + |
344 | + _columns = { |
345 | + 'purge_line_ids': fields.one2many( |
346 | + 'cleanup.purge.line.data', |
347 | + 'wizard_id', 'Data to purge'), |
348 | + } |
349 | |
350 | === added file 'database_cleanup/model/purge_models.py' |
351 | --- database_cleanup/model/purge_models.py 1970-01-01 00:00:00 +0000 |
352 | +++ database_cleanup/model/purge_models.py 2014-03-14 13:27:36 +0000 |
353 | @@ -0,0 +1,127 @@ |
354 | +# -*- coding: utf-8 -*- |
355 | +############################################################################## |
356 | +# |
357 | +# OpenERP, Open Source Management Solution |
358 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
359 | +# |
360 | +# This program is free software: you can redistribute it and/or modify |
361 | +# it under the terms of the GNU Affero General Public License as |
362 | +# published by the Free Software Foundation, either version 3 of the |
363 | +# License, or (at your option) any later version. |
364 | +# |
365 | +# This program is distributed in the hope that it will be useful, |
366 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
367 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
368 | +# GNU Affero General Public License for more details. |
369 | +# |
370 | +# You should have received a copy of the GNU Affero General Public License |
371 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
372 | +# |
373 | +############################################################################## |
374 | + |
375 | +from openerp.osv import orm, fields |
376 | +from openerp.tools.translate import _ |
377 | +from openerp.addons.base.ir.ir_model import MODULE_UNINSTALL_FLAG |
378 | + |
379 | + |
380 | +class IrModel(orm.Model): |
381 | + _inherit = 'ir.model' |
382 | + |
383 | + def _drop_table(self, cr, uid, ids, context=None): |
384 | + # Allow to skip this step during model unlink |
385 | + # The super method crashes if the model cannot be instantiated |
386 | + if context and context.get('no_drop_table'): |
387 | + return True |
388 | + return super(IrModel, self)._drop_table(cr, uid, ids, context=context) |
389 | + |
390 | + |
391 | +class CleanupPurgeLineModel(orm.TransientModel): |
392 | + _inherit = 'cleanup.purge.line' |
393 | + _name = 'cleanup.purge.line.model' |
394 | + |
395 | + _columns = { |
396 | + 'wizard_id': fields.many2one( |
397 | + 'cleanup.purge.wizard.model', 'Purge Wizard', readonly=True), |
398 | + } |
399 | + |
400 | + def purge(self, cr, uid, ids, context=None): |
401 | + """ |
402 | + Unlink models upon manual confirmation. |
403 | + """ |
404 | + model_pool = self.pool['ir.model'] |
405 | + attachment_pool = self.pool['ir.attachment'] |
406 | + constraint_pool = self.pool['ir.model.constraint'] |
407 | + fields_pool = self.pool['ir.model.fields'] |
408 | + |
409 | + local_context = (context or {}).copy() |
410 | + local_context.update({ |
411 | + MODULE_UNINSTALL_FLAG: True, |
412 | + 'no_drop_table': True, |
413 | + }) |
414 | + |
415 | + for line in self.browse(cr, uid, ids, context=context): |
416 | + cr.execute( |
417 | + "SELECT id, model from ir_model WHERE model = %s", |
418 | + (line.name,)) |
419 | + row = cr.fetchone() |
420 | + if row: |
421 | + self.logger.info('Purging model %s', row[1]) |
422 | + attachment_ids = attachment_pool.search( |
423 | + cr, uid, [('res_model', '=', line.name)], context=context) |
424 | + if attachment_ids: |
425 | + cr.execute( |
426 | + "UPDATE ir_attachment SET res_model = FALSE " |
427 | + "WHERE id in %s", |
428 | + (tuple(attachment_ids), )) |
429 | + constraint_ids = constraint_pool.search( |
430 | + cr, uid, [('model', '=', line.name)], context=context) |
431 | + if constraint_ids: |
432 | + constraint_pool.unlink( |
433 | + cr, uid, constraint_ids, context=context) |
434 | + relation_ids = fields_pool.search( |
435 | + cr, uid, [('relation', '=', row[1])], context=context) |
436 | + for relation in relation_ids: |
437 | + try: |
438 | + # Fails if the model on the target side |
439 | + # cannot be instantiated |
440 | + fields_pool.unlink(cr, uid, [relation], |
441 | + context=local_context) |
442 | + except AttributeError: |
443 | + pass |
444 | + model_pool.unlink(cr, uid, [row[0]], context=local_context) |
445 | + line.write({'purged': True}) |
446 | + cr.commit() |
447 | + return True |
448 | + |
449 | + |
450 | +class CleanupPurgeWizardModel(orm.TransientModel): |
451 | + _inherit = 'cleanup.purge.wizard' |
452 | + _name = 'cleanup.purge.wizard.model' |
453 | + |
454 | + def default_get(self, cr, uid, fields, context=None): |
455 | + res = super(CleanupPurgeWizardModel, self).default_get( |
456 | + cr, uid, fields, context=context) |
457 | + if 'name' in fields: |
458 | + res['name'] = _('Purge models') |
459 | + return res |
460 | + |
461 | + def find(self, cr, uid, context=None): |
462 | + """ |
463 | + Search for models that cannot be instantiated. |
464 | + """ |
465 | + res = [] |
466 | + cr.execute("SELECT model from ir_model") |
467 | + for (model,) in cr.fetchall(): |
468 | + if not self.pool.get(model): |
469 | + res.append((0, 0, {'name': model})) |
470 | + if not res: |
471 | + raise orm.except_orm( |
472 | + _('Nothing to do'), |
473 | + _('No orphaned models found')) |
474 | + return res |
475 | + |
476 | + _columns = { |
477 | + 'purge_line_ids': fields.one2many( |
478 | + 'cleanup.purge.line.model', |
479 | + 'wizard_id', 'Models to purge'), |
480 | + } |
481 | |
482 | === added file 'database_cleanup/model/purge_modules.py' |
483 | --- database_cleanup/model/purge_modules.py 1970-01-01 00:00:00 +0000 |
484 | +++ database_cleanup/model/purge_modules.py 2014-03-14 13:27:36 +0000 |
485 | @@ -0,0 +1,91 @@ |
486 | +# -*- coding: utf-8 -*- |
487 | +############################################################################## |
488 | +# |
489 | +# OpenERP, Open Source Management Solution |
490 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
491 | +# |
492 | +# This program is free software: you can redistribute it and/or modify |
493 | +# it under the terms of the GNU Affero General Public License as |
494 | +# published by the Free Software Foundation, either version 3 of the |
495 | +# License, or (at your option) any later version. |
496 | +# |
497 | +# This program is distributed in the hope that it will be useful, |
498 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
499 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
500 | +# GNU Affero General Public License for more details. |
501 | +# |
502 | +# You should have received a copy of the GNU Affero General Public License |
503 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
504 | +# |
505 | +############################################################################## |
506 | + |
507 | +from openerp import pooler |
508 | +from openerp.osv import orm, fields |
509 | +from openerp.modules.module import get_module_path |
510 | +from openerp.tools.translate import _ |
511 | + |
512 | + |
513 | +class CleanupPurgeLineModule(orm.TransientModel): |
514 | + _inherit = 'cleanup.purge.line' |
515 | + _name = 'cleanup.purge.line.module' |
516 | + |
517 | + _columns = { |
518 | + 'wizard_id': fields.many2one( |
519 | + 'cleanup.purge.wizard.module', 'Purge Wizard', readonly=True), |
520 | + } |
521 | + |
522 | + def purge(self, cr, uid, ids, context=None): |
523 | + """ |
524 | + Uninstall modules upon manual confirmation, then reload |
525 | + the database. |
526 | + """ |
527 | + module_pool = self.pool['ir.module.module'] |
528 | + lines = self.browse(cr, uid, ids, context=context) |
529 | + module_names = [line.name for line in lines if not line.purged] |
530 | + module_ids = module_pool.search( |
531 | + cr, uid, [('name', 'in', module_names)], context=context) |
532 | + if not module_ids: |
533 | + return True |
534 | + self.logger.info('Purging modules %s', ', '.join(module_names)) |
535 | + module_pool.write( |
536 | + cr, uid, module_ids, {'state': 'to remove'}, context=context) |
537 | + cr.commit() |
538 | + _db, _pool = pooler.restart_pool(cr.dbname, update_module=True) |
539 | + module_pool.unlink(cr, uid, module_ids, context=context) |
540 | + return self.write(cr, uid, ids, {'purged': True}, context=context) |
541 | + |
542 | + |
543 | +class CleanupPurgeWizardModule(orm.TransientModel): |
544 | + _inherit = 'cleanup.purge.wizard' |
545 | + _name = 'cleanup.purge.wizard.module' |
546 | + |
547 | + def default_get(self, cr, uid, fields, context=None): |
548 | + res = super(CleanupPurgeWizardModule, self).default_get( |
549 | + cr, uid, fields, context=context) |
550 | + if 'name' in fields: |
551 | + res['name'] = _('Purge modules') |
552 | + return res |
553 | + |
554 | + def find(self, cr, uid, context=None): |
555 | + module_pool = self.pool['ir.module.module'] |
556 | + module_ids = module_pool.search(cr, uid, [], context=context) |
557 | + res = [] |
558 | + for module in module_pool.browse(cr, uid, module_ids, context=context): |
559 | + if get_module_path(module.name): |
560 | + continue |
561 | + if module.state == 'uninstalled': |
562 | + module_pool.unlink(cr, uid, module.id, context=context) |
563 | + continue |
564 | + res.append((0, 0, {'name': module.name})) |
565 | + |
566 | + if not res: |
567 | + raise orm.except_orm( |
568 | + _('Nothing to do'), |
569 | + _('No modules found to purge')) |
570 | + return res |
571 | + |
572 | + _columns = { |
573 | + 'purge_line_ids': fields.one2many( |
574 | + 'cleanup.purge.line.module', |
575 | + 'wizard_id', 'Modules to purge'), |
576 | + } |
577 | |
578 | === added file 'database_cleanup/model/purge_tables.py' |
579 | --- database_cleanup/model/purge_tables.py 1970-01-01 00:00:00 +0000 |
580 | +++ database_cleanup/model/purge_tables.py 2014-03-14 13:27:36 +0000 |
581 | @@ -0,0 +1,138 @@ |
582 | +# -*- coding: utf-8 -*- |
583 | +############################################################################## |
584 | +# |
585 | +# OpenERP, Open Source Management Solution |
586 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
587 | +# |
588 | +# This program is free software: you can redistribute it and/or modify |
589 | +# it under the terms of the GNU Affero General Public License as |
590 | +# published by the Free Software Foundation, either version 3 of the |
591 | +# License, or (at your option) any later version. |
592 | +# |
593 | +# This program is distributed in the hope that it will be useful, |
594 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
595 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
596 | +# GNU Affero General Public License for more details. |
597 | +# |
598 | +# You should have received a copy of the GNU Affero General Public License |
599 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
600 | +# |
601 | +############################################################################## |
602 | + |
603 | +from openerp.osv import orm, fields |
604 | +from openerp.tools.translate import _ |
605 | + |
606 | + |
607 | +class CleanupPurgeLineTable(orm.TransientModel): |
608 | + _inherit = 'cleanup.purge.line' |
609 | + _name = 'cleanup.purge.line.table' |
610 | + |
611 | + _columns = { |
612 | + 'wizard_id': fields.many2one( |
613 | + 'cleanup.purge.wizard.table', 'Purge Wizard', readonly=True), |
614 | + } |
615 | + |
616 | + def purge(self, cr, uid, ids, context=None): |
617 | + """ |
618 | + Unlink tables upon manual confirmation. |
619 | + """ |
620 | + lines = self.browse(cr, uid, ids, context=context) |
621 | + tables = [line.name for line in lines] |
622 | + for line in lines: |
623 | + if line.purged: |
624 | + continue |
625 | + |
626 | + # Retrieve constraints on the tables to be dropped |
627 | + # This query is referenced in numerous places |
628 | + # on the Internet but credits probably go to Tom Lane |
629 | + # in this post http://www.postgresql.org/\ |
630 | + # message-id/22895.1226088573@sss.pgh.pa.us |
631 | + # Only using the constraint name and the source table, |
632 | + # but I'm leaving the rest in for easier debugging |
633 | + cr.execute( |
634 | + """ |
635 | + SELECT conname, confrelid::regclass, af.attname AS fcol, |
636 | + conrelid::regclass, a.attname AS col |
637 | + FROM pg_attribute af, pg_attribute a, |
638 | + (SELECT conname, conrelid, confrelid,conkey[i] AS conkey, |
639 | + confkey[i] AS confkey |
640 | + FROM (select conname, conrelid, confrelid, conkey, |
641 | + confkey, generate_series(1,array_upper(conkey,1)) AS i |
642 | + FROM pg_constraint WHERE contype = 'f') ss) ss2 |
643 | + WHERE af.attnum = confkey AND af.attrelid = confrelid AND |
644 | + a.attnum = conkey AND a.attrelid = conrelid |
645 | + AND confrelid::regclass = '%s'::regclass; |
646 | + """ % line.name) |
647 | + |
648 | + for constraint in cr.fetchall(): |
649 | + if constraint[3] in tables: |
650 | + self.logger.info( |
651 | + 'Dropping constraint %s on table %s (to be dropped)', |
652 | + constraint[0], constraint[3]) |
653 | + cr.execute( |
654 | + "ALTER TABLE %s DROP CONSTRAINT %s" % ( |
655 | + constraint[3], constraint[0])) |
656 | + |
657 | + self.logger.info( |
658 | + 'Dropping table %s', line.name) |
659 | + cr.execute("DROP TABLE \"%s\"" % (line.name,)) |
660 | + line.write({'purged': True}) |
661 | + cr.commit() |
662 | + return True |
663 | + |
664 | + |
665 | +class CleanupPurgeWizardTable(orm.TransientModel): |
666 | + _inherit = 'cleanup.purge.wizard' |
667 | + _name = 'cleanup.purge.wizard.table' |
668 | + |
669 | + def default_get(self, cr, uid, fields, context=None): |
670 | + res = super(CleanupPurgeWizardTable, self).default_get( |
671 | + cr, uid, fields, context=context) |
672 | + if 'name' in fields: |
673 | + res['name'] = _('Purge tables') |
674 | + return res |
675 | + |
676 | + def find(self, cr, uid, context=None): |
677 | + """ |
678 | + Search for tables that cannot be instantiated. |
679 | + Ignore views for now. |
680 | + """ |
681 | + model_ids = self.pool['ir.model'].search(cr, uid, [], context=context) |
682 | + # Start out with known tables with no model |
683 | + known_tables = ['wkf_witm_trans'] |
684 | + for model in self.pool['ir.model'].browse( |
685 | + cr, uid, model_ids, context=context): |
686 | + |
687 | + model_pool = self.pool.get(model.model) |
688 | + if not model_pool: |
689 | + continue |
690 | + known_tables.append(model_pool._table) |
691 | + known_tables += [ |
692 | + column._sql_names(model_pool)[0] |
693 | + for column in model_pool._columns.values() |
694 | + if column._type == 'many2many' |
695 | + # unstored function fields of type m2m don't have _rel |
696 | + and hasattr(column, '_rel') |
697 | + ] |
698 | + |
699 | + # Cannot pass table names as a psycopg argument |
700 | + known_tables_repr = ",".join( |
701 | + [("'%s'" % table) for table in known_tables]) |
702 | + cr.execute( |
703 | + """ |
704 | + SELECT table_name FROM information_schema.tables |
705 | + WHERE table_schema = 'public' AND table_type = 'BASE TABLE' |
706 | + AND table_name NOT IN (%s)""" % known_tables_repr) |
707 | + |
708 | + res = [(0, 0, {'name': row[0]}) for row in cr.fetchall()] |
709 | + if not res: |
710 | + raise orm.except_orm( |
711 | + _('Nothing to do'), |
712 | + _('No orphaned tables found')) |
713 | + return res |
714 | + |
715 | + _columns = { |
716 | + 'purge_line_ids': fields.one2many( |
717 | + 'cleanup.purge.line.table', |
718 | + 'wizard_id', 'Tables to purge'), |
719 | + } |
720 | |
721 | === added file 'database_cleanup/model/purge_wizard.py' |
722 | --- database_cleanup/model/purge_wizard.py 1970-01-01 00:00:00 +0000 |
723 | +++ database_cleanup/model/purge_wizard.py 2014-03-14 13:27:36 +0000 |
724 | @@ -0,0 +1,64 @@ |
725 | +# -*- coding: utf-8 -*- |
726 | +############################################################################## |
727 | +# |
728 | +# OpenERP, Open Source Management Solution |
729 | +# This module copyright (C) 2014 Therp BV (<http://therp.nl>). |
730 | +# |
731 | +# This program is free software: you can redistribute it and/or modify |
732 | +# it under the terms of the GNU Affero General Public License as |
733 | +# published by the Free Software Foundation, either version 3 of the |
734 | +# License, or (at your option) any later version. |
735 | +# |
736 | +# This program is distributed in the hope that it will be useful, |
737 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
738 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
739 | +# GNU Affero General Public License for more details. |
740 | +# |
741 | +# You should have received a copy of the GNU Affero General Public License |
742 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
743 | +# |
744 | +############################################################################## |
745 | + |
746 | +import logging |
747 | +from openerp.osv import orm, fields |
748 | + |
749 | + |
750 | +class CleanupPurgeLine(orm.AbstractModel): |
751 | + """ Abstract base class for the purge wizard lines """ |
752 | + _name = 'cleanup.purge.line' |
753 | + _columns = { |
754 | + 'name': fields.char('Name', size=256, readonly=True), |
755 | + 'purged': fields.boolean('Purged', readonly=True), |
756 | + } |
757 | + |
758 | + logger = logging.getLogger('openerp.addons.database_cleanup') |
759 | + |
760 | + def purge(self, cr, uid, ids, context=None): |
761 | + raise NotImplementedError |
762 | + |
763 | + |
764 | +class PurgeWizard(orm.AbstractModel): |
765 | + """ Abstract base class for the purge wizards """ |
766 | + _name = 'cleanup.purge.wizard' |
767 | + |
768 | + def default_get(self, cr, uid, fields, context=None): |
769 | + res = super(PurgeWizard, self).default_get( |
770 | + cr, uid, fields, context=context) |
771 | + if 'purge_line_ids' in fields: |
772 | + res['purge_line_ids'] = self.find(cr, uid, context=None) |
773 | + return res |
774 | + |
775 | + def find(self, cr, uid, ids, context=None): |
776 | + raise NotImplementedError |
777 | + |
778 | + def purge_all(self, cr, uid, ids, context=None): |
779 | + line_pool = self.pool[self._columns['purge_line_ids']._obj] |
780 | + for wizard in self.browse(cr, uid, ids, context=context): |
781 | + line_pool.purge( |
782 | + cr, uid, [line.id for line in wizard.purge_line_ids], |
783 | + context=context) |
784 | + return True |
785 | + |
786 | + _columns = { |
787 | + 'name': fields.char('Name', size=64, readonly=True), |
788 | + } |
789 | |
790 | === added directory 'database_cleanup/static' |
791 | === added directory 'database_cleanup/static/src' |
792 | === added directory 'database_cleanup/static/src/img' |
793 | === added file 'database_cleanup/static/src/img/icon.png' |
794 | Binary files database_cleanup/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and database_cleanup/static/src/img/icon.png 2014-03-14 13:27:36 +0000 differ |
795 | === added directory 'database_cleanup/view' |
796 | === added file 'database_cleanup/view/menu.xml' |
797 | --- database_cleanup/view/menu.xml 1970-01-01 00:00:00 +0000 |
798 | +++ database_cleanup/view/menu.xml 2014-03-14 13:27:36 +0000 |
799 | @@ -0,0 +1,48 @@ |
800 | +<?xml version="1.0" encoding="utf-8"?> |
801 | +<openerp> |
802 | + <data> |
803 | + |
804 | + <record model="ir.ui.menu" id="menu_database_cleanup"> |
805 | + <field name="name">Database cleanup</field> |
806 | + <field name="sequence" eval="10" /> |
807 | + <!-- attach to Settings -> Technical --> |
808 | + <field name="parent_id" ref="base.menu_custom"/> |
809 | + </record> |
810 | + |
811 | + <record model="ir.ui.menu" id="menu_purge_modules"> |
812 | + <field name="name">Purge obsolete modules</field> |
813 | + <field name="sequence" eval="10" /> |
814 | + <field name="action" ref="action_purge_modules" /> |
815 | + <field name="parent_id" ref="menu_database_cleanup"/> |
816 | + </record> |
817 | + |
818 | + <record model="ir.ui.menu" id="menu_purge_models"> |
819 | + <field name="name">Purge obsolete models</field> |
820 | + <field name="sequence" eval="20" /> |
821 | + <field name="action" ref="action_purge_models" /> |
822 | + <field name="parent_id" ref="menu_database_cleanup"/> |
823 | + </record> |
824 | + |
825 | + <record model="ir.ui.menu" id="menu_purge_columns"> |
826 | + <field name="name">Purge obsolete columns</field> |
827 | + <field name="sequence" eval="30" /> |
828 | + <field name="action" ref="action_purge_columns" /> |
829 | + <field name="parent_id" ref="menu_database_cleanup"/> |
830 | + </record> |
831 | + |
832 | + <record model="ir.ui.menu" id="menu_purge_tables"> |
833 | + <field name="name">Purge obsolete tables</field> |
834 | + <field name="sequence" eval="40" /> |
835 | + <field name="action" ref="action_purge_tables" /> |
836 | + <field name="parent_id" ref="menu_database_cleanup"/> |
837 | + </record> |
838 | + |
839 | + <record model="ir.ui.menu" id="menu_purge_data"> |
840 | + <field name="name">Purge obsolete data entries</field> |
841 | + <field name="sequence" eval="50" /> |
842 | + <field name="action" ref="action_purge_data" /> |
843 | + <field name="parent_id" ref="menu_database_cleanup"/> |
844 | + </record> |
845 | + |
846 | + </data> |
847 | +</openerp> |
848 | |
849 | === added file 'database_cleanup/view/purge_columns.xml' |
850 | --- database_cleanup/view/purge_columns.xml 1970-01-01 00:00:00 +0000 |
851 | +++ database_cleanup/view/purge_columns.xml 2014-03-14 13:27:36 +0000 |
852 | @@ -0,0 +1,37 @@ |
853 | +<?xml version="1.0" encoding="utf-8"?> |
854 | +<openerp> |
855 | + <data> |
856 | + |
857 | + <record id="purge_columns_view" model="ir.ui.view"> |
858 | + <field name="name">Form view for purge columns wizard</field> |
859 | + <field name="model">cleanup.purge.wizard.column</field> |
860 | + <field name="arch" type="xml"> |
861 | + <form string="Purge columns" version="7.0"> |
862 | + <h1> |
863 | + <field name="name"/> |
864 | + </h1> |
865 | + <button type="object" name="purge_all" string="Purge all columns" /> |
866 | + <field name="purge_line_ids" colspan="4" nolabel="1"> |
867 | + <tree string="Purge columns"> |
868 | + <field name="name" /> |
869 | + <field name="model_id" /> |
870 | + <field name="purged" invisible="0" /> |
871 | + <button type="object" name="purge" |
872 | + icon="gtk-cancel" string="Purge this column" |
873 | + attrs="{'invisible': [('purged', '=', True)]}"/> |
874 | + </tree> |
875 | + </field> |
876 | + </form> |
877 | + </field> |
878 | + </record> |
879 | + |
880 | + <record id="action_purge_columns" model="ir.actions.act_window"> |
881 | + <field name="name">Purge columns</field> |
882 | + <field name="type">ir.actions.act_window</field> |
883 | + <field name="res_model">cleanup.purge.wizard.column</field> |
884 | + <field name="view_type">form</field> |
885 | + <field name="view_mode">form</field> |
886 | + </record> |
887 | + |
888 | + </data> |
889 | +</openerp> |
890 | |
891 | === added file 'database_cleanup/view/purge_data.xml' |
892 | --- database_cleanup/view/purge_data.xml 1970-01-01 00:00:00 +0000 |
893 | +++ database_cleanup/view/purge_data.xml 2014-03-14 13:27:36 +0000 |
894 | @@ -0,0 +1,37 @@ |
895 | +<?xml version="1.0" encoding="utf-8"?> |
896 | +<openerp> |
897 | + <data> |
898 | + |
899 | + <record id="purge_data_view" model="ir.ui.view"> |
900 | + <field name="name">Form view for purge data wizard</field> |
901 | + <field name="model">cleanup.purge.wizard.data</field> |
902 | + <field name="arch" type="xml"> |
903 | + <form string="Purge data entries that refer to missing resources" version="7.0"> |
904 | + <h1> |
905 | + <field name="name"/> |
906 | + </h1> |
907 | + <button type="object" name="purge_all" string="Purge all data" /> |
908 | + <field name="purge_line_ids" colspan="4" nolabel="1"> |
909 | + <tree string="Purge data"> |
910 | + <field name="name" /> |
911 | + <field name="data_id" /> |
912 | + <field name="purged" invisible="0" /> |
913 | + <button type="object" name="purge" |
914 | + icon="gtk-cancel" string="Purge this data" |
915 | + attrs="{'invisible': [('purged', '=', True)]}"/> |
916 | + </tree> |
917 | + </field> |
918 | + </form> |
919 | + </field> |
920 | + </record> |
921 | + |
922 | + <record id="action_purge_data" model="ir.actions.act_window"> |
923 | + <field name="name">Purge data entries that refer to missing resources</field> |
924 | + <field name="type">ir.actions.act_window</field> |
925 | + <field name="res_model">cleanup.purge.wizard.data</field> |
926 | + <field name="view_type">form</field> |
927 | + <field name="view_mode">form</field> |
928 | + </record> |
929 | + |
930 | + </data> |
931 | +</openerp> |
932 | |
933 | === added file 'database_cleanup/view/purge_models.xml' |
934 | --- database_cleanup/view/purge_models.xml 1970-01-01 00:00:00 +0000 |
935 | +++ database_cleanup/view/purge_models.xml 2014-03-14 13:27:36 +0000 |
936 | @@ -0,0 +1,36 @@ |
937 | +<?xml version="1.0" encoding="utf-8"?> |
938 | +<openerp> |
939 | + <data> |
940 | + |
941 | + <record id="purge_models_view" model="ir.ui.view"> |
942 | + <field name="name">Form view for purge models wizard</field> |
943 | + <field name="model">cleanup.purge.wizard.model</field> |
944 | + <field name="arch" type="xml"> |
945 | + <form string="Purge models" version="7.0"> |
946 | + <h1> |
947 | + <field name="name"/> |
948 | + </h1> |
949 | + <button type="object" name="purge_all" string="Purge all models" /> |
950 | + <field name="purge_line_ids" colspan="4" nolabel="1"> |
951 | + <tree string="Purge models"> |
952 | + <field name="name" /> |
953 | + <field name="purged" invisible="0" /> |
954 | + <button type="object" name="purge" |
955 | + icon="gtk-cancel" string="Purge this model" |
956 | + attrs="{'invisible': [('purged', '=', True)]}"/> |
957 | + </tree> |
958 | + </field> |
959 | + </form> |
960 | + </field> |
961 | + </record> |
962 | + |
963 | + <record id="action_purge_models" model="ir.actions.act_window"> |
964 | + <field name="name">Purge models</field> |
965 | + <field name="type">ir.actions.act_window</field> |
966 | + <field name="res_model">cleanup.purge.wizard.model</field> |
967 | + <field name="view_type">form</field> |
968 | + <field name="view_mode">form</field> |
969 | + </record> |
970 | + |
971 | + </data> |
972 | +</openerp> |
973 | |
974 | === added file 'database_cleanup/view/purge_modules.xml' |
975 | --- database_cleanup/view/purge_modules.xml 1970-01-01 00:00:00 +0000 |
976 | +++ database_cleanup/view/purge_modules.xml 2014-03-14 13:27:36 +0000 |
977 | @@ -0,0 +1,36 @@ |
978 | +<?xml version="1.0" encoding="utf-8"?> |
979 | +<openerp> |
980 | + <data> |
981 | + |
982 | + <record id="purge_modules_view" model="ir.ui.view"> |
983 | + <field name="name">Form view for purge modules wizard</field> |
984 | + <field name="model">cleanup.purge.wizard.module</field> |
985 | + <field name="arch" type="xml"> |
986 | + <form string="Purge modules" version="7.0"> |
987 | + <h1> |
988 | + <field name="name"/> |
989 | + </h1> |
990 | + <button type="object" name="purge_all" string="Purge all modules" /> |
991 | + <field name="purge_line_ids" colspan="4" nolabel="1"> |
992 | + <tree string="Purge modules"> |
993 | + <field name="name" /> |
994 | + <field name="purged" invisible="0" /> |
995 | + <button type="object" name="purge" |
996 | + icon="gtk-cancel" string="Purge this module" |
997 | + attrs="{'invisible': [('purged', '=', True)]}"/> |
998 | + </tree> |
999 | + </field> |
1000 | + </form> |
1001 | + </field> |
1002 | + </record> |
1003 | + |
1004 | + <record id="action_purge_modules" model="ir.actions.act_window"> |
1005 | + <field name="name">Purge modules</field> |
1006 | + <field name="type">ir.actions.act_window</field> |
1007 | + <field name="res_model">cleanup.purge.wizard.module</field> |
1008 | + <field name="view_type">form</field> |
1009 | + <field name="view_mode">form</field> |
1010 | + </record> |
1011 | + |
1012 | + </data> |
1013 | +</openerp> |
1014 | |
1015 | === added file 'database_cleanup/view/purge_tables.xml' |
1016 | --- database_cleanup/view/purge_tables.xml 1970-01-01 00:00:00 +0000 |
1017 | +++ database_cleanup/view/purge_tables.xml 2014-03-14 13:27:36 +0000 |
1018 | @@ -0,0 +1,36 @@ |
1019 | +<?xml version="1.0" encoding="utf-8"?> |
1020 | +<openerp> |
1021 | + <data> |
1022 | + |
1023 | + <record id="purge_tables_view" model="ir.ui.view"> |
1024 | + <field name="name">Form view for purge tables wizard</field> |
1025 | + <field name="model">cleanup.purge.wizard.table</field> |
1026 | + <field name="arch" type="xml"> |
1027 | + <form string="Purge tables" version="7.0"> |
1028 | + <h1> |
1029 | + <field name="name"/> |
1030 | + </h1> |
1031 | + <button type="object" name="purge_all" string="Purge all tables" /> |
1032 | + <field name="purge_line_ids" colspan="4" nolabel="1"> |
1033 | + <tree string="Purge tables"> |
1034 | + <field name="name" /> |
1035 | + <field name="purged" invisible="0" /> |
1036 | + <button type="object" name="purge" |
1037 | + icon="gtk-cancel" string="Purge this table" |
1038 | + attrs="{'invisible': [('purged', '=', True)]}"/> |
1039 | + </tree> |
1040 | + </field> |
1041 | + </form> |
1042 | + </field> |
1043 | + </record> |
1044 | + |
1045 | + <record id="action_purge_tables" model="ir.actions.act_window"> |
1046 | + <field name="name">Purge tables</field> |
1047 | + <field name="type">ir.actions.act_window</field> |
1048 | + <field name="res_model">cleanup.purge.wizard.table</field> |
1049 | + <field name="view_type">form</field> |
1050 | + <field name="view_mode">form</field> |
1051 | + </record> |
1052 | + |
1053 | + </data> |
1054 | +</openerp> |
Hi Stefan,
Thanks for this handy module!
I had an error when purging a model having attachments (poweremail. mailbox) . Not sure if this issue lies in the ir.attachment code or the purge though.
In openerp/ addons/ base/ir/ ir_attachment. py the method check() calls exists on the deleted model.
mids = self.pool. get(model) .exists( cr, uid, mids)
ima. check(cr, uid, model, mode)
self. pool.get( model). check_access_ rule(cr, uid, mids, mode, context=context)
Maybe should the purge delete the attachments or drop the relationship beforehand?
Traceback (most recent call last): gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ netsvc. py", line 292, in dispatch_rpc getService( service_ name).dispatch( method, params) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ service/ web_services. py", line 626, in dispatch gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ osv/osv. py", line 190, in execute_kw gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ osv/osv. py", line 132, in wrapper gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ osv/osv. py", line 199, in execute gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ osv/osv. py", line 187, in execute_cr gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/openupgra de-tools/ database_ cleanup/ model/purge_ wizard. py", line 58, in purge_all context) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/openupgra de-tools/ database_ cleanup/ model/purge_ models. py", line 73, in purge context) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/addons/ base_calendar/ crm_meeting. py", line 160, in write attachment, self).write(cr, uid, ids, vals, context=context) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/addons/ document/ document. py", line 137, in write file, self).write(cr, uid, ids, vals, context) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ addons/ base/ir/ ir_attachment. py", line 277, in write gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/addons/ document/ document. py", line 77, in check document_ file, self).check(cr, uid, ids, mode, context=context, values=values) gbaconnier/ code/instances/ openerp_ foobar/ trunk7/ parts/server/ openerp/ addons/ base/ir/ ir_attachment. py", line 211, in check get(model) .exists( cr, uid, mids)
File "/home/
result = ExportService.
File "/home/
res = fn(db, uid, *params)
File "/home/
return self.execute(db, uid, obj, method, *args, **kw or {})
File "/home/
return f(self, dbname, *args, **kwargs)
File "/home/
res = self.execute_cr(cr, uid, obj, method, *args, **kw)
File "/home/
return getattr(object, method)(cr, uid, *args, **kw)
File "/home/
context=
File "/home/
context=
File "/home/
return super(ir_
File "/home/
return super(document_
File "/home/
self.check(cr, uid, ids, 'write', context=context, values=vals)
File "/home/
super(
File "/home/
mids = self.pool.
AttributeError: 'NoneType' object has no attribute 'exists'