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

View File

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

View File

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

View File

@ -3,18 +3,19 @@
# pylint: disable=consider-merging-classes-inherited
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineColumn(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.column'
_description = 'Purge Column Wizard Lines'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.column"
_description = "Purge Column Wizard Lines"
model_id = fields.Many2one('ir.model', 'Model', required=True,
ondelete='CASCADE')
model_id = fields.Many2one("ir.model", "Model", required=True, ondelete="CASCADE")
wizard_id = fields.Many2one(
'cleanup.purge.wizard.column', 'Purge Wizard', readonly=True)
"cleanup.purge.wizard.column", "Purge Wizard", readonly=True
)
@api.multi
def purge(self):
@ -24,8 +25,9 @@ class CleanupPurgeLineColumn(models.TransientModel):
if self:
objs = self
else:
objs = self.env['cleanup.purge.line.column']\
.browse(self._context.get('active_ids'))
objs = self.env["cleanup.purge.line.column"].browse(
self._context.get("active_ids")
)
for line in objs:
if line.purged:
continue
@ -34,24 +36,23 @@ class CleanupPurgeLineColumn(models.TransientModel):
# Inheritance such as stock.picking.in from stock.picking
# can lead to double attempts at removal
self.env.cr.execute(
'SELECT count(attname) FROM pg_attribute '
'WHERE attrelid = '
'( SELECT oid FROM pg_class WHERE relname = %s ) '
'AND attname = %s',
(model_pool._table, line.name))
"SELECT count(attname) FROM pg_attribute "
"WHERE attrelid = "
"( SELECT oid FROM pg_class WHERE relname = %s ) "
"AND attname = %s",
(model_pool._table, line.name),
)
if not self.env.cr.fetchone()[0]:
continue
self.logger.info(
'Dropping column %s from table %s',
line.name, model_pool._table)
"Dropping column %s from table %s", line.name, model_pool._table
)
self.env.cr.execute(
'ALTER TABLE %s DROP COLUMN %s',
(
IdentifierAdapter(model_pool._table),
IdentifierAdapter(line.name)
))
line.write({'purged': True})
"ALTER TABLE %s DROP COLUMN %s",
(IdentifierAdapter(model_pool._table), IdentifierAdapter(line.name)),
)
line.write({"purged": True})
# we need this commit because the ORM will deadlock if
# we still have a pending transaction
self.env.cr.commit() # pylint: disable=invalid-commit
@ -59,15 +60,15 @@ class CleanupPurgeLineColumn(models.TransientModel):
class CleanupPurgeWizardColumn(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.column'
_description = 'Purge columns'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.column"
_description = "Purge columns"
# List of known columns in use without corresponding fields
# Format: {table: [fields]}
blacklist = {
'wkf_instance': ['uid'], # lp:1277899
'res_users': ['password', 'password_crypt'],
"wkf_instance": ["uid"], # lp:1277899
"res_users": ["password", "password_crypt"],
}
@api.model
@ -77,12 +78,14 @@ class CleanupPurgeWizardColumn(models.TransientModel):
Iterate on the database columns to identify columns
of fields which have been removed
"""
columns = list(set([
column.name
for model_pool in model_pools
for column in model_pool._fields.values()
if not (column.compute is not None and not column.store)
]))
columns = list(
{
column.name
for model_pool in model_pools
for column in model_pool._fields.values()
if not (column.compute is not None and not column.store)
}
)
columns += models.MAGIC_COLUMNS
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) "
"NOT IN ('cid', 'tid', 'oid', 'xid') "
"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()]
@api.model
@ -109,24 +113,23 @@ class CleanupPurgeWizardColumn(models.TransientModel):
# mapping of tables to tuples (model id, [pool1, pool2, ...])
table2model = {}
for model in self.env['ir.model'].search([]):
for model in self.env["ir.model"].search([]):
if model.model not in self.env:
continue
model_pool = self.env[model.model]
if not model_pool._auto:
continue
table2model.setdefault(
model_pool._table, (model.id, [])
)[1].append(model_pool)
table2model.setdefault(model_pool._table, (model.id, []))[1].append(
model_pool
)
for table, model_spec in table2model.items():
for column in self.get_orphaned_columns(model_spec[1]):
res.append((0, 0, {
'name': column,
'model_id': model_spec[0]}))
res.append((0, 0, {"name": column, "model_id": model_spec[0]}))
if not res:
raise UserError(_('No orphaned columns found'))
raise UserError(_("No orphaned columns found"))
return res
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).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineData(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.data'
_inherit = "cleanup.purge.line"
_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(
'cleanup.purge.wizard.data', 'Purge Wizard', readonly=True)
"cleanup.purge.wizard.data", "Purge Wizard", readonly=True
)
@api.multi
def purge(self):
@ -19,18 +21,19 @@ class CleanupPurgeLineData(models.TransientModel):
if self:
objs = self
else:
objs = self.env['cleanup.purge.line.data']\
.browse(self._context.get('active_ids'))
objs = self.env["cleanup.purge.line.data"].browse(
self._context.get("active_ids")
)
to_unlink = objs.filtered(lambda x: not x.purged and x.data_id)
self.logger.info('Purging data entries: %s', to_unlink.mapped('name'))
to_unlink.mapped('data_id').unlink()
return to_unlink.write({'purged': True})
self.logger.info("Purging data entries: %s", to_unlink.mapped("name"))
to_unlink.mapped("data_id").unlink()
return to_unlink.write({"purged": True})
class CleanupPurgeWizardData(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.data'
_description = 'Purge data'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.data"
_description = "Purge data"
@api.model
def find(self):
@ -41,7 +44,7 @@ class CleanupPurgeWizardData(models.TransientModel):
data_ids = []
unknown_models = []
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:
continue
if model not in self.env:
@ -54,19 +57,35 @@ class CleanupPurgeWizardData(models.TransientModel):
AND res_id IS NOT NULL
AND NOT EXISTS (
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 += self.env['ir.model.data'].search([
('model', 'in', unknown_models),
]).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)}))
data_ids += (
self.env["ir.model.data"]
.search(
[
("model", "in", unknown_models),
]
)
.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:
raise UserError(_('No orphaned data entries found'))
raise UserError(_("No orphaned data entries found"))
return res
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):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.menu'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.menu"
wizard_id = fields.Many2one(
'cleanup.purge.wizard.menu', 'Purge Wizard', readonly=True)
menu_id = fields.Many2one('ir.ui.menu', 'Menu entry')
"cleanup.purge.wizard.menu", "Purge Wizard", readonly=True
)
menu_id = fields.Many2one("ir.ui.menu", "Menu entry")
@api.multi
def purge(self):
@ -19,18 +20,19 @@ class CleanupPurgeLineMenu(models.TransientModel):
if self:
objs = self
else:
objs = self.env['cleanup.purge.line.menu']\
.browse(self._context.get('active_ids'))
objs = self.env["cleanup.purge.line.menu"].browse(
self._context.get("active_ids")
)
to_unlink = objs.filtered(lambda x: not x.purged and x.menu_id)
self.logger.info('Purging menu entries: %s', to_unlink.mapped('name'))
to_unlink.mapped('menu_id').unlink()
return to_unlink.write({'purged': True})
self.logger.info("Purging menu entries: %s", to_unlink.mapped("name"))
to_unlink.mapped("menu_id").unlink()
return to_unlink.write({"purged": True})
class CleanupPurgeWizardMenu(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.menu'
_description = 'Purge menus'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.menu"
_description = "Purge menus"
@api.model
def find(self):
@ -38,21 +40,30 @@ class CleanupPurgeWizardMenu(models.TransientModel):
Search for models that cannot be instantiated.
"""
res = []
for menu in self.env['ir.ui.menu'].with_context(active_test=False)\
.search([('action', '!=', False)]):
if menu.action.type != 'ir.actions.act_window':
for menu in (
self.env["ir.ui.menu"]
.with_context(active_test=False)
.search([("action", "!=", False)])
):
if menu.action.type != "ir.actions.act_window":
continue
if (menu.action.res_model and menu.action.res_model not in
self.env) or \
(menu.action.src_model and menu.action.src_model not in
self.env):
res.append((0, 0, {
'name': menu.complete_name,
'menu_id': menu.id,
}))
if (menu.action.res_model and menu.action.res_model not in self.env) or (
menu.action.src_model and menu.action.src_model not in self.env
):
res.append(
(
0,
0,
{
"name": menu.complete_name,
"menu_id": menu.id,
},
)
)
if not res:
raise UserError(_('No dangling menu entries found'))
raise UserError(_("No dangling menu entries found"))
return res
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>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# 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.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
class IrModel(models.Model):
_inherit = 'ir.model'
_inherit = "ir.model"
def _drop_table(self):
"""this function crashes for undefined models"""
@ -22,7 +23,7 @@ class IrModel(models.Model):
class IrModelFields(models.Model):
_inherit = 'ir.model.fields'
_inherit = "ir.model.fields"
@api.multi
def _prepare_update(self):
@ -32,12 +33,13 @@ class IrModelFields(models.Model):
class CleanupPurgeLineModel(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.model'
_description = 'Purge models'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.model"
_description = "Purge models"
wizard_id = fields.Many2one(
'cleanup.purge.wizard.model', 'Purge Wizard', readonly=True)
"cleanup.purge.wizard.model", "Purge Wizard", readonly=True
)
@api.multi
def purge(self):
@ -46,36 +48,45 @@ class CleanupPurgeLineModel(models.TransientModel):
"""
context_flags = {
MODULE_UNINSTALL_FLAG: True,
'purge': True,
"purge": True,
}
if self:
objs = self
else:
objs = self.env['cleanup.purge.line.model']\
.browse(self._context.get('active_ids'))
objs = self.env["cleanup.purge.line.model"].browse(
self._context.get("active_ids")
)
for line in objs:
self.env.cr.execute(
"SELECT id, model from ir_model WHERE model = %s",
(line.name,))
"SELECT id, model from ir_model WHERE model = %s", (line.name,)
)
row = self.env.cr.fetchone()
if not row:
continue
self.logger.info('Purging model %s', row[1])
attachments = self.env['ir.attachment'].search([
('res_model', '=', line.name)
])
self.logger.info("Purging model %s", row[1])
attachments = self.env["ir.attachment"].search(
[("res_model", "=", line.name)]
)
if attachments:
self.env.cr.execute(
"UPDATE ir_attachment SET res_model = NULL "
"WHERE id in %s",
(tuple(attachments.ids), ))
self.env['ir.model.constraint'].search([
('model', '=', line.name),
]).unlink()
relations = self.env['ir.model.fields'].search([
('relation', '=', row[1]),
]).with_context(**context_flags)
"UPDATE ir_attachment SET res_model = NULL " "WHERE id in %s",
(tuple(attachments.ids),),
)
self.env["ir.model.constraint"].search(
[
("model", "=", line.name),
]
).unlink()
relations = (
self.env["ir.model.fields"]
.search(
[
("relation", "=", row[1]),
]
)
.with_context(**context_flags)
)
for relation in relations:
try:
# Fails if the model on the target side
@ -85,19 +96,18 @@ class CleanupPurgeLineModel(models.TransientModel):
pass
except AttributeError:
pass
self.env['ir.model.relation'].search([
('model', '=', line.name)
]).with_context(**context_flags).unlink()
self.env['ir.model'].browse([row[0]])\
.with_context(**context_flags).unlink()
line.write({'purged': True})
self.env["ir.model.relation"].search(
[("model", "=", line.name)]
).with_context(**context_flags).unlink()
self.env["ir.model"].browse([row[0]]).with_context(**context_flags).unlink()
line.write({"purged": True})
return True
class CleanupPurgeWizardModel(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.model'
_description = 'Purge models'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.model"
_description = "Purge models"
@api.model
def find(self):
@ -106,12 +116,13 @@ class CleanupPurgeWizardModel(models.TransientModel):
"""
res = []
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:
res.append((0, 0, {'name': model}))
res.append((0, 0, {"name": model}))
if not res:
raise UserError(_('No orphaned models found'))
raise UserError(_("No orphaned models found"))
return res
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.exceptions import UserError
from odoo.modules.module import get_module_path
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
class IrModelData(models.Model):
_inherit = 'ir.model.data'
_inherit = "ir.model.data"
@api.model
def _module_data_uninstall(self, modules_to_remove):
"""this function crashes for xmlids on undefined models or fields
referring to undefined models"""
for this in self.search([('module', 'in', modules_to_remove)]):
if this.model == 'ir.model.fields':
field = self.env[this.model].with_context(
**{MODULE_UNINSTALL_FLAG: True}).browse(this.res_id)
for this in self.search([("module", "in", modules_to_remove)]):
if this.model == "ir.model.fields":
field = (
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:
this.unlink()
continue
if this.model not in self.env:
this.unlink()
return super(IrModelData, self)._module_data_uninstall(
modules_to_remove)
return super(IrModelData, self)._module_data_uninstall(modules_to_remove)
class CleanupPurgeLineModule(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.module'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.module"
wizard_id = fields.Many2one(
'cleanup.purge.wizard.module', 'Purge Wizard', readonly=True)
"cleanup.purge.wizard.module", "Purge Wizard", readonly=True
)
@api.multi
def purge(self):
@ -40,48 +44,46 @@ class CleanupPurgeLineModule(models.TransientModel):
Uninstall modules upon manual confirmation, then reload
the database.
"""
module_names = self.filtered(lambda x: not x.purged).mapped('name')
modules = self.env['ir.module.module'].search([
('name', 'in', module_names)
])
module_names = self.filtered(lambda x: not x.purged).mapped("name")
modules = self.env["ir.module.module"].search([("name", "in", module_names)])
if not modules:
return True
self.logger.info('Purging modules %s', ', '.join(module_names))
self.logger.info("Purging modules %s", ", ".join(module_names))
modules.filtered(
lambda x: x.state not in ('uninstallable', 'uninstalled')
lambda x: x.state not in ("uninstallable", "uninstalled")
).button_immediate_uninstall()
modules.refresh()
modules.unlink()
return self.write({'purged': True})
return self.write({"purged": True})
class CleanupPurgeWizardModule(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.module'
_description = 'Purge modules'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.module"
_description = "Purge modules"
@api.model
def find(self):
res = []
IrModule = self.env['ir.module.module']
IrModule = self.env["ir.module.module"]
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):
continue
if module.state == 'uninstalled':
self.env['cleanup.purge.line.module'].create({
'name': module.name,
}).purge()
if module.state == "uninstalled":
self.env["cleanup.purge.line.module"].create(
{
"name": module.name,
}
).purge()
continue
res.append((0, 0, {'name': module.name}))
res.append((0, 0, {"name": module.name}))
if not res:
raise UserError(_('No modules found to purge'))
raise UserError(_("No modules found to purge"))
return res
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>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited
from odoo import api, models, fields
from odoo import api, fields, models
REASON_DUPLICATE = 1
REASON_DEFAULT = 2
REASON_DEFAULT_FALSE = 3
@ -9,31 +10,34 @@ REASON_UNKNOWN_MODEL = 4
class CleanupPurgeLineProperty(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.property'
_description = 'Purge properties'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.property"
_description = "Purge properties"
wizard_id = fields.Many2one(
'cleanup.purge.wizard.property', 'Purge Wizard', readonly=True)
property_id = fields.Many2one('ir.property')
reason = fields.Selection([
(REASON_DUPLICATE, 'Duplicated property'),
(REASON_DEFAULT, 'Same value as default'),
(REASON_DEFAULT_FALSE, 'Empty default property'),
(REASON_UNKNOWN_MODEL, 'Unknown model'),
])
"cleanup.purge.wizard.property", "Purge Wizard", readonly=True
)
property_id = fields.Many2one("ir.property")
reason = fields.Selection(
[
(REASON_DUPLICATE, "Duplicated property"),
(REASON_DEFAULT, "Same value as default"),
(REASON_DEFAULT_FALSE, "Empty default property"),
(REASON_UNKNOWN_MODEL, "Unknown model"),
]
)
@api.multi
def purge(self):
"""Delete properties"""
self.write({'purged': True})
return self.mapped('property_id').unlink()
self.write({"purged": True})
return self.mapped("property_id").unlink()
class CleanupPurgeWizardProperty(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.property'
_description = 'Purge properties'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.property"
_description = "Purge properties"
@api.model
def find(self):
@ -41,96 +45,118 @@ class CleanupPurgeWizardProperty(models.TransientModel):
Search property records which are duplicated or the same as the default
"""
result = []
default_properties = self.env['ir.property'].search([
('res_id', '=', False),
])
default_properties = self.env["ir.property"].search(
[
("res_id", "=", False),
]
)
handled_field_ids = []
for prop in default_properties:
value = None
try:
value = prop.get_by_record()
except KeyError:
result.append({
'name': '%s@%s: %s' % (
prop.name, prop.res_id, value,
),
'property_id': prop.id,
'reason': REASON_UNKNOWN_MODEL,
})
result.append(
{
"name": "%s@%s: %s"
% (
prop.name,
prop.res_id,
value,
),
"property_id": prop.id,
"reason": REASON_UNKNOWN_MODEL,
}
)
continue
if not value:
result.append({
'name': '%s@%s: %s' % (
prop.name, prop.res_id, value,
),
'property_id': prop.id,
'reason': REASON_DEFAULT_FALSE,
})
result.append(
{
"name": "%s@%s: %s"
% (
prop.name,
prop.res_id,
value,
),
"property_id": prop.id,
"reason": REASON_DEFAULT_FALSE,
}
)
continue
if prop.fields_id.id in handled_field_ids:
continue
domain = [
('id', '!=', prop.id),
('fields_id', '=', prop.fields_id.id),
("id", "!=", prop.id),
("fields_id", "=", prop.fields_id.id),
# =? explicitly tests for None or False, not falsyness
('value_float', '=?', prop.value_float or False),
('value_integer', '=?', prop.value_integer or False),
('value_text', '=?', prop.value_text or False),
('value_binary', '=?', prop.value_binary or False),
('value_reference', '=?', prop.value_reference or False),
('value_datetime', '=?', prop.value_datetime or False),
("value_float", "=?", prop.value_float or False),
("value_integer", "=?", prop.value_integer or False),
("value_text", "=?", prop.value_text or False),
("value_binary", "=?", prop.value_binary or False),
("value_reference", "=?", prop.value_reference or False),
("value_datetime", "=?", prop.value_datetime or False),
]
if prop.company_id:
domain.append(('company_id', '=', prop.company_id.id))
domain.append(("company_id", "=", prop.company_id.id))
else:
domain.extend([
'|',
('company_id', '=', False),
(
'company_id', 'in', self.env['res.company'].search([
(
'id', 'not in', default_properties.filtered(
lambda x: x.company_id and
x.fields_id == prop.fields_id
).ids,
domain.extend(
[
"|",
("company_id", "=", False),
(
"company_id",
"in",
self.env["res.company"]
.search(
[
(
"id",
"not in",
default_properties.filtered(
lambda x: x.company_id
and x.fields_id == prop.fields_id
).ids,
)
]
)
]).ids
),
])
.ids,
),
]
)
for redundant_property in self.env['ir.property'].search(domain):
result.append({
'name': '%s@%s: %s' % (
prop.name, redundant_property.res_id,
prop.get_by_record()
),
'property_id': redundant_property.id,
'reason': REASON_DEFAULT,
})
for redundant_property in self.env["ir.property"].search(domain):
result.append(
{
"name": "%s@%s: %s"
% (prop.name, redundant_property.res_id, prop.get_by_record()),
"property_id": redundant_property.id,
"reason": REASON_DEFAULT,
}
)
handled_field_ids.append(prop.fields_id.id)
self.env.cr.execute(
'''
"""
with grouped_properties(ids, cnt) as (
select array_agg(id), count(*)
from ir_property group by res_id, company_id, fields_id
)
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
for prop in self.env['ir.property'].search([
('id', 'in', ids)
])[1:]:
result.append({
'name': '%s@%s: %s' % (
prop.name, prop.res_id, prop.get_by_record()
),
'property_id': prop.id,
'reason': REASON_DUPLICATE,
})
for prop in self.env["ir.property"].search([("id", "in", ids)])[1:]:
result.append(
{
"name": "%s@%s: %s"
% (prop.name, prop.res_id, prop.get_by_record()),
"property_id": prop.id,
"reason": REASON_DUPLICATE,
}
)
return result
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>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# pylint: disable=consider-merging-classes-inherited
from odoo import api, fields, models, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from ..identifier_adapter import IdentifierAdapter
class CleanupPurgeLineTable(models.TransientModel):
_inherit = 'cleanup.purge.line'
_name = 'cleanup.purge.line.table'
_description = 'Purge tables wizard lines'
_inherit = "cleanup.purge.line"
_name = "cleanup.purge.line.table"
_description = "Purge tables wizard lines"
wizard_id = fields.Many2one(
'cleanup.purge.wizard.table', 'Purge Wizard', readonly=True)
"cleanup.purge.wizard.table", "Purge Wizard", readonly=True
)
@api.multi
def purge(self):
@ -22,9 +24,10 @@ class CleanupPurgeLineTable(models.TransientModel):
if self:
objs = self
else:
objs = self.env['cleanup.purge.line.table']\
.browse(self._context.get('active_ids'))
tables = objs.mapped('name')
objs = self.env["cleanup.purge.line.table"].browse(
self._context.get("active_ids")
)
tables = objs.mapped("name")
for line in objs:
if line.purged:
continue
@ -49,32 +52,35 @@ class CleanupPurgeLineTable(models.TransientModel):
WHERE af.attnum = confkey AND af.attrelid = confrelid AND
a.attnum = conkey AND a.attrelid = conrelid
AND confrelid::regclass = '%s'::regclass;
""", (IdentifierAdapter(line.name, quote=False),))
""",
(IdentifierAdapter(line.name, quote=False),),
)
for constraint in self.env.cr.fetchall():
if constraint[3] in tables:
self.logger.info(
'Dropping constraint %s on table %s (to be dropped)',
constraint[0], constraint[3])
"Dropping constraint %s on table %s (to be dropped)",
constraint[0],
constraint[3],
)
self.env.cr.execute(
"ALTER TABLE %s DROP CONSTRAINT %s",
(
IdentifierAdapter(constraint[3]),
IdentifierAdapter(constraint[0])
))
IdentifierAdapter(constraint[0]),
),
)
self.logger.info(
'Dropping table %s', line.name)
self.env.cr.execute(
"DROP TABLE %s", (IdentifierAdapter(line.name),))
line.write({'purged': True})
self.logger.info("Dropping table %s", line.name)
self.env.cr.execute("DROP TABLE %s", (IdentifierAdapter(line.name),))
line.write({"purged": True})
return True
class CleanupPurgeWizardTable(models.TransientModel):
_inherit = 'cleanup.purge.wizard'
_name = 'cleanup.purge.wizard.table'
_description = 'Purge tables'
_inherit = "cleanup.purge.wizard"
_name = "cleanup.purge.wizard.table"
_description = "Purge tables"
@api.model
def find(self):
@ -83,7 +89,7 @@ class CleanupPurgeWizardTable(models.TransientModel):
Ignore views for now.
"""
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:
continue
model_pool = self.env[model.model]
@ -91,8 +97,8 @@ class CleanupPurgeWizardTable(models.TransientModel):
known_tables += [
column.relation
for column in model_pool._fields.values()
if column.type == 'many2many' and
(column.compute is None or column.store)
if column.type == "many2many"
and (column.compute is None or column.store)
and column.relation
]
@ -100,12 +106,15 @@ class CleanupPurgeWizardTable(models.TransientModel):
"""
SELECT table_name FROM information_schema.tables
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:
raise UserError(_('No orphaned tables found'))
raise UserError(_("No orphaned tables found"))
return res
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
import logging
from odoo import _, api, fields, models
from odoo.exceptions import AccessDenied
class CleanupPurgeLine(models.AbstractModel):
""" 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)
purged = fields.Boolean('Purged', readonly=True)
wizard_id = fields.Many2one('cleanup.purge.wizard')
_name = "cleanup.purge.line"
_order = "name"
_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
def purge(self):
@ -26,22 +28,22 @@ class CleanupPurgeLine(models.AbstractModel):
@api.model
def create(self, values):
# make sure the user trying this is actually supposed to do it
if self.env.ref(
'base.group_erp_manager') not in self.env.user.groups_id:
if self.env.ref("base.group_erp_manager") not in self.env.user.groups_id:
raise AccessDenied
return super(CleanupPurgeLine, self).create(values)
class PurgeWizard(models.AbstractModel):
""" Abstract base class for the purge wizards """
_name = 'cleanup.purge.wizard'
_description = 'Purge stuff'
_name = "cleanup.purge.wizard"
_description = "Purge stuff"
@api.model
def default_get(self, fields_list):
res = super(PurgeWizard, self).default_get(fields_list)
if 'purge_line_ids' in fields_list:
res['purge_line_ids'] = self.find()
if "purge_line_ids" in fields_list:
res["purge_line_ids"] = self.find()
return res
@api.multi
@ -50,47 +52,43 @@ class PurgeWizard(models.AbstractModel):
@api.multi
def purge_all(self):
self.mapped('purge_line_ids').purge()
self.mapped("purge_line_ids").purge()
return True
@api.model
def get_wizard_action(self):
wizard = self.create({})
return {
'type': 'ir.actions.act_window',
'name': wizard.display_name,
'views': [(False, 'form')],
'res_model': self._name,
'res_id': wizard.id,
'flags': {
'action_buttons': False,
'sidebar': False,
"type": "ir.actions.act_window",
"name": wizard.display_name,
"views": [(False, "form")],
"res_model": self._name,
"res_id": wizard.id,
"flags": {
"action_buttons": False,
"sidebar": False,
},
}
@api.multi
def select_lines(self):
return {
'type': 'ir.actions.act_window',
'name': _('Select lines to purge'),
'views': [(False, 'tree'), (False, 'form')],
'res_model': self._fields['purge_line_ids'].comodel_name,
'domain': [('wizard_id', 'in', self.ids)],
"type": "ir.actions.act_window",
"name": _("Select lines to purge"),
"views": [(False, "tree"), (False, "form")],
"res_model": self._fields["purge_line_ids"].comodel_name,
"domain": [("wizard_id", "in", self.ids)],
}
@api.multi
def name_get(self):
return [
(this.id, self._description)
for this in self
]
return [(this.id, self._description) for this in self]
@api.model
def create(self, values):
# make sure the user trying this is actually supposed to do it
if self.env.ref(
'base.group_erp_manager') not in self.env.user.groups_id:
if self.env.ref("base.group_erp_manager") not in self.env.user.groups_id:
raise AccessDenied
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>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2 import ProgrammingError
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.tools import config, mute_logger
# Use post_install to get all models loaded more info: odoo/odoo#13458
@ -15,103 +16,129 @@ class TestDatabaseCleanup(TransactionCase):
self.module = None
self.model = None
# Create one property for tests
self.env['ir.property'].create({
'fields_id': self.env.ref('base.field_res_partner__name').id,
'type': 'char',
'value_text': 'My default partner name',
})
self.env["ir.property"].create(
{
"fields_id": self.env.ref("base.field_res_partner__name").id,
"type": "char",
"value_text": "My default partner name",
}
)
def test_database_cleanup(self):
# delete some index and check if our module recreated it
self.env.cr.execute('drop index res_partner_name_index')
create_indexes = self.env['cleanup.create_indexes.wizard'].create({})
self.env.cr.execute("drop index res_partner_name_index")
create_indexes = self.env["cleanup.create_indexes.wizard"].create({})
create_indexes.purge_all()
self.env.cr.execute(
'select indexname from pg_indexes '
"select indexname from pg_indexes "
"where indexname='res_partner_name_index' and "
"tablename='res_partner'"
)
self.assertEqual(self.env.cr.rowcount, 1)
# duplicate a property
duplicate_property = self.env['ir.property'].search([], limit=1).copy()
purge_property = self.env['cleanup.purge.wizard.property'].create({})
duplicate_property = self.env["ir.property"].search([], limit=1).copy()
purge_property = self.env["cleanup.purge.wizard.property"].create({})
purge_property.purge_all()
self.assertFalse(duplicate_property.exists())
# create an orphaned column
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)
partner_model = self.env['ir.model'].search([
('model', '=', 'res.partner')], limit=1)
purge_columns = self.env['cleanup.purge.wizard.column'].create({
'purge_line_ids': [(0, 0, {
'model_id': partner_model.id, 'name': 'database_cleanup_test'}
)]})
partner_model = self.env["ir.model"].search(
[("model", "=", "res.partner")], limit=1
)
purge_columns = self.env["cleanup.purge.wizard.column"].create(
{
"purge_line_ids": [
(
0,
0,
{"model_id": partner_model.id, "name": "database_cleanup_test"},
)
]
}
)
purge_columns.purge_all()
# must be removed by the wizard
with self.assertRaises(ProgrammingError):
with self.env.registry.cursor() as cr:
with mute_logger('odoo.sql_db'):
cr.execute('select database_cleanup_test from res_partner')
with mute_logger("odoo.sql_db"):
cr.execute("select database_cleanup_test from res_partner")
# create a data entry pointing nowhere
self.env.cr.execute('select max(id) + 1 from res_users')
self.env['ir.model.data'].create({
'module': 'database_cleanup',
'name': 'test_no_data_entry',
'model': 'res.users',
'res_id': self.env.cr.fetchone()[0],
})
purge_data = self.env['cleanup.purge.wizard.data'].create({})
self.env.cr.execute("select max(id) + 1 from res_users")
self.env["ir.model.data"].create(
{
"module": "database_cleanup",
"name": "test_no_data_entry",
"model": "res.users",
"res_id": self.env.cr.fetchone()[0],
}
)
purge_data = self.env["cleanup.purge.wizard.data"].create({})
purge_data.purge_all()
# must be removed by the wizard
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
self.model = self.env['ir.model'].create({
'name': 'Database cleanup test model',
'model': 'x_database.cleanup.test.model',
})
self.model = self.env["ir.model"].create(
{
"name": "Database cleanup test model",
"model": "x_database.cleanup.test.model",
}
)
self.env.cr.execute(
'insert into ir_attachment (name, res_model, res_id, type) values '
"('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({})
"insert into ir_attachment (name, res_model, res_id, type) values "
"('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({})
purge_models.purge_all()
# must be removed by the wizard
self.assertFalse(self.env['ir.model'].search([
('model', '=', 'x_database.cleanup.test.model'),
]))
self.assertFalse(
self.env["ir.model"].search(
[
("model", "=", "x_database.cleanup.test.model"),
]
)
)
# create a nonexistent module
self.module = self.env['ir.module.module'].create({
'name': 'database_cleanup_test',
'state': 'to upgrade',
})
purge_modules = self.env['cleanup.purge.wizard.module'].create({})
self.module = self.env["ir.module.module"].create(
{
"name": "database_cleanup_test",
"state": "to upgrade",
}
)
purge_modules = self.env["cleanup.purge.wizard.module"].create({})
# 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
# reference to it
original_registry = Registry.registries[self.env.cr.dbname]
config.options['test_enable'] = False
config.options["test_enable"] = False
purge_modules.purge_all()
config.options['test_enable'] = True
config.options["test_enable"] = True
# must be removed by the wizard
self.assertFalse(self.env['ir.module.module'].search([
('name', '=', 'database_cleanup_test'),
]))
self.assertFalse(
self.env["ir.module.module"].search(
[
("name", "=", "database_cleanup_test"),
]
)
)
# reset afterwards
Registry.registries[self.env.cr.dbname] = original_registry
# create an orphaned table
self.env.cr.execute('create table database_cleanup_test (test int)')
purge_tables = self.env['cleanup.purge.wizard.table'].create({})
self.env.cr.execute("create table database_cleanup_test (test int)")
purge_tables = self.env["cleanup.purge.wizard.table"].create({})
purge_tables.purge_all()
with self.assertRaises(ProgrammingError):
with self.env.registry.cursor() as cr:
with mute_logger('odoo.sql_db'):
cr.execute('select * from database_cleanup_test')
with mute_logger("odoo.sql_db"):
cr.execute("select * from database_cleanup_test")
def tearDown(self):
super(TestDatabaseCleanup, self).tearDown()
@ -120,10 +147,8 @@ class TestDatabaseCleanup(TransactionCase):
self.env.cr.rollback()
if self.module:
cr2.execute(
"DELETE FROM ir_module_module WHERE id=%s",
(self.module.id,))
"DELETE FROM ir_module_module WHERE id=%s", (self.module.id,)
)
if self.model:
cr2.execute(
"DELETE FROM ir_model WHERE id=%s",
(self.model.id,))
cr2.execute("DELETE FROM ir_model WHERE id=%s", (self.model.id,))
cr2.commit()

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="cleanup_create_indexes_wizard_view_form" model="ir.ui.view">
<field name="model">cleanup.create_indexes.wizard</field>
@ -18,8 +18,13 @@
<field name="name">Create missing indexes</field>
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<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>
<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 id="cleanup_create_indexes_line_view_tree" model="ir.ui.view">
@ -38,8 +43,14 @@
<field name="name">Create</field>
<field name="type">ir.actions.server</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="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>
</odoo>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="purge_property_view" model="ir.ui.view">
<field name="model">cleanup.purge.wizard.property</field>
@ -13,8 +13,13 @@
<field name="name">Purge properties</field>
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<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>
<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 id="purge_property_line_tree" model="ir.ui.view">
@ -32,8 +37,14 @@
<field name="name">Purge</field>
<field name="type">ir.actions.server</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="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>
</odoo>

View File

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

View File

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