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.

158 lines
5.9 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # This module copyright (C) 2014 Therp BV (<http://therp.nl>).
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. from openerp.osv import orm, fields
  22. from openerp.tools.translate import _
  23. from ..identifier_adapter import IdentifierAdapter
  24. class CleanupPurgeLineColumn(orm.TransientModel):
  25. _inherit = 'cleanup.purge.line'
  26. _name = 'cleanup.purge.line.column'
  27. _columns = {
  28. 'model_id': fields.many2one(
  29. 'ir.model', 'Model',
  30. required=True, ondelete='CASCADE'),
  31. 'wizard_id': fields.many2one(
  32. 'cleanup.purge.wizard.column', 'Purge Wizard', readonly=True),
  33. }
  34. def purge(self, cr, uid, ids, context=None):
  35. """
  36. Unlink columns upon manual confirmation.
  37. """
  38. for line in self.browse(cr, uid, ids, context=context):
  39. if line.purged:
  40. continue
  41. model_pool = self.pool[line.model_id.model]
  42. # Check whether the column actually still exists.
  43. # Inheritance such as stock.picking.in from stock.picking
  44. # can lead to double attempts at removal
  45. cr.execute(
  46. 'SELECT count(attname) FROM pg_attribute '
  47. 'WHERE attrelid = '
  48. '( SELECT oid FROM pg_class WHERE relname = %s ) '
  49. 'AND attname = %s',
  50. (model_pool._table, line.name))
  51. if not cr.fetchone()[0]:
  52. continue
  53. self.logger.info(
  54. 'Dropping column %s from table %s',
  55. line.name, model_pool._table)
  56. cr.execute(
  57. "ALTER TABLE %s DROP COLUMN %s",
  58. (
  59. IdentifierAdapter(model_pool._table),
  60. IdentifierAdapter(line.name),
  61. ))
  62. line.write({'purged': True})
  63. cr.commit()
  64. return True
  65. class CleanupPurgeWizardColumn(orm.TransientModel):
  66. _inherit = 'cleanup.purge.wizard'
  67. _name = 'cleanup.purge.wizard.column'
  68. # List of known columns in use without corresponding fields
  69. # Format: {table: [fields]}
  70. blacklist = {
  71. 'wkf_instance': ['uid'], # lp:1277899
  72. }
  73. def default_get(self, cr, uid, fields, context=None):
  74. res = super(CleanupPurgeWizardColumn, self).default_get(
  75. cr, uid, fields, context=context)
  76. if 'name' in fields:
  77. res['name'] = _('Purge columns')
  78. return res
  79. def get_orphaned_columns(self, cr, uid, model_pools, context=None):
  80. """
  81. From openobject-server/openerp/osv/orm.py
  82. Iterate on the database columns to identify columns
  83. of fields which have been removed
  84. """
  85. columns = list(set([
  86. column for model_pool in model_pools
  87. for column in model_pool._columns
  88. if not (isinstance(model_pool._columns[column],
  89. fields.function) and
  90. not model_pool._columns[column].store)
  91. ]))
  92. columns += orm.MAGIC_COLUMNS
  93. columns += self.blacklist.get(model_pools[0]._table, [])
  94. cr.execute("SELECT a.attname"
  95. " FROM pg_class c, pg_attribute a"
  96. " WHERE c.relname=%s"
  97. " AND c.oid=a.attrelid"
  98. " AND a.attisdropped=%s"
  99. " AND pg_catalog.format_type(a.atttypid, a.atttypmod)"
  100. " NOT IN ('cid', 'tid', 'oid', 'xid')"
  101. " AND a.attname NOT IN %s",
  102. (model_pools[0]._table, False, tuple(columns))),
  103. return [column[0] for column in cr.fetchall()]
  104. def find(self, cr, uid, context=None):
  105. """
  106. Search for columns that are not in the corresponding model.
  107. Group models by table to prevent false positives for columns
  108. that are only in some of the models sharing the same table.
  109. Example of this is 'sale_id' not being a field of stock.picking.in
  110. """
  111. res = []
  112. model_pool = self.pool['ir.model']
  113. model_ids = model_pool.search(cr, uid, [], context=context)
  114. # mapping of tables to tuples (model id, [pool1, pool2, ...])
  115. table2model = {}
  116. for model in model_pool.browse(cr, uid, model_ids, context=context):
  117. model_pool = self.pool.get(model.model)
  118. if not model_pool or not model_pool._auto:
  119. continue
  120. table2model.setdefault(
  121. model_pool._table, (model.id, []))[1].append(model_pool)
  122. for table, model_spec in table2model.iteritems():
  123. for column in self.get_orphaned_columns(
  124. cr, uid, model_spec[1], context=context):
  125. res.append((0, 0, {
  126. 'name': column,
  127. 'model_id': model_spec[0]}))
  128. if not res:
  129. raise orm.except_orm(
  130. _('Nothing to do'),
  131. _('No orphaned columns found'))
  132. return res
  133. _columns = {
  134. 'purge_line_ids': fields.one2many(
  135. 'cleanup.purge.line.column',
  136. 'wizard_id', 'Columns to purge'),
  137. }