[IMP] database_cleanup: black, isort, prettier

pull/2684/head
Stephane Mangin 2021-01-12 14:15:12 +01:00 committed by Miika Nissi
parent 5d0be1e42e
commit d1acfa4b4a
No known key found for this signature in database
GPG Key ID: B20DC9FCFAF92E7F
22 changed files with 649 additions and 466 deletions

View File

@ -1,23 +1,23 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # Copyright 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Database cleanup', "name": "Database cleanup",
'version': '12.0.1.0.1', "version": "12.0.1.0.1",
'author': "Therp BV,Odoo Community Association (OCA)", "author": "Therp BV,Odoo Community Association (OCA)",
'depends': ['base'], "depends": ["base"],
'license': 'AGPL-3', "license": "AGPL-3",
'category': 'Tools', "category": "Tools",
'data': [ "data": [
"views/purge_wizard.xml", "views/purge_wizard.xml",
'views/purge_menus.xml', "views/purge_menus.xml",
'views/purge_modules.xml', "views/purge_modules.xml",
'views/purge_models.xml', "views/purge_models.xml",
'views/purge_columns.xml', "views/purge_columns.xml",
'views/purge_tables.xml', "views/purge_tables.xml",
'views/purge_data.xml', "views/purge_data.xml",
"views/create_indexes.xml", "views/create_indexes.xml",
'views/purge_properties.xml', "views/purge_properties.xml",
'views/menu.xml', "views/menu.xml",
], ],
'installable': True, "installable": True,
} }

View File

@ -14,11 +14,9 @@ class IdentifierAdapter(ISQLQuote):
def getquoted(self): def getquoted(self):
def is_identifier_char(c): def is_identifier_char(c):
return c.isalnum() or c in ['_', '$'] return c.isalnum() or c in ["_", "$"]
format_string = '"%s"' format_string = '"%s"'
if not self.quote: if not self.quote:
format_string = '%s' format_string = "%s"
return format_string % ''.join( return format_string % "".join(filter(is_identifier_char, self.identifier))
filter(is_identifier_char, self.identifier)
)

View File

@ -1,26 +1,27 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
from ..identifier_adapter import IdentifierAdapter
from odoo import api, fields, models from odoo import api, fields, models
from ..identifier_adapter import IdentifierAdapter
class CreateIndexesLine(models.TransientModel): class CreateIndexesLine(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.create_indexes.line' _name = "cleanup.create_indexes.line"
purged = fields.Boolean('Created') purged = fields.Boolean("Created")
wizard_id = fields.Many2one('cleanup.create_indexes.wizard') wizard_id = fields.Many2one("cleanup.create_indexes.wizard")
field_id = fields.Many2one('ir.model.fields', required=True) field_id = fields.Many2one("ir.model.fields", required=True)
@api.multi @api.multi
def purge(self): def purge(self):
tables = set() tables = set()
for field in self.mapped('field_id'): for field in self.mapped("field_id"):
model = self.env[field.model] model = self.env[field.model]
name = '%s_%s_index' % (model._table, field.name) name = "{}_{}_index".format(model._table, field.name)
self.env.cr.execute( self.env.cr.execute(
'create index %s ON %s (%s)', "create index %s ON %s (%s)",
( (
IdentifierAdapter(name, quote=False), IdentifierAdapter(name, quote=False),
IdentifierAdapter(model._table), IdentifierAdapter(model._table),
@ -29,54 +30,66 @@ class CreateIndexesLine(models.TransientModel):
) )
tables.add(model._table) tables.add(model._table)
for table in tables: for table in tables:
self.env.cr.execute( self.env.cr.execute("analyze %s", (IdentifierAdapter(model._table),))
'analyze %s', (IdentifierAdapter(model._table),) self.write(
{
"purged": True,
}
) )
self.write({
'purged': True,
})
class CreateIndexesWizard(models.TransientModel): class CreateIndexesWizard(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.create_indexes.wizard' _name = "cleanup.create_indexes.wizard"
_description = 'Create indexes' _description = "Create indexes"
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.create_indexes.line', 'wizard_id', "cleanup.create_indexes.line",
"wizard_id",
) )
@api.multi @api.multi
def find(self): def find(self):
res = list() res = list()
for field in self.env['ir.model.fields'].search([ for field in self.env["ir.model.fields"].search(
('index', '=', True), [
]): ("index", "=", True),
]
):
if field.model not in self.env.registry: if field.model not in self.env.registry:
continue continue
model = self.env[field.model] model = self.env[field.model]
name = '%s_%s_index' % (model._table, field.name) name = "{}_{}_index".format(model._table, field.name)
self.env.cr.execute( self.env.cr.execute(
'select indexname from pg_indexes ' "select indexname from pg_indexes "
'where indexname=%s and tablename=%s', "where indexname=%s and tablename=%s",
(name, model._table) (name, model._table),
) )
if self.env.cr.rowcount: if self.env.cr.rowcount:
continue continue
self.env.cr.execute( self.env.cr.execute(
'select a.attname ' "select a.attname "
'from pg_attribute a ' "from pg_attribute a "
'join pg_class c on a.attrelid=c.oid ' "join pg_class c on a.attrelid=c.oid "
'join pg_tables t on t.tablename=c.relname ' "join pg_tables t on t.tablename=c.relname "
'where attname=%s and c.relname=%s', "where attname=%s and c.relname=%s",
(field.name, model._table,) (
field.name,
model._table,
),
) )
if not self.env.cr.rowcount: if not self.env.cr.rowcount:
continue continue
res.append((0, 0, { res.append(
'name': '%s.%s' % (field.model, field.name), (
'field_id': field.id, 0,
})) 0,
{
"name": "{}.{}".format(field.model, field.name),
"field_id": field.id,
},
)
)
return res return res

View File

@ -3,18 +3,19 @@
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineColumn(models.TransientModel): class CleanupPurgeLineColumn(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.column' _name = "cleanup.purge.line.column"
_description = 'Purge Column Wizard Lines' _description = "Purge Column Wizard Lines"
model_id = fields.Many2one('ir.model', 'Model', required=True, model_id = fields.Many2one("ir.model", "Model", required=True, ondelete="CASCADE")
ondelete='CASCADE')
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.column', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.column", "Purge Wizard", readonly=True
)
@api.multi @api.multi
def purge(self): def purge(self):
@ -24,8 +25,9 @@ class CleanupPurgeLineColumn(models.TransientModel):
if self: if self:
objs = self objs = self
else: else:
objs = self.env['cleanup.purge.line.column']\ objs = self.env["cleanup.purge.line.column"].browse(
.browse(self._context.get('active_ids')) self._context.get("active_ids")
)
for line in objs: for line in objs:
if line.purged: if line.purged:
continue continue
@ -34,24 +36,23 @@ class CleanupPurgeLineColumn(models.TransientModel):
# Inheritance such as stock.picking.in from stock.picking # Inheritance such as stock.picking.in from stock.picking
# can lead to double attempts at removal # can lead to double attempts at removal
self.env.cr.execute( self.env.cr.execute(
'SELECT count(attname) FROM pg_attribute ' "SELECT count(attname) FROM pg_attribute "
'WHERE attrelid = ' "WHERE attrelid = "
'( SELECT oid FROM pg_class WHERE relname = %s ) ' "( SELECT oid FROM pg_class WHERE relname = %s ) "
'AND attname = %s', "AND attname = %s",
(model_pool._table, line.name)) (model_pool._table, line.name),
)
if not self.env.cr.fetchone()[0]: if not self.env.cr.fetchone()[0]:
continue continue
self.logger.info( self.logger.info(
'Dropping column %s from table %s', "Dropping column %s from table %s", line.name, model_pool._table
line.name, model_pool._table) )
self.env.cr.execute( self.env.cr.execute(
'ALTER TABLE %s DROP COLUMN %s', "ALTER TABLE %s DROP COLUMN %s",
( (IdentifierAdapter(model_pool._table), IdentifierAdapter(line.name)),
IdentifierAdapter(model_pool._table), )
IdentifierAdapter(line.name) line.write({"purged": True})
))
line.write({'purged': True})
# we need this commit because the ORM will deadlock if # we need this commit because the ORM will deadlock if
# we still have a pending transaction # we still have a pending transaction
self.env.cr.commit() # pylint: disable=invalid-commit self.env.cr.commit() # pylint: disable=invalid-commit
@ -59,15 +60,15 @@ class CleanupPurgeLineColumn(models.TransientModel):
class CleanupPurgeWizardColumn(models.TransientModel): class CleanupPurgeWizardColumn(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.column' _name = "cleanup.purge.wizard.column"
_description = 'Purge columns' _description = "Purge columns"
# List of known columns in use without corresponding fields # List of known columns in use without corresponding fields
# Format: {table: [fields]} # Format: {table: [fields]}
blacklist = { blacklist = {
'wkf_instance': ['uid'], # lp:1277899 "wkf_instance": ["uid"], # lp:1277899
'res_users': ['password', 'password_crypt'], "res_users": ["password", "password_crypt"],
} }
@api.model @api.model
@ -77,12 +78,14 @@ class CleanupPurgeWizardColumn(models.TransientModel):
Iterate on the database columns to identify columns Iterate on the database columns to identify columns
of fields which have been removed of fields which have been removed
""" """
columns = list(set([ columns = list(
{
column.name column.name
for model_pool in model_pools for model_pool in model_pools
for column in model_pool._fields.values() for column in model_pool._fields.values()
if not (column.compute is not None and not column.store) if not (column.compute is not None and not column.store)
])) }
)
columns += models.MAGIC_COLUMNS columns += models.MAGIC_COLUMNS
columns += self.blacklist.get(model_pools[0]._table, []) columns += self.blacklist.get(model_pools[0]._table, [])
@ -92,7 +95,8 @@ class CleanupPurgeWizardColumn(models.TransientModel):
"AND pg_catalog.format_type(a.atttypid, a.atttypmod) " "AND pg_catalog.format_type(a.atttypid, a.atttypmod) "
"NOT IN ('cid', 'tid', 'oid', 'xid') " "NOT IN ('cid', 'tid', 'oid', 'xid') "
"AND a.attname NOT IN %s", "AND a.attname NOT IN %s",
(model_pools[0]._table, tuple(columns))) (model_pools[0]._table, tuple(columns)),
)
return [column for column, in self.env.cr.fetchall()] return [column for column, in self.env.cr.fetchall()]
@api.model @api.model
@ -109,24 +113,23 @@ class CleanupPurgeWizardColumn(models.TransientModel):
# mapping of tables to tuples (model id, [pool1, pool2, ...]) # mapping of tables to tuples (model id, [pool1, pool2, ...])
table2model = {} table2model = {}
for model in self.env['ir.model'].search([]): for model in self.env["ir.model"].search([]):
if model.model not in self.env: if model.model not in self.env:
continue continue
model_pool = self.env[model.model] model_pool = self.env[model.model]
if not model_pool._auto: if not model_pool._auto:
continue continue
table2model.setdefault( table2model.setdefault(model_pool._table, (model.id, []))[1].append(
model_pool._table, (model.id, []) model_pool
)[1].append(model_pool) )
for table, model_spec in table2model.items(): for table, model_spec in table2model.items():
for column in self.get_orphaned_columns(model_spec[1]): for column in self.get_orphaned_columns(model_spec[1]):
res.append((0, 0, { res.append((0, 0, {"name": column, "model_id": model_spec[0]}))
'name': column,
'model_id': model_spec[0]}))
if not res: if not res:
raise UserError(_('No orphaned columns found')) raise UserError(_("No orphaned columns found"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.column', 'wizard_id', 'Columns to purge') "cleanup.purge.line.column", "wizard_id", "Columns to purge"
)

View File

@ -2,16 +2,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineData(models.TransientModel): class CleanupPurgeLineData(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.data' _name = "cleanup.purge.line.data"
data_id = fields.Many2one('ir.model.data', 'Data entry') data_id = fields.Many2one("ir.model.data", "Data entry")
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.data', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.data", "Purge Wizard", readonly=True
)
@api.multi @api.multi
def purge(self): def purge(self):
@ -19,18 +21,19 @@ class CleanupPurgeLineData(models.TransientModel):
if self: if self:
objs = self objs = self
else: else:
objs = self.env['cleanup.purge.line.data']\ objs = self.env["cleanup.purge.line.data"].browse(
.browse(self._context.get('active_ids')) self._context.get("active_ids")
)
to_unlink = objs.filtered(lambda x: not x.purged and x.data_id) to_unlink = objs.filtered(lambda x: not x.purged and x.data_id)
self.logger.info('Purging data entries: %s', to_unlink.mapped('name')) self.logger.info("Purging data entries: %s", to_unlink.mapped("name"))
to_unlink.mapped('data_id').unlink() to_unlink.mapped("data_id").unlink()
return to_unlink.write({'purged': True}) return to_unlink.write({"purged": True})
class CleanupPurgeWizardData(models.TransientModel): class CleanupPurgeWizardData(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.data' _name = "cleanup.purge.wizard.data"
_description = 'Purge data' _description = "Purge data"
@api.model @api.model
def find(self): def find(self):
@ -41,7 +44,7 @@ class CleanupPurgeWizardData(models.TransientModel):
data_ids = [] data_ids = []
unknown_models = [] unknown_models = []
self.env.cr.execute("""SELECT DISTINCT(model) FROM ir_model_data""") self.env.cr.execute("""SELECT DISTINCT(model) FROM ir_model_data""")
for model, in self.env.cr.fetchall(): for (model,) in self.env.cr.fetchall():
if not model: if not model:
continue continue
if model not in self.env: if model not in self.env:
@ -54,19 +57,35 @@ class CleanupPurgeWizardData(models.TransientModel):
AND res_id IS NOT NULL AND res_id IS NOT NULL
AND NOT EXISTS ( AND NOT EXISTS (
SELECT id FROM %s WHERE id=ir_model_data.res_id) SELECT id FROM %s WHERE id=ir_model_data.res_id)
""", (model, IdentifierAdapter(self.env[model]._table))) """,
(model, IdentifierAdapter(self.env[model]._table)),
)
data_ids.extend(data_row for data_row, in self.env.cr.fetchall()) data_ids.extend(data_row for data_row, in self.env.cr.fetchall())
data_ids += self.env['ir.model.data'].search([ data_ids += (
('model', 'in', unknown_models), self.env["ir.model.data"]
]).ids .search(
for data in self.env['ir.model.data'].browse(data_ids): [
res.append((0, 0, { ("model", "in", unknown_models),
'data_id': data.id, ]
'name': "%s.%s, object of type %s" % ( )
data.module, data.name, data.model)})) .ids
)
for data in self.env["ir.model.data"].browse(data_ids):
res.append(
(
0,
0,
{
"data_id": data.id,
"name": "%s.%s, object of type %s"
% (data.module, data.name, data.model),
},
)
)
if not res: if not res:
raise UserError(_('No orphaned data entries found')) raise UserError(_("No orphaned data entries found"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.data', 'wizard_id', 'Data to purge') "cleanup.purge.line.data", "wizard_id", "Data to purge"
)

View File

@ -6,12 +6,13 @@ from odoo.exceptions import UserError
class CleanupPurgeLineMenu(models.TransientModel): class CleanupPurgeLineMenu(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.menu' _name = "cleanup.purge.line.menu"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.menu', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.menu", "Purge Wizard", readonly=True
menu_id = fields.Many2one('ir.ui.menu', 'Menu entry') )
menu_id = fields.Many2one("ir.ui.menu", "Menu entry")
@api.multi @api.multi
def purge(self): def purge(self):
@ -19,18 +20,19 @@ class CleanupPurgeLineMenu(models.TransientModel):
if self: if self:
objs = self objs = self
else: else:
objs = self.env['cleanup.purge.line.menu']\ objs = self.env["cleanup.purge.line.menu"].browse(
.browse(self._context.get('active_ids')) self._context.get("active_ids")
)
to_unlink = objs.filtered(lambda x: not x.purged and x.menu_id) to_unlink = objs.filtered(lambda x: not x.purged and x.menu_id)
self.logger.info('Purging menu entries: %s', to_unlink.mapped('name')) self.logger.info("Purging menu entries: %s", to_unlink.mapped("name"))
to_unlink.mapped('menu_id').unlink() to_unlink.mapped("menu_id").unlink()
return to_unlink.write({'purged': True}) return to_unlink.write({"purged": True})
class CleanupPurgeWizardMenu(models.TransientModel): class CleanupPurgeWizardMenu(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.menu' _name = "cleanup.purge.wizard.menu"
_description = 'Purge menus' _description = "Purge menus"
@api.model @api.model
def find(self): def find(self):
@ -38,21 +40,30 @@ class CleanupPurgeWizardMenu(models.TransientModel):
Search for models that cannot be instantiated. Search for models that cannot be instantiated.
""" """
res = [] res = []
for menu in self.env['ir.ui.menu'].with_context(active_test=False)\ for menu in (
.search([('action', '!=', False)]): self.env["ir.ui.menu"]
if menu.action.type != 'ir.actions.act_window': .with_context(active_test=False)
.search([("action", "!=", False)])
):
if menu.action.type != "ir.actions.act_window":
continue continue
if (menu.action.res_model and menu.action.res_model not in if (menu.action.res_model and menu.action.res_model not in self.env) or (
self.env) or \ menu.action.src_model and menu.action.src_model not in self.env
(menu.action.src_model and menu.action.src_model not in ):
self.env): res.append(
res.append((0, 0, { (
'name': menu.complete_name, 0,
'menu_id': menu.id, 0,
})) {
"name": menu.complete_name,
"menu_id": menu.id,
},
)
)
if not res: if not res:
raise UserError(_('No dangling menu entries found')) raise UserError(_("No dangling menu entries found"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.menu', 'wizard_id', 'Menus to purge') "cleanup.purge.line.menu", "wizard_id", "Menus to purge"
)

View File

@ -1,13 +1,14 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # Copyright 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
from odoo import _, api, models, fields from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
class IrModel(models.Model): class IrModel(models.Model):
_inherit = 'ir.model' _inherit = "ir.model"
def _drop_table(self): def _drop_table(self):
"""this function crashes for undefined models""" """this function crashes for undefined models"""
@ -22,7 +23,7 @@ class IrModel(models.Model):
class IrModelFields(models.Model): class IrModelFields(models.Model):
_inherit = 'ir.model.fields' _inherit = "ir.model.fields"
@api.multi @api.multi
def _prepare_update(self): def _prepare_update(self):
@ -32,12 +33,13 @@ class IrModelFields(models.Model):
class CleanupPurgeLineModel(models.TransientModel): class CleanupPurgeLineModel(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.model' _name = "cleanup.purge.line.model"
_description = 'Purge models' _description = "Purge models"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.model', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.model", "Purge Wizard", readonly=True
)
@api.multi @api.multi
def purge(self): def purge(self):
@ -46,36 +48,45 @@ class CleanupPurgeLineModel(models.TransientModel):
""" """
context_flags = { context_flags = {
MODULE_UNINSTALL_FLAG: True, MODULE_UNINSTALL_FLAG: True,
'purge': True, "purge": True,
} }
if self: if self:
objs = self objs = self
else: else:
objs = self.env['cleanup.purge.line.model']\ objs = self.env["cleanup.purge.line.model"].browse(
.browse(self._context.get('active_ids')) self._context.get("active_ids")
)
for line in objs: for line in objs:
self.env.cr.execute( self.env.cr.execute(
"SELECT id, model from ir_model WHERE model = %s", "SELECT id, model from ir_model WHERE model = %s", (line.name,)
(line.name,)) )
row = self.env.cr.fetchone() row = self.env.cr.fetchone()
if not row: if not row:
continue continue
self.logger.info('Purging model %s', row[1]) self.logger.info("Purging model %s", row[1])
attachments = self.env['ir.attachment'].search([ attachments = self.env["ir.attachment"].search(
('res_model', '=', line.name) [("res_model", "=", line.name)]
]) )
if attachments: if attachments:
self.env.cr.execute( self.env.cr.execute(
"UPDATE ir_attachment SET res_model = NULL " "UPDATE ir_attachment SET res_model = NULL " "WHERE id in %s",
"WHERE id in %s", (tuple(attachments.ids),),
(tuple(attachments.ids), )) )
self.env['ir.model.constraint'].search([ self.env["ir.model.constraint"].search(
('model', '=', line.name), [
]).unlink() ("model", "=", line.name),
relations = self.env['ir.model.fields'].search([ ]
('relation', '=', row[1]), ).unlink()
]).with_context(**context_flags) relations = (
self.env["ir.model.fields"]
.search(
[
("relation", "=", row[1]),
]
)
.with_context(**context_flags)
)
for relation in relations: for relation in relations:
try: try:
# Fails if the model on the target side # Fails if the model on the target side
@ -85,19 +96,18 @@ class CleanupPurgeLineModel(models.TransientModel):
pass pass
except AttributeError: except AttributeError:
pass pass
self.env['ir.model.relation'].search([ self.env["ir.model.relation"].search(
('model', '=', line.name) [("model", "=", line.name)]
]).with_context(**context_flags).unlink() ).with_context(**context_flags).unlink()
self.env['ir.model'].browse([row[0]])\ self.env["ir.model"].browse([row[0]]).with_context(**context_flags).unlink()
.with_context(**context_flags).unlink() line.write({"purged": True})
line.write({'purged': True})
return True return True
class CleanupPurgeWizardModel(models.TransientModel): class CleanupPurgeWizardModel(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.model' _name = "cleanup.purge.wizard.model"
_description = 'Purge models' _description = "Purge models"
@api.model @api.model
def find(self): def find(self):
@ -106,12 +116,13 @@ class CleanupPurgeWizardModel(models.TransientModel):
""" """
res = [] res = []
self.env.cr.execute("SELECT model from ir_model") self.env.cr.execute("SELECT model from ir_model")
for model, in self.env.cr.fetchall(): for (model,) in self.env.cr.fetchall():
if model not in self.env: if model not in self.env:
res.append((0, 0, {'name': model})) res.append((0, 0, {"name": model}))
if not res: if not res:
raise UserError(_('No orphaned models found')) raise UserError(_("No orphaned models found"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.model', 'wizard_id', 'Models to purge') "cleanup.purge.line.model", "wizard_id", "Models to purge"
)

View File

@ -4,35 +4,39 @@
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.modules.module import get_module_path from odoo.modules.module import get_module_path
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
class IrModelData(models.Model): class IrModelData(models.Model):
_inherit = 'ir.model.data' _inherit = "ir.model.data"
@api.model @api.model
def _module_data_uninstall(self, modules_to_remove): def _module_data_uninstall(self, modules_to_remove):
"""this function crashes for xmlids on undefined models or fields """this function crashes for xmlids on undefined models or fields
referring to undefined models""" referring to undefined models"""
for this in self.search([('module', 'in', modules_to_remove)]): for this in self.search([("module", "in", modules_to_remove)]):
if this.model == 'ir.model.fields': if this.model == "ir.model.fields":
field = self.env[this.model].with_context( field = (
**{MODULE_UNINSTALL_FLAG: True}).browse(this.res_id) self.env[this.model]
.with_context(**{MODULE_UNINSTALL_FLAG: True})
.browse(this.res_id)
)
if not field.exists() or field.model not in self.env: if not field.exists() or field.model not in self.env:
this.unlink() this.unlink()
continue continue
if this.model not in self.env: if this.model not in self.env:
this.unlink() this.unlink()
return super(IrModelData, self)._module_data_uninstall( return super(IrModelData, self)._module_data_uninstall(modules_to_remove)
modules_to_remove)
class CleanupPurgeLineModule(models.TransientModel): class CleanupPurgeLineModule(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.module' _name = "cleanup.purge.line.module"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.module', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.module", "Purge Wizard", readonly=True
)
@api.multi @api.multi
def purge(self): def purge(self):
@ -40,48 +44,46 @@ class CleanupPurgeLineModule(models.TransientModel):
Uninstall modules upon manual confirmation, then reload Uninstall modules upon manual confirmation, then reload
the database. the database.
""" """
module_names = self.filtered(lambda x: not x.purged).mapped('name') module_names = self.filtered(lambda x: not x.purged).mapped("name")
modules = self.env['ir.module.module'].search([ modules = self.env["ir.module.module"].search([("name", "in", module_names)])
('name', 'in', module_names)
])
if not modules: if not modules:
return True return True
self.logger.info('Purging modules %s', ', '.join(module_names)) self.logger.info("Purging modules %s", ", ".join(module_names))
modules.filtered( modules.filtered(
lambda x: x.state not in ('uninstallable', 'uninstalled') lambda x: x.state not in ("uninstallable", "uninstalled")
).button_immediate_uninstall() ).button_immediate_uninstall()
modules.refresh() modules.refresh()
modules.unlink() modules.unlink()
return self.write({'purged': True}) return self.write({"purged": True})
class CleanupPurgeWizardModule(models.TransientModel): class CleanupPurgeWizardModule(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.module' _name = "cleanup.purge.wizard.module"
_description = 'Purge modules' _description = "Purge modules"
@api.model @api.model
def find(self): def find(self):
res = [] res = []
IrModule = self.env['ir.module.module'] IrModule = self.env["ir.module.module"]
for module in IrModule.search( for module in IrModule.search(
[ [("to_buy", "=", False), ("name", "!=", "studio_customization")]
('to_buy', '=', False),
('name', '!=', 'studio_customization')
]
): ):
if get_module_path(module.name, display_warning=False): if get_module_path(module.name, display_warning=False):
continue continue
if module.state == 'uninstalled': if module.state == "uninstalled":
self.env['cleanup.purge.line.module'].create({ self.env["cleanup.purge.line.module"].create(
'name': module.name, {
}).purge() "name": module.name,
}
).purge()
continue continue
res.append((0, 0, {'name': module.name})) res.append((0, 0, {"name": module.name}))
if not res: if not res:
raise UserError(_('No modules found to purge')) raise UserError(_("No modules found to purge"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.module', 'wizard_id', 'Modules to purge') "cleanup.purge.line.module", "wizard_id", "Modules to purge"
)

View File

@ -1,7 +1,8 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
from odoo import api, models, fields from odoo import api, fields, models
REASON_DUPLICATE = 1 REASON_DUPLICATE = 1
REASON_DEFAULT = 2 REASON_DEFAULT = 2
REASON_DEFAULT_FALSE = 3 REASON_DEFAULT_FALSE = 3
@ -9,31 +10,34 @@ REASON_UNKNOWN_MODEL = 4
class CleanupPurgeLineProperty(models.TransientModel): class CleanupPurgeLineProperty(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.property' _name = "cleanup.purge.line.property"
_description = 'Purge properties' _description = "Purge properties"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.property', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.property", "Purge Wizard", readonly=True
property_id = fields.Many2one('ir.property') )
reason = fields.Selection([ property_id = fields.Many2one("ir.property")
(REASON_DUPLICATE, 'Duplicated property'), reason = fields.Selection(
(REASON_DEFAULT, 'Same value as default'), [
(REASON_DEFAULT_FALSE, 'Empty default property'), (REASON_DUPLICATE, "Duplicated property"),
(REASON_UNKNOWN_MODEL, 'Unknown model'), (REASON_DEFAULT, "Same value as default"),
]) (REASON_DEFAULT_FALSE, "Empty default property"),
(REASON_UNKNOWN_MODEL, "Unknown model"),
]
)
@api.multi @api.multi
def purge(self): def purge(self):
"""Delete properties""" """Delete properties"""
self.write({'purged': True}) self.write({"purged": True})
return self.mapped('property_id').unlink() return self.mapped("property_id").unlink()
class CleanupPurgeWizardProperty(models.TransientModel): class CleanupPurgeWizardProperty(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.property' _name = "cleanup.purge.wizard.property"
_description = 'Purge properties' _description = "Purge properties"
@api.model @api.model
def find(self): def find(self):
@ -41,96 +45,118 @@ class CleanupPurgeWizardProperty(models.TransientModel):
Search property records which are duplicated or the same as the default Search property records which are duplicated or the same as the default
""" """
result = [] result = []
default_properties = self.env['ir.property'].search([ default_properties = self.env["ir.property"].search(
('res_id', '=', False), [
]) ("res_id", "=", False),
]
)
handled_field_ids = [] handled_field_ids = []
for prop in default_properties: for prop in default_properties:
value = None value = None
try: try:
value = prop.get_by_record() value = prop.get_by_record()
except KeyError: except KeyError:
result.append({ result.append(
'name': '%s@%s: %s' % ( {
prop.name, prop.res_id, value, "name": "%s@%s: %s"
% (
prop.name,
prop.res_id,
value,
), ),
'property_id': prop.id, "property_id": prop.id,
'reason': REASON_UNKNOWN_MODEL, "reason": REASON_UNKNOWN_MODEL,
}) }
)
continue continue
if not value: if not value:
result.append({ result.append(
'name': '%s@%s: %s' % ( {
prop.name, prop.res_id, value, "name": "%s@%s: %s"
% (
prop.name,
prop.res_id,
value,
), ),
'property_id': prop.id, "property_id": prop.id,
'reason': REASON_DEFAULT_FALSE, "reason": REASON_DEFAULT_FALSE,
}) }
)
continue continue
if prop.fields_id.id in handled_field_ids: if prop.fields_id.id in handled_field_ids:
continue continue
domain = [ domain = [
('id', '!=', prop.id), ("id", "!=", prop.id),
('fields_id', '=', prop.fields_id.id), ("fields_id", "=", prop.fields_id.id),
# =? explicitly tests for None or False, not falsyness # =? explicitly tests for None or False, not falsyness
('value_float', '=?', prop.value_float or False), ("value_float", "=?", prop.value_float or False),
('value_integer', '=?', prop.value_integer or False), ("value_integer", "=?", prop.value_integer or False),
('value_text', '=?', prop.value_text or False), ("value_text", "=?", prop.value_text or False),
('value_binary', '=?', prop.value_binary or False), ("value_binary", "=?", prop.value_binary or False),
('value_reference', '=?', prop.value_reference or False), ("value_reference", "=?", prop.value_reference or False),
('value_datetime', '=?', prop.value_datetime or False), ("value_datetime", "=?", prop.value_datetime or False),
] ]
if prop.company_id: if prop.company_id:
domain.append(('company_id', '=', prop.company_id.id)) domain.append(("company_id", "=", prop.company_id.id))
else: else:
domain.extend([ domain.extend(
'|', [
('company_id', '=', False), "|",
("company_id", "=", False),
( (
'company_id', 'in', self.env['res.company'].search([ "company_id",
"in",
self.env["res.company"]
.search(
[
( (
'id', 'not in', default_properties.filtered( "id",
lambda x: x.company_id and "not in",
x.fields_id == prop.fields_id default_properties.filtered(
lambda x: x.company_id
and x.fields_id == prop.fields_id
).ids, ).ids,
) )
]).ids ]
)
.ids,
), ),
]) ]
)
for redundant_property in self.env['ir.property'].search(domain): for redundant_property in self.env["ir.property"].search(domain):
result.append({ result.append(
'name': '%s@%s: %s' % ( {
prop.name, redundant_property.res_id, "name": "%s@%s: %s"
prop.get_by_record() % (prop.name, redundant_property.res_id, prop.get_by_record()),
), "property_id": redundant_property.id,
'property_id': redundant_property.id, "reason": REASON_DEFAULT,
'reason': REASON_DEFAULT, }
}) )
handled_field_ids.append(prop.fields_id.id) handled_field_ids.append(prop.fields_id.id)
self.env.cr.execute( self.env.cr.execute(
''' """
with grouped_properties(ids, cnt) as ( with grouped_properties(ids, cnt) as (
select array_agg(id), count(*) select array_agg(id), count(*)
from ir_property group by res_id, company_id, fields_id from ir_property group by res_id, company_id, fields_id
) )
select ids from grouped_properties where cnt > 1 select ids from grouped_properties where cnt > 1
''' """
) )
for ids, in self.env.cr.fetchall(): for (ids,) in self.env.cr.fetchall():
# odoo uses the first property found by search # odoo uses the first property found by search
for prop in self.env['ir.property'].search([ for prop in self.env["ir.property"].search([("id", "in", ids)])[1:]:
('id', 'in', ids) result.append(
])[1:]: {
result.append({ "name": "%s@%s: %s"
'name': '%s@%s: %s' % ( % (prop.name, prop.res_id, prop.get_by_record()),
prop.name, prop.res_id, prop.get_by_record() "property_id": prop.id,
), "reason": REASON_DUPLICATE,
'property_id': prop.id, }
'reason': REASON_DUPLICATE, )
})
return result return result
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.property', 'wizard_id', 'Properties to purge') "cleanup.purge.line.property", "wizard_id", "Properties to purge"
)

View File

@ -1,18 +1,20 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # Copyright 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineTable(models.TransientModel): class CleanupPurgeLineTable(models.TransientModel):
_inherit = 'cleanup.purge.line' _inherit = "cleanup.purge.line"
_name = 'cleanup.purge.line.table' _name = "cleanup.purge.line.table"
_description = 'Purge tables wizard lines' _description = "Purge tables wizard lines"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
'cleanup.purge.wizard.table', 'Purge Wizard', readonly=True) "cleanup.purge.wizard.table", "Purge Wizard", readonly=True
)
@api.multi @api.multi
def purge(self): def purge(self):
@ -22,9 +24,10 @@ class CleanupPurgeLineTable(models.TransientModel):
if self: if self:
objs = self objs = self
else: else:
objs = self.env['cleanup.purge.line.table']\ objs = self.env["cleanup.purge.line.table"].browse(
.browse(self._context.get('active_ids')) self._context.get("active_ids")
tables = objs.mapped('name') )
tables = objs.mapped("name")
for line in objs: for line in objs:
if line.purged: if line.purged:
continue continue
@ -49,32 +52,35 @@ class CleanupPurgeLineTable(models.TransientModel):
WHERE af.attnum = confkey AND af.attrelid = confrelid AND WHERE af.attnum = confkey AND af.attrelid = confrelid AND
a.attnum = conkey AND a.attrelid = conrelid a.attnum = conkey AND a.attrelid = conrelid
AND confrelid::regclass = '%s'::regclass; AND confrelid::regclass = '%s'::regclass;
""", (IdentifierAdapter(line.name, quote=False),)) """,
(IdentifierAdapter(line.name, quote=False),),
)
for constraint in self.env.cr.fetchall(): for constraint in self.env.cr.fetchall():
if constraint[3] in tables: if constraint[3] in tables:
self.logger.info( self.logger.info(
'Dropping constraint %s on table %s (to be dropped)', "Dropping constraint %s on table %s (to be dropped)",
constraint[0], constraint[3]) constraint[0],
constraint[3],
)
self.env.cr.execute( self.env.cr.execute(
"ALTER TABLE %s DROP CONSTRAINT %s", "ALTER TABLE %s DROP CONSTRAINT %s",
( (
IdentifierAdapter(constraint[3]), IdentifierAdapter(constraint[3]),
IdentifierAdapter(constraint[0]) IdentifierAdapter(constraint[0]),
)) ),
)
self.logger.info( self.logger.info("Dropping table %s", line.name)
'Dropping table %s', line.name) self.env.cr.execute("DROP TABLE %s", (IdentifierAdapter(line.name),))
self.env.cr.execute( line.write({"purged": True})
"DROP TABLE %s", (IdentifierAdapter(line.name),))
line.write({'purged': True})
return True return True
class CleanupPurgeWizardTable(models.TransientModel): class CleanupPurgeWizardTable(models.TransientModel):
_inherit = 'cleanup.purge.wizard' _inherit = "cleanup.purge.wizard"
_name = 'cleanup.purge.wizard.table' _name = "cleanup.purge.wizard.table"
_description = 'Purge tables' _description = "Purge tables"
@api.model @api.model
def find(self): def find(self):
@ -83,7 +89,7 @@ class CleanupPurgeWizardTable(models.TransientModel):
Ignore views for now. Ignore views for now.
""" """
known_tables = [] known_tables = []
for model in self.env['ir.model'].search([]): for model in self.env["ir.model"].search([]):
if model.model not in self.env: if model.model not in self.env:
continue continue
model_pool = self.env[model.model] model_pool = self.env[model.model]
@ -91,8 +97,8 @@ class CleanupPurgeWizardTable(models.TransientModel):
known_tables += [ known_tables += [
column.relation column.relation
for column in model_pool._fields.values() for column in model_pool._fields.values()
if column.type == 'many2many' and if column.type == "many2many"
(column.compute is None or column.store) and (column.compute is None or column.store)
and column.relation and column.relation
] ]
@ -100,12 +106,15 @@ class CleanupPurgeWizardTable(models.TransientModel):
""" """
SELECT table_name FROM information_schema.tables SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE' WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
AND table_name NOT IN %s""", (tuple(known_tables),)) AND table_name NOT IN %s""",
(tuple(known_tables),),
)
res = [(0, 0, {'name': row[0]}) for row in self.env.cr.fetchall()] res = [(0, 0, {"name": row[0]}) for row in self.env.cr.fetchall()]
if not res: if not res:
raise UserError(_('No orphaned tables found')) raise UserError(_("No orphaned tables found"))
return res return res
purge_line_ids = fields.One2many( purge_line_ids = fields.One2many(
'cleanup.purge.line.table', 'wizard_id', 'Tables to purge') "cleanup.purge.line.table", "wizard_id", "Tables to purge"
)

View File

@ -3,21 +3,23 @@
# pylint: disable=consider-merging-classes-inherited # pylint: disable=consider-merging-classes-inherited
import logging import logging
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import AccessDenied from odoo.exceptions import AccessDenied
class CleanupPurgeLine(models.AbstractModel): class CleanupPurgeLine(models.AbstractModel):
""" Abstract base class for the purge wizard lines """ """ Abstract base class for the purge wizard lines """
_name = 'cleanup.purge.line'
_order = 'name'
_description = 'Purge Column Abstract Wizard'
name = fields.Char('Name', readonly=True) _name = "cleanup.purge.line"
purged = fields.Boolean('Purged', readonly=True) _order = "name"
wizard_id = fields.Many2one('cleanup.purge.wizard') _description = "Purge Column Abstract Wizard"
logger = logging.getLogger('odoo.addons.database_cleanup') name = fields.Char("Name", readonly=True)
purged = fields.Boolean("Purged", readonly=True)
wizard_id = fields.Many2one("cleanup.purge.wizard")
logger = logging.getLogger("odoo.addons.database_cleanup")
@api.multi @api.multi
def purge(self): def purge(self):
@ -26,22 +28,22 @@ class CleanupPurgeLine(models.AbstractModel):
@api.model @api.model
def create(self, values): def create(self, values):
# make sure the user trying this is actually supposed to do it # make sure the user trying this is actually supposed to do it
if self.env.ref( if self.env.ref("base.group_erp_manager") not in self.env.user.groups_id:
'base.group_erp_manager') not in self.env.user.groups_id:
raise AccessDenied raise AccessDenied
return super(CleanupPurgeLine, self).create(values) return super(CleanupPurgeLine, self).create(values)
class PurgeWizard(models.AbstractModel): class PurgeWizard(models.AbstractModel):
""" Abstract base class for the purge wizards """ """ Abstract base class for the purge wizards """
_name = 'cleanup.purge.wizard'
_description = 'Purge stuff' _name = "cleanup.purge.wizard"
_description = "Purge stuff"
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):
res = super(PurgeWizard, self).default_get(fields_list) res = super(PurgeWizard, self).default_get(fields_list)
if 'purge_line_ids' in fields_list: if "purge_line_ids" in fields_list:
res['purge_line_ids'] = self.find() res["purge_line_ids"] = self.find()
return res return res
@api.multi @api.multi
@ -50,47 +52,43 @@ class PurgeWizard(models.AbstractModel):
@api.multi @api.multi
def purge_all(self): def purge_all(self):
self.mapped('purge_line_ids').purge() self.mapped("purge_line_ids").purge()
return True return True
@api.model @api.model
def get_wizard_action(self): def get_wizard_action(self):
wizard = self.create({}) wizard = self.create({})
return { return {
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'name': wizard.display_name, "name": wizard.display_name,
'views': [(False, 'form')], "views": [(False, "form")],
'res_model': self._name, "res_model": self._name,
'res_id': wizard.id, "res_id": wizard.id,
'flags': { "flags": {
'action_buttons': False, "action_buttons": False,
'sidebar': False, "sidebar": False,
}, },
} }
@api.multi @api.multi
def select_lines(self): def select_lines(self):
return { return {
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'name': _('Select lines to purge'), "name": _("Select lines to purge"),
'views': [(False, 'tree'), (False, 'form')], "views": [(False, "tree"), (False, "form")],
'res_model': self._fields['purge_line_ids'].comodel_name, "res_model": self._fields["purge_line_ids"].comodel_name,
'domain': [('wizard_id', 'in', self.ids)], "domain": [("wizard_id", "in", self.ids)],
} }
@api.multi @api.multi
def name_get(self): def name_get(self):
return [ return [(this.id, self._description) for this in self]
(this.id, self._description)
for this in self
]
@api.model @api.model
def create(self, values): def create(self, values):
# make sure the user trying this is actually supposed to do it # make sure the user trying this is actually supposed to do it
if self.env.ref( if self.env.ref("base.group_erp_manager") not in self.env.user.groups_id:
'base.group_erp_manager') not in self.env.user.groups_id:
raise AccessDenied raise AccessDenied
return super(PurgeWizard, self).create(values) return super(PurgeWizard, self).create(values)
purge_line_ids = fields.One2many('cleanup.purge.line', 'wizard_id') purge_line_ids = fields.One2many("cleanup.purge.line", "wizard_id")

View File

@ -1,9 +1,10 @@
# Copyright 2016 Therp BV <http://therp.nl> # Copyright 2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2 import ProgrammingError from psycopg2 import ProgrammingError
from odoo.modules.registry import Registry from odoo.modules.registry import Registry
from odoo.tools import config, mute_logger
from odoo.tests.common import TransactionCase, at_install, post_install from odoo.tests.common import TransactionCase, at_install, post_install
from odoo.tools import config, mute_logger
# Use post_install to get all models loaded more info: odoo/odoo#13458 # Use post_install to get all models loaded more info: odoo/odoo#13458
@ -15,103 +16,129 @@ class TestDatabaseCleanup(TransactionCase):
self.module = None self.module = None
self.model = None self.model = None
# Create one property for tests # Create one property for tests
self.env['ir.property'].create({ self.env["ir.property"].create(
'fields_id': self.env.ref('base.field_res_partner__name').id, {
'type': 'char', "fields_id": self.env.ref("base.field_res_partner__name").id,
'value_text': 'My default partner name', "type": "char",
}) "value_text": "My default partner name",
}
)
def test_database_cleanup(self): def test_database_cleanup(self):
# delete some index and check if our module recreated it # delete some index and check if our module recreated it
self.env.cr.execute('drop index res_partner_name_index') self.env.cr.execute("drop index res_partner_name_index")
create_indexes = self.env['cleanup.create_indexes.wizard'].create({}) create_indexes = self.env["cleanup.create_indexes.wizard"].create({})
create_indexes.purge_all() create_indexes.purge_all()
self.env.cr.execute( self.env.cr.execute(
'select indexname from pg_indexes ' "select indexname from pg_indexes "
"where indexname='res_partner_name_index' and " "where indexname='res_partner_name_index' and "
"tablename='res_partner'" "tablename='res_partner'"
) )
self.assertEqual(self.env.cr.rowcount, 1) self.assertEqual(self.env.cr.rowcount, 1)
# duplicate a property # duplicate a property
duplicate_property = self.env['ir.property'].search([], limit=1).copy() duplicate_property = self.env["ir.property"].search([], limit=1).copy()
purge_property = self.env['cleanup.purge.wizard.property'].create({}) purge_property = self.env["cleanup.purge.wizard.property"].create({})
purge_property.purge_all() purge_property.purge_all()
self.assertFalse(duplicate_property.exists()) self.assertFalse(duplicate_property.exists())
# create an orphaned column # create an orphaned column
self.env.cr.execute( self.env.cr.execute(
'alter table res_partner add column database_cleanup_test int') "alter table res_partner add column database_cleanup_test int"
)
# We need use a model that is not blocked (Avoid use res.users) # We need use a model that is not blocked (Avoid use res.users)
partner_model = self.env['ir.model'].search([ partner_model = self.env["ir.model"].search(
('model', '=', 'res.partner')], limit=1) [("model", "=", "res.partner")], limit=1
purge_columns = self.env['cleanup.purge.wizard.column'].create({ )
'purge_line_ids': [(0, 0, { purge_columns = self.env["cleanup.purge.wizard.column"].create(
'model_id': partner_model.id, 'name': 'database_cleanup_test'} {
)]}) "purge_line_ids": [
(
0,
0,
{"model_id": partner_model.id, "name": "database_cleanup_test"},
)
]
}
)
purge_columns.purge_all() purge_columns.purge_all()
# must be removed by the wizard # must be removed by the wizard
with self.assertRaises(ProgrammingError): with self.assertRaises(ProgrammingError):
with self.env.registry.cursor() as cr: with self.env.registry.cursor() as cr:
with mute_logger('odoo.sql_db'): with mute_logger("odoo.sql_db"):
cr.execute('select database_cleanup_test from res_partner') cr.execute("select database_cleanup_test from res_partner")
# create a data entry pointing nowhere # create a data entry pointing nowhere
self.env.cr.execute('select max(id) + 1 from res_users') self.env.cr.execute("select max(id) + 1 from res_users")
self.env['ir.model.data'].create({ self.env["ir.model.data"].create(
'module': 'database_cleanup', {
'name': 'test_no_data_entry', "module": "database_cleanup",
'model': 'res.users', "name": "test_no_data_entry",
'res_id': self.env.cr.fetchone()[0], "model": "res.users",
}) "res_id": self.env.cr.fetchone()[0],
purge_data = self.env['cleanup.purge.wizard.data'].create({}) }
)
purge_data = self.env["cleanup.purge.wizard.data"].create({})
purge_data.purge_all() purge_data.purge_all()
# must be removed by the wizard # must be removed by the wizard
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
self.env.ref('database_cleanup.test_no_data_entry') self.env.ref("database_cleanup.test_no_data_entry")
# create a nonexistent model # create a nonexistent model
self.model = self.env['ir.model'].create({ self.model = self.env["ir.model"].create(
'name': 'Database cleanup test model', {
'model': 'x_database.cleanup.test.model', "name": "Database cleanup test model",
}) "model": "x_database.cleanup.test.model",
}
)
self.env.cr.execute( self.env.cr.execute(
'insert into ir_attachment (name, res_model, res_id, type) values ' "insert into ir_attachment (name, res_model, res_id, type) values "
"('test attachment', 'database.cleanup.test.model', 42, 'binary')") "('test attachment', 'database.cleanup.test.model', 42, 'binary')"
self.env.registry.models.pop('x_database.cleanup.test.model') )
purge_models = self.env['cleanup.purge.wizard.model'].create({}) self.env.registry.models.pop("x_database.cleanup.test.model")
purge_models = self.env["cleanup.purge.wizard.model"].create({})
purge_models.purge_all() purge_models.purge_all()
# must be removed by the wizard # must be removed by the wizard
self.assertFalse(self.env['ir.model'].search([ self.assertFalse(
('model', '=', 'x_database.cleanup.test.model'), self.env["ir.model"].search(
])) [
("model", "=", "x_database.cleanup.test.model"),
]
)
)
# create a nonexistent module # create a nonexistent module
self.module = self.env['ir.module.module'].create({ self.module = self.env["ir.module.module"].create(
'name': 'database_cleanup_test', {
'state': 'to upgrade', "name": "database_cleanup_test",
}) "state": "to upgrade",
purge_modules = self.env['cleanup.purge.wizard.module'].create({}) }
)
purge_modules = self.env["cleanup.purge.wizard.module"].create({})
# this reloads our registry, and we don't want to run tests twice # this reloads our registry, and we don't want to run tests twice
# we also need the original registry for further tests, so save a # we also need the original registry for further tests, so save a
# reference to it # reference to it
original_registry = Registry.registries[self.env.cr.dbname] original_registry = Registry.registries[self.env.cr.dbname]
config.options['test_enable'] = False config.options["test_enable"] = False
purge_modules.purge_all() purge_modules.purge_all()
config.options['test_enable'] = True config.options["test_enable"] = True
# must be removed by the wizard # must be removed by the wizard
self.assertFalse(self.env['ir.module.module'].search([ self.assertFalse(
('name', '=', 'database_cleanup_test'), self.env["ir.module.module"].search(
])) [
("name", "=", "database_cleanup_test"),
]
)
)
# reset afterwards # reset afterwards
Registry.registries[self.env.cr.dbname] = original_registry Registry.registries[self.env.cr.dbname] = original_registry
# create an orphaned table # create an orphaned table
self.env.cr.execute('create table database_cleanup_test (test int)') self.env.cr.execute("create table database_cleanup_test (test int)")
purge_tables = self.env['cleanup.purge.wizard.table'].create({}) purge_tables = self.env["cleanup.purge.wizard.table"].create({})
purge_tables.purge_all() purge_tables.purge_all()
with self.assertRaises(ProgrammingError): with self.assertRaises(ProgrammingError):
with self.env.registry.cursor() as cr: with self.env.registry.cursor() as cr:
with mute_logger('odoo.sql_db'): with mute_logger("odoo.sql_db"):
cr.execute('select * from database_cleanup_test') cr.execute("select * from database_cleanup_test")
def tearDown(self): def tearDown(self):
super(TestDatabaseCleanup, self).tearDown() super(TestDatabaseCleanup, self).tearDown()
@ -120,10 +147,8 @@ class TestDatabaseCleanup(TransactionCase):
self.env.cr.rollback() self.env.cr.rollback()
if self.module: if self.module:
cr2.execute( cr2.execute(
"DELETE FROM ir_module_module WHERE id=%s", "DELETE FROM ir_module_module WHERE id=%s", (self.module.id,)
(self.module.id,)) )
if self.model: if self.model:
cr2.execute( cr2.execute("DELETE FROM ir_model WHERE id=%s", (self.model.id,))
"DELETE FROM ir_model WHERE id=%s",
(self.model.id,))
cr2.commit() cr2.commit()

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<record id="cleanup_create_indexes_wizard_view_form" model="ir.ui.view"> <record id="cleanup_create_indexes_wizard_view_form" model="ir.ui.view">
<field name="model">cleanup.create_indexes.wizard</field> <field name="model">cleanup.create_indexes.wizard</field>
@ -18,8 +18,13 @@
<field name="name">Create missing indexes</field> <field name="name">Create missing indexes</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_create_indexes_wizard" /> <field
<field name="code">action = env.get('cleanup.create_indexes.wizard').get_wizard_action()</field> name="model_id"
ref="database_cleanup.model_cleanup_create_indexes_wizard"
/>
<field
name="code"
>action = env.get('cleanup.create_indexes.wizard').get_wizard_action()</field>
</record> </record>
<record id="cleanup_create_indexes_line_view_tree" model="ir.ui.view"> <record id="cleanup_create_indexes_line_view_tree" model="ir.ui.view">
@ -38,8 +43,14 @@
<field name="name">Create</field> <field name="name">Create</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_create_indexes_line" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_create_indexes_line"
/>
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_create_indexes_line" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_create_indexes_line"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,66 +1,66 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record model="ir.ui.menu" id="menu_database_cleanup"> <record model="ir.ui.menu" id="menu_database_cleanup">
<field name="name">Database cleanup</field> <field name="name">Database cleanup</field>
<field name="sequence" eval="10" /> <field name="sequence" eval="10" />
<!-- attach to Settings -> Technical --> <!-- attach to Settings -> Technical -->
<field name="parent_id" ref="base.menu_custom"/> <field name="parent_id" ref="base.menu_custom" />
<field name="groups_id" eval="[(6,0, [ref('base.group_erp_manager')])]"/> <field name="groups_id" eval="[(6,0, [ref('base.group_erp_manager')])]" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_modules"> <record model="ir.ui.menu" id="menu_purge_modules">
<field name="name">Purge obsolete modules</field> <field name="name">Purge obsolete modules</field>
<field name="sequence" eval="10" /> <field name="sequence" eval="10" />
<field name="action" ref="action_purge_modules" /> <field name="action" ref="action_purge_modules" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_models"> <record model="ir.ui.menu" id="menu_purge_models">
<field name="name">Purge obsolete models</field> <field name="name">Purge obsolete models</field>
<field name="sequence" eval="20" /> <field name="sequence" eval="20" />
<field name="action" ref="action_purge_models" /> <field name="action" ref="action_purge_models" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_columns"> <record model="ir.ui.menu" id="menu_purge_columns">
<field name="name">Purge obsolete columns</field> <field name="name">Purge obsolete columns</field>
<field name="sequence" eval="30" /> <field name="sequence" eval="30" />
<field name="action" ref="action_purge_columns" /> <field name="action" ref="action_purge_columns" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_tables"> <record model="ir.ui.menu" id="menu_purge_tables">
<field name="name">Purge obsolete tables</field> <field name="name">Purge obsolete tables</field>
<field name="sequence" eval="40" /> <field name="sequence" eval="40" />
<field name="action" ref="action_purge_tables" /> <field name="action" ref="action_purge_tables" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_data"> <record model="ir.ui.menu" id="menu_purge_data">
<field name="name">Purge obsolete data entries</field> <field name="name">Purge obsolete data entries</field>
<field name="sequence" eval="50" /> <field name="sequence" eval="50" />
<field name="action" ref="action_purge_data" /> <field name="action" ref="action_purge_data" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_menus"> <record model="ir.ui.menu" id="menu_purge_menus">
<field name="name">Purge obsolete menu entries</field> <field name="name">Purge obsolete menu entries</field>
<field name="sequence" eval="60" /> <field name="sequence" eval="60" />
<field name="action" ref="action_purge_menus" /> <field name="action" ref="action_purge_menus" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_create_indexes"> <record model="ir.ui.menu" id="menu_create_indexes">
<field name="name">Create missing indexes</field> <field name="name">Create missing indexes</field>
<field name="sequence" eval="70" /> <field name="sequence" eval="70" />
<field name="action" ref="cleanup_create_indexes_wizard_action" /> <field name="action" ref="cleanup_create_indexes_wizard_action" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
<record model="ir.ui.menu" id="menu_purge_property"> <record model="ir.ui.menu" id="menu_purge_property">
<field name="name">Purge obsolete properties</field> <field name="name">Purge obsolete properties</field>
<field name="sequence" eval="80" /> <field name="sequence" eval="80" />
<field name="action" ref="action_purge_property" /> <field name="action" ref="action_purge_property" />
<field name="parent_id" ref="menu_database_cleanup"/> <field name="parent_id" ref="menu_database_cleanup" />
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_columns_view" model="ir.ui.view"> <record id="purge_columns_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.column</field> <field name="model">cleanup.purge.wizard.column</field>
@ -15,7 +15,10 @@
<field name="name">Purge columns</field> <field name="name">Purge columns</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_column" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_purge_wizard_column"
/>
<field name="code"> <field name="code">
action = env.get('cleanup.purge.wizard.column').get_wizard_action() action = env.get('cleanup.purge.wizard.column').get_wizard_action()
</field> </field>
@ -38,6 +41,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_column" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_column" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_column" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_column"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_data_view" model="ir.ui.view"> <record id="purge_data_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.data</field> <field name="model">cleanup.purge.wizard.data</field>
@ -38,6 +38,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_data" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_data" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_data" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_data"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_menus_view" model="ir.ui.view"> <record id="purge_menus_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.menu</field> <field name="model">cleanup.purge.wizard.menu</field>
<field name="inherit_id" ref="form_purge_wizard" /> <field name="inherit_id" ref="form_purge_wizard" />
<field name="mode">primary</field> <field name="mode">primary</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<data/> <data />
</field> </field>
</record> </record>
@ -34,6 +34,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_menu" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_menu" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_menu" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_menu"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_models_view" model="ir.ui.view"> <record id="purge_models_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.model</field> <field name="model">cleanup.purge.wizard.model</field>
@ -13,7 +13,10 @@
<field name="name">Purge models</field> <field name="name">Purge models</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_model" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_purge_wizard_model"
/>
<field name="code"> <field name="code">
action = env.get('cleanup.purge.wizard.model').get_wizard_action() action = env.get('cleanup.purge.wizard.model').get_wizard_action()
</field> </field>
@ -34,6 +37,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_model" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_model" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_model" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_model"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_modules_view" model="ir.ui.view"> <record id="purge_modules_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.module</field> <field name="model">cleanup.purge.wizard.module</field>
@ -13,7 +13,10 @@
<field name="name">Purge modules</field> <field name="name">Purge modules</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_module" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_purge_wizard_module"
/>
<field name="code"> <field name="code">
action = env.get('cleanup.purge.wizard.module').get_wizard_action() action = env.get('cleanup.purge.wizard.module').get_wizard_action()
</field> </field>
@ -24,7 +27,7 @@
<field name="inherit_id" ref="tree_purge_line" /> <field name="inherit_id" ref="tree_purge_line" />
<field name="mode">primary</field> <field name="mode">primary</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<data/> <data />
</field> </field>
</record> </record>
@ -34,6 +37,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_module" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_module" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_module" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_module"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_property_view" model="ir.ui.view"> <record id="purge_property_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.property</field> <field name="model">cleanup.purge.wizard.property</field>
@ -13,8 +13,13 @@
<field name="name">Purge properties</field> <field name="name">Purge properties</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_property" /> <field
<field name="code">action = env.get('cleanup.purge.wizard.property').get_wizard_action()</field> name="model_id"
ref="database_cleanup.model_cleanup_purge_wizard_property"
/>
<field
name="code"
>action = env.get('cleanup.purge.wizard.property').get_wizard_action()</field>
</record> </record>
<record id="purge_property_line_tree" model="ir.ui.view"> <record id="purge_property_line_tree" model="ir.ui.view">
@ -32,8 +37,14 @@
<field name="name">Purge</field> <field name="name">Purge</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_property" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_purge_line_property"
/>
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_property" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_property"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="purge_tables_view" model="ir.ui.view"> <record id="purge_tables_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.table</field> <field name="model">cleanup.purge.wizard.table</field>
@ -13,7 +13,10 @@
<field name="name">Purge tables</field> <field name="name">Purge tables</field>
<field name="type">ir.actions.server</field> <field name="type">ir.actions.server</field>
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_table" /> <field
name="model_id"
ref="database_cleanup.model_cleanup_purge_wizard_table"
/>
<field name="code"> <field name="code">
action = env.get('cleanup.purge.wizard.table').get_wizard_action() action = env.get('cleanup.purge.wizard.table').get_wizard_action()
</field> </field>
@ -34,6 +37,9 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_line_table" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_line_table" />
<field name="code">records.purge()</field> <field name="code">records.purge()</field>
<field name="binding_model_id" ref="database_cleanup.model_cleanup_purge_line_table" /> <field
name="binding_model_id"
ref="database_cleanup.model_cleanup_purge_line_table"
/>
</record> </record>
</odoo> </odoo>

View File

@ -1,26 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="form_purge_wizard" model="ir.ui.view"> <record id="form_purge_wizard" model="ir.ui.view">
<field name="model">cleanup.purge.wizard</field> <field name="model">cleanup.purge.wizard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header> <header>
<button type="object" name="purge_all" string="Purge all" class="oe_highlight" /> <button
type="object"
name="purge_all"
string="Purge all"
class="oe_highlight"
/>
<button type="object" name="select_lines" string="Select lines" /> <button type="object" name="select_lines" string="Select lines" />
</header> </header>
<div attrs="{'invisible': [('purge_line_ids', '!=', [])]}"> <div attrs="{'invisible': [('purge_line_ids', '!=', [])]}">
Nothing found to clean up. Nothing found to clean up.
</div> </div>
<field name="purge_line_ids" attrs="{'invisible': [('purge_line_ids', '=', [])]}"> <field
name="purge_line_ids"
attrs="{'invisible': [('purge_line_ids', '=', [])]}"
>
<form> <form>
<group> <group>
<field name="name" /> <field name="name" />
<field name="purged" /> <field name="purged" />
</group> </group>
<footer> <footer>
<button type="object" name="purge" class="oe_highlight" <button
type="object"
name="purge"
class="oe_highlight"
string="Purge" string="Purge"
attrs="{'invisible': [('purged', '=', True)]}"/> attrs="{'invisible': [('purged', '=', True)]}"
/>
</footer> </footer>
</form> </form>
</field> </field>
@ -33,9 +45,13 @@
<tree string="Purge models" delete="false" create="false"> <tree string="Purge models" delete="false" create="false">
<field name="name" /> <field name="name" />
<field name="purged" /> <field name="purged" />
<button type="object" name="purge" <button
icon="fa-times-circle text-danger" string="Purge this model" type="object"
attrs="{'invisible': [('purged', '=', True)]}"/> name="purge"
icon="fa-times-circle text-danger"
string="Purge this model"
attrs="{'invisible': [('purged', '=', True)]}"
/>
</tree> </tree>
</field> </field>
</record> </record>