136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
# Copyright 2014-2016 Therp BV <http://therp.nl>
|
|
# Copyright 2021 Camptocamp <https://camptocamp.com>
|
|
# 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.exceptions import UserError
|
|
|
|
from ..identifier_adapter import IdentifierAdapter
|
|
|
|
|
|
class CleanupPurgeLineColumn(models.TransientModel):
|
|
_inherit = "cleanup.purge.line"
|
|
_name = "cleanup.purge.line.column"
|
|
_description = "Cleanup Purge Line Column"
|
|
|
|
model_id = fields.Many2one("ir.model", "Model", required=True, ondelete="CASCADE")
|
|
wizard_id = fields.Many2one(
|
|
"cleanup.purge.wizard.column", "Purge Wizard", readonly=True
|
|
)
|
|
|
|
def purge(self):
|
|
"""
|
|
Unlink columns upon manual confirmation.
|
|
"""
|
|
if self:
|
|
objs = self
|
|
else:
|
|
objs = self.env["cleanup.purge.line.column"].browse(
|
|
self._context.get("active_ids")
|
|
)
|
|
for line in objs:
|
|
if line.purged:
|
|
continue
|
|
model_pool = self.env[line.model_id.model]
|
|
# Check whether the column actually still exists.
|
|
# 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),
|
|
)
|
|
if not self.env.cr.fetchone()[0]:
|
|
continue
|
|
|
|
self.logger.info(
|
|
"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})
|
|
# we need this commit because the ORM will deadlock if
|
|
# we still have a pending transaction
|
|
self.env.cr.commit() # pylint: disable=invalid-commit
|
|
return True
|
|
|
|
|
|
class CleanupPurgeWizardColumn(models.TransientModel):
|
|
_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"],
|
|
}
|
|
|
|
@api.model
|
|
def get_orphaned_columns(self, model_pools):
|
|
"""
|
|
From openobject-server/openerp/osv/orm.py
|
|
Iterate on the database columns to identify columns
|
|
of fields which have been removed
|
|
"""
|
|
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, [])
|
|
|
|
self.env.cr.execute(
|
|
"SELECT a.attname FROM pg_class c, pg_attribute a "
|
|
"WHERE c.relname=%s AND c.oid=a.attrelid AND a.attisdropped=False "
|
|
"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)),
|
|
)
|
|
return [column for column, in self.env.cr.fetchall()]
|
|
|
|
@api.model
|
|
def find(self):
|
|
"""
|
|
Search for columns that are not in the corresponding model.
|
|
|
|
Group models by table to prevent false positives for columns
|
|
that are only in some of the models sharing the same table.
|
|
Example of this is 'sale_id' not being a field of stock.picking.in
|
|
"""
|
|
res = []
|
|
|
|
# mapping of tables to tuples (model id, [pool1, pool2, ...])
|
|
table2model = {}
|
|
|
|
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
|
|
)
|
|
|
|
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]}))
|
|
if not res:
|
|
raise UserError(_("No orphaned columns found"))
|
|
return res
|
|
|
|
purge_line_ids = fields.One2many(
|
|
"cleanup.purge.line.column", "wizard_id", "Columns to purge"
|
|
)
|