[MIG] database_cleanup: Migration to 14.0

pull/2684/head
Stephane Mangin 2021-01-12 14:28:34 +01:00 committed by Miika Nissi
parent d1acfa4b4a
commit 7d8967fe32
No known key found for this signature in database
GPG Key ID: B20DC9FCFAF92E7F
28 changed files with 411 additions and 222 deletions

View File

@ -1,10 +1,29 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg ===================
Base Technical User
===================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3 :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/14.0/database_cleanup
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-database_cleanup
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/149/14.0
:alt: Try me on Runbot
================ |badge1| |badge2| |badge3| |badge4| |badge5|
Database cleanup
================
Clean your Odoo database from remnants of modules, models, columns and Clean your Odoo database from remnants of modules, models, columns and
tables left by uninstalled modules (prior to 7.0) or a homebrew database tables left by uninstalled modules (prior to 7.0) or a homebrew database
@ -16,6 +35,11 @@ with the technical details of the Odoo data model of *all* the modules
that have ever been installed on your database, and do not purge any module, that have ever been installed on your database, and do not purge any module,
model, column or table if you do not know exactly what you are doing. model, column or table if you do not know exactly what you are doing.
**Table of contents**
.. contents::
:local:
Usage Usage
===== =====
@ -28,42 +52,46 @@ in one big step (if you are *really* confident).
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/11.0 :target: https://runbot.odoo-community.org/runbot/149/14.0
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/database_cleanup/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback. If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20database_cleanup%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits Credits
======= =======
Images Authors
------ ~~~~~~~
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. * Therp BV
Contributors Contributors
------------ ~~~~~~~~~~~~
* Stefan Rijnhart <stefan@opener.amsterdam> * Stefan Rijnhart <stefan@opener.amsterdam>
* Holger Brunn <hbrunn@therp.nl> * Holger Brunn <hbrunn@therp.nl>
* Stéphane Mangin <stephane.mangin@camptocamp.com>
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. Maintainers
~~~~~~~~~~~
Maintainer This module is maintained by the OCA.
----------
.. image:: https://odoo-community.org/logo.png .. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association :alt: Odoo Community Association
:target: https://odoo-community.org :target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
To contribute to this module, please visit https://odoo-community.org. This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/14.0/base_technical_user>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -1,9 +1,11 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # Copyright 2014-2016 Therp BV <http://therp.nl>
# Copyright 2021 Camptocamp SA <https://camptocamp.com>
# 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": "14.0.1.0.0",
"author": "Therp BV,Odoo Community Association (OCA)", "author": "Therp BV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/server-tools",
"depends": ["base"], "depends": ["base"],
"license": "AGPL-3", "license": "AGPL-3",
"category": "Tools", "category": "Tools",
@ -18,6 +20,7 @@
"views/create_indexes.xml", "views/create_indexes.xml",
"views/purge_properties.xml", "views/purge_properties.xml",
"views/menu.xml", "views/menu.xml",
"security/ir.model.access.csv",
], ],
"installable": True, "installable": True,
} }

View File

@ -457,7 +457,7 @@ msgstr "Purge obsolete modules"
#, fuzzy #, fuzzy
#| msgid "Purge obsolete models" #| msgid "Purge obsolete models"
msgid "Purge obsolete properties" msgid "Purge obsolete properties"
msgstr "Purge obsolete models" msgstr "Purge obsolete properties"
#. module: database_cleanup #. module: database_cleanup
#: model:ir.ui.menu,name:database_cleanup.menu_purge_tables #: model:ir.ui.menu,name:database_cleanup.menu_purge_tables

View File

@ -1,7 +1,8 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# Copyright 2021 Camptocamp <https://camptocamp.com>
# 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 fields, models
from ..identifier_adapter import IdentifierAdapter from ..identifier_adapter import IdentifierAdapter
@ -9,14 +10,13 @@ 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"
_description = "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
def purge(self): def purge(self):
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 = "{}_{}_index".format(model._table, field.name) name = "{}_{}_index".format(model._table, field.name)
@ -28,8 +28,6 @@ class CreateIndexesLine(models.TransientModel):
IdentifierAdapter(field.name), IdentifierAdapter(field.name),
), ),
) )
tables.add(model._table)
for table in tables:
self.env.cr.execute("analyze %s", (IdentifierAdapter(model._table),)) self.env.cr.execute("analyze %s", (IdentifierAdapter(model._table),))
self.write( self.write(
{ {
@ -48,7 +46,6 @@ class CreateIndexesWizard(models.TransientModel):
"wizard_id", "wizard_id",
) )
@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(

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -10,14 +11,13 @@ 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 = "Cleanup Purge Line Column"
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( 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): def purge(self):
""" """
Unlink columns upon manual confirmation. Unlink columns upon manual confirmation.
@ -123,7 +123,7 @@ class CleanupPurgeWizardColumn(models.TransientModel):
model_pool 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, {"name": column, "model_id": model_spec[0]})) res.append((0, 0, {"name": column, "model_id": model_spec[0]}))
if not res: if not res:

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -9,13 +10,13 @@ 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"
_description = "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
def purge(self): def purge(self):
"""Unlink data entries upon manual confirmation.""" """Unlink data entries upon manual confirmation."""
if self: if self:

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -8,13 +9,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"
_description = "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
def purge(self): def purge(self):
"""Unlink menu entries upon manual confirmation.""" """Unlink menu entries upon manual confirmation."""
if self: if self:
@ -47,9 +48,7 @@ class CleanupPurgeWizardMenu(models.TransientModel):
): ):
if menu.action.type != "ir.actions.act_window": if menu.action.type != "ir.actions.act_window":
continue continue
if (menu.action.res_model and menu.action.res_model not in self.env) or ( if menu.action.res_model and menu.action.res_model not in self.env:
menu.action.src_model and menu.action.src_model not in self.env
):
res.append( res.append(
( (
0, 0,

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -25,7 +26,6 @@ class IrModel(models.Model):
class IrModelFields(models.Model): class IrModelFields(models.Model):
_inherit = "ir.model.fields" _inherit = "ir.model.fields"
@api.multi
def _prepare_update(self): def _prepare_update(self):
"""this function crashes for undefined models""" """this function crashes for undefined models"""
existing = self.filtered(lambda x: x.model in self.env) existing = self.filtered(lambda x: x.model in self.env)
@ -35,13 +35,12 @@ 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 = "Cleanup Purge Line Model"
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
def purge(self): def purge(self):
""" """
Unlink models upon manual confirmation. Unlink models upon manual confirmation.

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -33,12 +34,12 @@ class IrModelData(models.Model):
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"
_description = "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
def purge(self): def purge(self):
""" """
Uninstall modules upon manual confirmation, then reload Uninstall modules upon manual confirmation, then reload

View File

@ -1,18 +1,19 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# Copyright 2021 Camptocamp <https://camptocamp.com>
# 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
REASON_DUPLICATE = 1 REASON_DUPLICATE = "REASON_DUPLICATE"
REASON_DEFAULT = 2 REASON_DEFAULT = "REASON_DEFAULT"
REASON_DEFAULT_FALSE = 3 REASON_DEFAULT_FALSE = "REASON_DEFAULT_FALSE"
REASON_UNKNOWN_MODEL = 4 REASON_UNKNOWN_MODEL = "REASON_UNKNOWN_MODEL"
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 = "Cleanup Purge Line Property"
wizard_id = fields.Many2one( wizard_id = fields.Many2one(
"cleanup.purge.wizard.property", "Purge Wizard", readonly=True "cleanup.purge.wizard.property", "Purge Wizard", readonly=True
@ -27,7 +28,6 @@ class CleanupPurgeLineProperty(models.TransientModel):
] ]
) )
@api.multi
def purge(self): def purge(self):
"""Delete properties""" """Delete properties"""
self.write({"purged": True}) self.write({"purged": True})
@ -58,12 +58,7 @@ class CleanupPurgeWizardProperty(models.TransientModel):
except KeyError: except KeyError:
result.append( result.append(
{ {
"name": "%s@%s: %s" "name": "{}@{}: {}".format(prop.name, prop.res_id, value),
% (
prop.name,
prop.res_id,
value,
),
"property_id": prop.id, "property_id": prop.id,
"reason": REASON_UNKNOWN_MODEL, "reason": REASON_UNKNOWN_MODEL,
} }
@ -72,12 +67,7 @@ class CleanupPurgeWizardProperty(models.TransientModel):
if not value: if not value:
result.append( result.append(
{ {
"name": "%s@%s: %s" "name": "{}@{}: {}".format(prop.name, prop.res_id, value),
% (
prop.name,
prop.res_id,
value,
),
"property_id": prop.id, "property_id": prop.id,
"reason": REASON_DEFAULT_FALSE, "reason": REASON_DEFAULT_FALSE,
} }
@ -127,8 +117,9 @@ class CleanupPurgeWizardProperty(models.TransientModel):
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" "name": "{}@{}: {}".format(
% (prop.name, redundant_property.res_id, 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,
} }
@ -148,8 +139,9 @@ class CleanupPurgeWizardProperty(models.TransientModel):
for prop in self.env["ir.property"].search([("id", "in", ids)])[1:]: for prop in self.env["ir.property"].search([("id", "in", ids)])[1:]:
result.append( result.append(
{ {
"name": "%s@%s: %s" "name": "{}@{}: {}".format(
% (prop.name, prop.res_id, prop.get_by_record()), prop.name, prop.res_id, prop.get_by_record()
),
"property_id": prop.id, "property_id": prop.id,
"reason": REASON_DUPLICATE, "reason": REASON_DUPLICATE,
} }

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -10,13 +11,12 @@ 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 = "Cleanup Purge Line Table"
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
def purge(self): def purge(self):
""" """
Unlink tables upon manual confirmation. Unlink tables upon manual confirmation.

View File

@ -1,4 +1,5 @@
# Copyright 2014-2016 Therp BV <http://therp.nl> # 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). # 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
@ -21,7 +22,6 @@ class CleanupPurgeLine(models.AbstractModel):
logger = logging.getLogger("odoo.addons.database_cleanup") logger = logging.getLogger("odoo.addons.database_cleanup")
@api.multi
def purge(self): def purge(self):
raise NotImplementedError raise NotImplementedError
@ -46,11 +46,9 @@ class PurgeWizard(models.AbstractModel):
res["purge_line_ids"] = self.find() res["purge_line_ids"] = self.find()
return res return res
@api.multi
def find(self): def find(self):
raise NotImplementedError raise NotImplementedError
@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
@ -70,7 +68,6 @@ class PurgeWizard(models.AbstractModel):
}, },
} }
@api.multi
def select_lines(self): def select_lines(self):
return { return {
"type": "ir.actions.act_window", "type": "ir.actions.act_window",
@ -80,7 +77,6 @@ class PurgeWizard(models.AbstractModel):
"domain": [("wizard_id", "in", self.ids)], "domain": [("wizard_id", "in", self.ids)],
} }
@api.multi
def name_get(self): def name_get(self):
return [(this.id, self._description) for this in self] return [(this.id, self._description) for this in self]

View File

@ -0,0 +1,3 @@
* Stefan Rijnhart <stefan@opener.amsterdam>
* Holger Brunn <hbrunn@therp.nl>
* Stéphane Mangin <stephane.mangin@camptocamp.com>

View File

@ -0,0 +1,9 @@
Clean your Odoo database from remnants of modules, models, columns and
tables left by uninstalled modules (prior to 7.0) or a homebrew database
upgrade to a new major version of Odoo.
Caution! This module is potentially harmful and can *easily* destroy the
integrity of your data. Do not use if you are not entirely comfortable
with the technical details of the Odoo data model of *all* the modules
that have ever been installed on your database, and do not purge any module,
model, column or table if you do not know exactly what you are doing.

View File

@ -0,0 +1,10 @@
After installation of this module, go to the Settings menu -> Technical ->
Database cleanup. This menu is only available to members of the *Access Rights*
group. Go through the modules, models, columns and tables
entries under this menu (in that order) and find out if there is orphaned data
in your database. You can either delete entries by line, or sweep all entries
in one big step (if you are *really* confident).
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/11.0

View File

@ -0,0 +1,17 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_cleanup_create_indexes_line,access_cleanup_create_indexes_line,model_cleanup_create_indexes_line,base.group_user,1,1,1,1
access_cleanup_create_indexes_wizard,access_cleanup_create_indexes_wizard,model_cleanup_create_indexes_wizard,base.group_user,1,1,1,1
access_cleanup_purge_line_module,access_cleanup_purge_line_module,model_cleanup_purge_line_module,base.group_user,1,1,1,1
access_cleanup_purge_wizard_module,access_cleanup_purge_wizard_module,model_cleanup_purge_wizard_module,base.group_user,1,1,1,1
access_cleanup_purge_line_model,access_cleanup_purge_line_model,model_cleanup_purge_line_model,base.group_user,1,1,1,1
access_cleanup_purge_wizard_model,access_cleanup_purge_wizard_model,model_cleanup_purge_wizard_model,base.group_user,1,1,1,1
access_cleanup_purge_line_column,access_cleanup_purge_line_column,model_cleanup_purge_line_column,base.group_user,1,1,1,1
access_cleanup_purge_wizard_column,access_cleanup_purge_wizard_column,model_cleanup_purge_wizard_column,base.group_user,1,1,1,1
access_cleanup_purge_line_table,access_cleanup_purge_line_table,model_cleanup_purge_line_table,base.group_user,1,1,1,1
access_cleanup_purge_wizard_table,access_cleanup_purge_wizard_table,model_cleanup_purge_wizard_table,base.group_user,1,1,1,1
access_cleanup_purge_line_data,access_cleanup_purge_line_data,model_cleanup_purge_line_data,base.group_user,1,1,1,1
access_cleanup_purge_wizard_data,access_cleanup_purge_wizard_data,model_cleanup_purge_wizard_data,base.group_user,1,1,1,1
access_cleanup_purge_line_menu,access_cleanup_purge_line_menu,model_cleanup_purge_line_menu,base.group_user,1,1,1,1
access_cleanup_purge_wizard_menu,access_cleanup_purge_wizard_menu,model_cleanup_purge_wizard_menu,base.group_user,1,1,1,1
access_cleanup_purge_line_property,access_cleanup_purge_line_property,model_cleanup_purge_line_property,base.group_user,1,1,1,1
access_cleanup_purge_wizard_property,access_cleanup_purge_wizard_property,model_cleanup_purge_wizard_property,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_cleanup_create_indexes_line access_cleanup_create_indexes_line model_cleanup_create_indexes_line base.group_user 1 1 1 1
3 access_cleanup_create_indexes_wizard access_cleanup_create_indexes_wizard model_cleanup_create_indexes_wizard base.group_user 1 1 1 1
4 access_cleanup_purge_line_module access_cleanup_purge_line_module model_cleanup_purge_line_module base.group_user 1 1 1 1
5 access_cleanup_purge_wizard_module access_cleanup_purge_wizard_module model_cleanup_purge_wizard_module base.group_user 1 1 1 1
6 access_cleanup_purge_line_model access_cleanup_purge_line_model model_cleanup_purge_line_model base.group_user 1 1 1 1
7 access_cleanup_purge_wizard_model access_cleanup_purge_wizard_model model_cleanup_purge_wizard_model base.group_user 1 1 1 1
8 access_cleanup_purge_line_column access_cleanup_purge_line_column model_cleanup_purge_line_column base.group_user 1 1 1 1
9 access_cleanup_purge_wizard_column access_cleanup_purge_wizard_column model_cleanup_purge_wizard_column base.group_user 1 1 1 1
10 access_cleanup_purge_line_table access_cleanup_purge_line_table model_cleanup_purge_line_table base.group_user 1 1 1 1
11 access_cleanup_purge_wizard_table access_cleanup_purge_wizard_table model_cleanup_purge_wizard_table base.group_user 1 1 1 1
12 access_cleanup_purge_line_data access_cleanup_purge_line_data model_cleanup_purge_line_data base.group_user 1 1 1 1
13 access_cleanup_purge_wizard_data access_cleanup_purge_wizard_data model_cleanup_purge_wizard_data base.group_user 1 1 1 1
14 access_cleanup_purge_line_menu access_cleanup_purge_line_menu model_cleanup_purge_line_menu base.group_user 1 1 1 1
15 access_cleanup_purge_wizard_menu access_cleanup_purge_wizard_menu model_cleanup_purge_wizard_menu base.group_user 1 1 1 1
16 access_cleanup_purge_line_property access_cleanup_purge_line_property model_cleanup_purge_line_property base.group_user 1 1 1 1
17 access_cleanup_purge_wizard_property access_cleanup_purge_wizard_property model_cleanup_purge_wizard_property base.group_user 1 1 1 1

View File

@ -1,3 +1,12 @@
# Copyright 2016 Therp BV <http://therp.nl> # Copyright 2016 Therp BV <http://therp.nl>
# Copyright 2021 Camptocamp
# 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 . import test_database_cleanup from . import common
from . import test_create_indexes
from . import test_purge_columns
from . import test_purge_data
from . import test_purge_menus
from . import test_purge_models
from . import test_purge_modules
from . import test_purge_properties
from . import test_purge_tables

View File

@ -0,0 +1,24 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.modules.registry import Registry
from odoo.tests import TransactionCase
from odoo.tests.common import tagged
# Use post_install to get all models loaded more info: odoo/odoo#13458
@tagged("post_install", "-at_install")
class Common(TransactionCase):
def setUp(self):
super(Common, self).setUp()
# 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
self.original_registry = Registry.registries[self.env.cr.dbname]
def tearDown(self):
super(Common, self).tearDown()
# Force rollback to avoid unstable test database
self.env.cr.rollback()
# reset afterwards
Registry.registries[self.env.cr.dbname] = self.original_registry

View File

@ -0,0 +1,20 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .common import Common
class TestCreateIndexesLine(Common):
def setUp(self):
super(TestCreateIndexesLine, self).setUp()
# delete some index and check if our module recreated it
self.env.cr.execute("drop index res_partner_name_index")
def test_deleted_index(self):
wizard = self.env["cleanup.create_indexes.wizard"].create({})
wizard.purge_all()
self.env.cr.execute(
"select indexname from pg_indexes where "
"indexname='res_partner_name_index' and tablename='res_partner' "
)
self.assertEqual(self.env.cr.rowcount, 1)

View File

@ -1,154 +0,0 @@
# 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.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
@at_install(False)
@post_install(True)
class TestDatabaseCleanup(TransactionCase):
def setUp(self):
super(TestDatabaseCleanup, self).setUp()
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",
}
)
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({})
create_indexes.purge_all()
self.env.cr.execute(
"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({})
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"
)
# 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"},
)
]
}
)
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")
# 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({})
purge_data.purge_all()
# must be removed by the wizard
with self.assertRaises(ValueError):
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.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({})
purge_models.purge_all()
# must be removed by the wizard
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({})
# 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
purge_modules.purge_all()
config.options["test_enable"] = True
# must be removed by the wizard
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({})
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")
def tearDown(self):
super(TestDatabaseCleanup, self).tearDown()
with self.registry.cursor() as cr2:
# Release blocked tables with pending deletes
self.env.cr.rollback()
if self.module:
cr2.execute(
"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.commit()

View File

@ -0,0 +1,39 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2 import ProgrammingError
from odoo.tools import mute_logger
from .common import Common
class TestCleanupPurgeLineColumn(Common):
def setUp(self):
super(TestCleanupPurgeLineColumn, self).setUp()
# create an orphaned column
self.env.cr.execute(
"alter table res_partner add column database_cleanup_test int"
)
def test_empty_column(self):
# 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
)
wizard = self.env["cleanup.purge.wizard.column"].create(
{
"purge_line_ids": [
(
0,
0,
{"model_id": partner_model.id, "name": "database_cleanup_test"},
)
]
}
)
wizard.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")

View File

@ -0,0 +1,26 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .common import Common
class TestCleanupPurgeLineData(Common):
def setUp(self):
super(TestCleanupPurgeLineData, self).setUp()
# 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],
}
)
def test_pointing_nowhere(self):
wizard = self.env["cleanup.purge.wizard.data"].create({})
wizard.purge_all()
# must be removed by the wizard
with self.assertRaises(ValueError):
self.env.ref("database_cleanup.test_no_data_entry")

View File

@ -0,0 +1,34 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .common import Common
class TestCleanupPurgeLineMenu(Common):
def setUp(self):
super(TestCleanupPurgeLineMenu, self).setUp()
# create a new empty menu
self.menu = self.env["ir.ui.menu"].create({"name": "database_cleanup_test"})
def test_empty_menu(self):
wizard = self.env["cleanup.purge.wizard.menu"].create(
{
"purge_line_ids": [
(
0,
0,
{
"menu_id": self.menu.id,
},
)
]
}
)
wizard.purge_all()
self.assertFalse(
self.env["ir.ui.menu"].search(
[
("name", "=", "database_cleanup_test"),
]
)
)

View File

@ -0,0 +1,40 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .common import Common
class TestCleanupPurgeLineColumn(Common):
def setUp(self):
super(TestCleanupPurgeLineColumn, self).setUp()
# create a nonexistent model
self.model_name = "x_database.cleanup.test.model"
self.model_values = {
"name": "Database cleanup test model",
"model": self.model_name,
}
self.model = self.env["ir.model"].create(self.model_values)
self.env.cr.execute(
"insert into ir_attachment (name, res_model, res_id, type) values "
"('test attachment', %s, 42, 'binary')",
[self.model_name],
)
self.env.registry.models.pop(self.model_name)
def tearDown(self):
""" We recreate the model to avoid registry Exception at loading """
super(TestCleanupPurgeLineColumn, self).tearDown()
# FIXME: issue origin is not clear but it must be addressed.
self.model = self.env["ir.model"].create(self.model_values)
def test_empty_model(self):
wizard = self.env["cleanup.purge.wizard.model"].create({})
wizard.purge_all()
# must be removed by the wizard
self.assertFalse(
self.env["ir.model"].search(
[
("model", "=", self.model_name),
]
)
)

View File

@ -0,0 +1,35 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import threading
from odoo.tools import config
from .common import Common
class TestCleanupPurgeLineModule(Common):
def setUp(self):
super(TestCleanupPurgeLineModule, self).setUp()
# create a nonexistent module
self.module = self.env["ir.module.module"].create(
{
"name": "database_cleanup_test",
"state": "to upgrade",
}
)
def test_remove_to_upgrade_module(self):
wizard = self.env["cleanup.purge.wizard.module"].create({})
config.options["test_enable"] = False # Maybe useless now ?!
self.patch(threading.currentThread(), "testing", False)
wizard.purge_all()
config.options["test_enable"] = True # Maybe useless now ?!
self.patch(threading.currentThread(), "testing", True)
# must be removed by the wizard
self.assertFalse(
self.env["ir.module.module"].search(
[
("name", "=", "database_cleanup_test"),
]
)
)

View File

@ -0,0 +1,39 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .common import Common
class TestCleanupPurgeLineProperty(Common):
def setUp(self):
super(TestCleanupPurgeLineProperty, self).setUp()
# Create one property for tests
self.partner_name_field_id = self.env["ir.model.fields"].search(
[("name", "=", "name"), ("model_id.model", "=", "res.partner")], limit=1
)
def test_property_to_not_removed(self):
self.property = self.env["ir.property"].create(
{
"fields_id": self.partner_name_field_id.id,
"type": "char",
"value_text": "My default partner name",
"res_id": False,
}
)
wizard = self.env["cleanup.purge.wizard.property"].create({})
wizard.purge_all()
self.assertTrue(self.property.exists())
def test_property_no_value(self):
self.property = self.env["ir.property"].create(
{
"fields_id": self.partner_name_field_id.id,
"type": "char",
"value_text": False,
"res_id": False,
}
)
wizard = self.env["cleanup.purge.wizard.property"].create({})
wizard.purge_all()
self.assertFalse(self.property.exists())

View File

@ -0,0 +1,22 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2 import ProgrammingError
from odoo.tools import mute_logger
from .common import Common
class TestCleanupPurgeLineTable(Common):
def setUp(self):
super(TestCleanupPurgeLineTable, self).setUp()
# create an orphaned table
self.env.cr.execute("create table database_cleanup_test (test int)")
def test_empty_table(self):
wizard = self.env["cleanup.purge.wizard.table"].create({})
wizard.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")

View File

@ -15,7 +15,7 @@
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_menu" /> <field name="model_id" ref="database_cleanup.model_cleanup_purge_wizard_menu" />
<field name="code"> <field name="code">
action = env.get('cleanup.purge.wizard.menu').get_wizard_action() action = env.get('cleanup.purge.wizard').get_wizard_action()
</field> </field>
</record> </record>