You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
4.8 KiB

  1. # Copyright 2014-2016 Therp BV <http://therp.nl>
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  3. # pylint: disable=consider-merging-classes-inherited
  4. from odoo import _, api, fields, models
  5. from odoo.exceptions import UserError
  6. from ..identifier_adapter import IdentifierAdapter
  7. class CleanupPurgeLineColumn(models.TransientModel):
  8. _inherit = 'cleanup.purge.line'
  9. _name = 'cleanup.purge.line.column'
  10. _description = 'Purge Column Wizard Lines'
  11. model_id = fields.Many2one('ir.model', 'Model', required=True,
  12. ondelete='CASCADE')
  13. wizard_id = fields.Many2one(
  14. 'cleanup.purge.wizard.column', 'Purge Wizard', readonly=True)
  15. @api.multi
  16. def purge(self):
  17. """
  18. Unlink columns upon manual confirmation.
  19. """
  20. if self:
  21. objs = self
  22. else:
  23. objs = self.env['cleanup.purge.line.column']\
  24. .browse(self._context.get('active_ids'))
  25. for line in objs:
  26. if line.purged:
  27. continue
  28. model_pool = self.env[line.model_id.model]
  29. # Check whether the column actually still exists.
  30. # Inheritance such as stock.picking.in from stock.picking
  31. # can lead to double attempts at removal
  32. self.env.cr.execute(
  33. 'SELECT count(attname) FROM pg_attribute '
  34. 'WHERE attrelid = '
  35. '( SELECT oid FROM pg_class WHERE relname = %s ) '
  36. 'AND attname = %s',
  37. (model_pool._table, line.name))
  38. if not self.env.cr.fetchone()[0]:
  39. continue
  40. self.logger.info(
  41. 'Dropping column %s from table %s',
  42. line.name, model_pool._table)
  43. self.env.cr.execute(
  44. 'ALTER TABLE %s DROP COLUMN %s',
  45. (
  46. IdentifierAdapter(model_pool._table),
  47. IdentifierAdapter(line.name)
  48. ))
  49. line.write({'purged': True})
  50. # we need this commit because the ORM will deadlock if
  51. # we still have a pending transaction
  52. self.env.cr.commit() # pylint: disable=invalid-commit
  53. return True
  54. class CleanupPurgeWizardColumn(models.TransientModel):
  55. _inherit = 'cleanup.purge.wizard'
  56. _name = 'cleanup.purge.wizard.column'
  57. _description = 'Purge columns'
  58. # List of known columns in use without corresponding fields
  59. # Format: {table: [fields]}
  60. blacklist = {
  61. 'wkf_instance': ['uid'], # lp:1277899
  62. 'res_users': ['password', 'password_crypt'],
  63. }
  64. @api.model
  65. def get_orphaned_columns(self, model_pools):
  66. """
  67. From openobject-server/openerp/osv/orm.py
  68. Iterate on the database columns to identify columns
  69. of fields which have been removed
  70. """
  71. columns = list(set([
  72. column.name
  73. for model_pool in model_pools
  74. for column in model_pool._fields.values()
  75. if not (column.compute is not None and not column.store)
  76. ]))
  77. columns += models.MAGIC_COLUMNS
  78. columns += self.blacklist.get(model_pools[0]._table, [])
  79. self.env.cr.execute(
  80. "SELECT a.attname FROM pg_class c, pg_attribute a "
  81. "WHERE c.relname=%s AND c.oid=a.attrelid AND a.attisdropped=False "
  82. "AND pg_catalog.format_type(a.atttypid, a.atttypmod) "
  83. "NOT IN ('cid', 'tid', 'oid', 'xid') "
  84. "AND a.attname NOT IN %s",
  85. (model_pools[0]._table, tuple(columns)))
  86. return [column for column, in self.env.cr.fetchall()]
  87. @api.model
  88. def find(self):
  89. """
  90. Search for columns that are not in the corresponding model.
  91. Group models by table to prevent false positives for columns
  92. that are only in some of the models sharing the same table.
  93. Example of this is 'sale_id' not being a field of stock.picking.in
  94. """
  95. res = []
  96. # mapping of tables to tuples (model id, [pool1, pool2, ...])
  97. table2model = {}
  98. for model in self.env['ir.model'].search([]):
  99. if model.model not in self.env:
  100. continue
  101. model_pool = self.env[model.model]
  102. if not model_pool._auto:
  103. continue
  104. table2model.setdefault(
  105. model_pool._table, (model.id, [])
  106. )[1].append(model_pool)
  107. for table, model_spec in table2model.items():
  108. for column in self.get_orphaned_columns(model_spec[1]):
  109. res.append((0, 0, {
  110. 'name': column,
  111. 'model_id': model_spec[0]}))
  112. if not res:
  113. raise UserError(_('No orphaned columns found'))
  114. return res
  115. purge_line_ids = fields.One2many(
  116. 'cleanup.purge.line.column', 'wizard_id', 'Columns to purge')