[MERGE] Feature: improved module uninstallation
bzr revid: odo@openerp.com-20120331212752-r63t69jbypbmq14g
This commit is contained in:
@@ -510,7 +510,7 @@ class actions_server(osv.osv):
|
||||
'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
|
||||
"It is a Python block that can use the same values as for the condition field"),
|
||||
'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."),
|
||||
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."),
|
||||
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
|
||||
'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
|
||||
'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
|
||||
'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -23,20 +24,21 @@ import re
|
||||
import time
|
||||
import types
|
||||
|
||||
from osv import fields,osv
|
||||
import netsvc
|
||||
from osv.orm import except_orm, browse_record
|
||||
import tools
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools import config
|
||||
from tools.translate import _
|
||||
import pooler
|
||||
from openerp.osv import fields,osv
|
||||
from openerp import netsvc, pooler, tools
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools import config
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
|
||||
EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MODULE_UNINSTALL_FLAG = '_force_unlink'
|
||||
|
||||
def _get_fields_type(self, cr, uid, context=None):
|
||||
# Avoid too many nested `if`s below, as RedHat's Python 2.6
|
||||
# break on it. See bug 939653.
|
||||
# break on it. See bug 939653.
|
||||
return sorted([(k,k) for k,v in fields.__dict__.iteritems()
|
||||
if type(v) == types.TypeType and \
|
||||
issubclass(v, fields._column) and \
|
||||
@@ -73,7 +75,7 @@ class ir_model(osv.osv):
|
||||
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
|
||||
if not domain:
|
||||
return []
|
||||
field, operator, value = domain[0]
|
||||
__, operator, value = domain[0]
|
||||
if operator not in ['=', '!=']:
|
||||
raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
|
||||
value = bool(value) if operator == '=' else not bool(value)
|
||||
@@ -136,13 +138,33 @@ class ir_model(osv.osv):
|
||||
super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
|
||||
context=context)
|
||||
|
||||
def _drop_table(self, cr, uid, ids, context=None):
|
||||
for model in self.browse(cr, uid, ids, context):
|
||||
model_pool = self.pool.get(model.model)
|
||||
cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
|
||||
result = cr.fetchone()
|
||||
if result and result[0] == 'v':
|
||||
cr.execute('DROP view %s' % (model_pool._table,))
|
||||
elif result and result[0] == 'r':
|
||||
cr.execute('DROP TABLE %s' % (model_pool._table,))
|
||||
return True
|
||||
|
||||
def unlink(self, cr, user, ids, context=None):
|
||||
for model in self.browse(cr, user, ids, context):
|
||||
if model.state != 'manual':
|
||||
raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
|
||||
# Prevent manual deletion of module tables
|
||||
if context is None: context = {}
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if not context.get(MODULE_UNINSTALL_FLAG) and \
|
||||
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
|
||||
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
|
||||
|
||||
self._drop_table(cr, user, ids, context)
|
||||
res = super(ir_model, self).unlink(cr, user, ids, context)
|
||||
pooler.restart_pool(cr.dbname)
|
||||
if not context.get(MODULE_UNINSTALL_FLAG):
|
||||
# only reload pool for normal unlink. For module uninstall the
|
||||
# reload is done independently in openerp.modules.loading
|
||||
pooler.restart_pool(cr.dbname)
|
||||
|
||||
return res
|
||||
|
||||
def write(self, cr, user, ids, vals, context=None):
|
||||
@@ -266,16 +288,30 @@ class ir_model_fields(osv.osv):
|
||||
('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
|
||||
]
|
||||
|
||||
def _drop_column(self, cr, uid, ids, context=None):
|
||||
for field in self.browse(cr, uid, ids, context):
|
||||
model = self.pool.get(field.model)
|
||||
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
|
||||
result = cr.fetchone()
|
||||
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
|
||||
column_name = cr.fetchone()
|
||||
if column_name and (result and result[0] == 'r'):
|
||||
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
|
||||
model._columns.pop(field.name, None)
|
||||
return True
|
||||
|
||||
def unlink(self, cr, user, ids, context=None):
|
||||
for field in self.browse(cr, user, ids, context):
|
||||
if field.state <> 'manual':
|
||||
raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
|
||||
#
|
||||
# MAY BE ADD A ALTER TABLE DROP ?
|
||||
#
|
||||
#Removing _columns entry for that table
|
||||
self.pool.get(field.model)._columns.pop(field.name,None)
|
||||
return super(ir_model_fields, self).unlink(cr, user, ids, context)
|
||||
# Prevent manual deletion of module columns
|
||||
if context is None: context = {}
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if not context.get(MODULE_UNINSTALL_FLAG) and \
|
||||
any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
|
||||
raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
|
||||
|
||||
self._drop_column(cr, user, ids, context)
|
||||
res = super(ir_model_fields, self).unlink(cr, user, ids, context)
|
||||
return res
|
||||
|
||||
def create(self, cr, user, vals, context=None):
|
||||
if 'model_id' in vals:
|
||||
@@ -295,7 +331,7 @@ class ir_model_fields(osv.osv):
|
||||
raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
|
||||
|
||||
if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
|
||||
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
|
||||
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
|
||||
|
||||
if self.pool.get(vals['model']):
|
||||
self.pool.get(vals['model']).__init__(self.pool, cr)
|
||||
@@ -414,7 +450,7 @@ class ir_model_fields(osv.osv):
|
||||
ctx = context.copy()
|
||||
ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
|
||||
|
||||
for model_key, patch_struct in models_patch.items():
|
||||
for __, patch_struct in models_patch.items():
|
||||
obj = patch_struct[0]
|
||||
for col_name, col_prop, val in patch_struct[1]:
|
||||
setattr(obj._columns[col_name], col_prop, val)
|
||||
@@ -622,10 +658,10 @@ class ir_model_data(osv.osv):
|
||||
def __init__(self, pool, cr):
|
||||
osv.osv.__init__(self, pool, cr)
|
||||
self.doinit = True
|
||||
|
||||
# also stored in pool to avoid being discarded along with this osv instance
|
||||
if getattr(pool, 'model_data_reference_ids', None) is None:
|
||||
self.pool.model_data_reference_ids = {}
|
||||
|
||||
self.loads = self.pool.model_data_reference_ids
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
@@ -670,6 +706,7 @@ class ir_model_data(osv.osv):
|
||||
id = False
|
||||
return id
|
||||
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
""" Regular unlink method, but make sure to clear the caches. """
|
||||
self._get_id.clear_cache(self)
|
||||
@@ -680,17 +717,14 @@ class ir_model_data(osv.osv):
|
||||
model_obj = self.pool.get(model)
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
# records created during module install should result in res.log entries that are already read!
|
||||
context = dict(context, res_log_read=True)
|
||||
|
||||
if xml_id and ('.' in xml_id):
|
||||
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
|
||||
module, xml_id = xml_id.split('.')
|
||||
if (not xml_id) and (not self.doinit):
|
||||
return False
|
||||
action_id = False
|
||||
|
||||
if xml_id:
|
||||
cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
|
||||
FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
|
||||
@@ -794,52 +828,139 @@ class ir_model_data(osv.osv):
|
||||
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
|
||||
return True
|
||||
|
||||
def _module_data_uninstall(self, cr, uid, ids, context=None):
|
||||
"""Deletes all the records referenced by the ir.model.data entries
|
||||
``ids`` along with their corresponding database backed (including
|
||||
dropping tables, columns, FKs, etc, as long as there is no other
|
||||
ir.model.data entry holding a reference to them (which indicates that
|
||||
they are still owned by another module).
|
||||
Attempts to perform the deletion in an appropriate order to maximize
|
||||
the chance of gracefully deleting all records.
|
||||
This step is performed as part of the full uninstallation of a module.
|
||||
"""
|
||||
|
||||
if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
|
||||
raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
|
||||
|
||||
context = dict(context or {})
|
||||
context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
|
||||
|
||||
ids_set = set(ids)
|
||||
wkf_todo = []
|
||||
to_unlink = []
|
||||
to_drop_table = []
|
||||
ids.sort()
|
||||
ids.reverse()
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
model = data.model
|
||||
res_id = data.res_id
|
||||
model_obj = self.pool.get(model)
|
||||
name = tools.ustr(data.name)
|
||||
|
||||
if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
|
||||
or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
|
||||
# double-check we are really going to delete all the owners of this schema element
|
||||
cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
|
||||
external_ids = [x[0] for x in cr.fetchall()]
|
||||
if (set(external_ids)-ids_set):
|
||||
# as installed modules have defined this element we must not delete it!
|
||||
continue
|
||||
|
||||
if name.startswith(EXT_ID_PREFIX_FK):
|
||||
name = name[len(EXT_ID_PREFIX_FK):]
|
||||
# test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
|
||||
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
||||
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
|
||||
if cr.fetchone():
|
||||
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
|
||||
_logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
|
||||
continue
|
||||
|
||||
if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
|
||||
name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
|
||||
cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
|
||||
if cr.fetchone() and not name in to_drop_table:
|
||||
to_drop_table.append(name)
|
||||
continue
|
||||
|
||||
if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
|
||||
name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
|
||||
# test if constraint exists
|
||||
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
|
||||
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
|
||||
if cr.fetchone():
|
||||
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
|
||||
_logger.info('Dropped CONSTRAINT %s@%s', name, model)
|
||||
continue
|
||||
|
||||
pair_to_unlink = (model, res_id)
|
||||
if pair_to_unlink not in to_unlink:
|
||||
to_unlink.append(pair_to_unlink)
|
||||
|
||||
if model == 'workflow.activity':
|
||||
# Special treatment for workflow activities: temporarily revert their
|
||||
# incoming transition and trigger an update to force all workflow items
|
||||
# to move out before deleting them
|
||||
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
|
||||
wkf_todo.extend(cr.fetchall())
|
||||
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
|
||||
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
for model,res_id in wkf_todo:
|
||||
try:
|
||||
wf_service.trg_write(uid, model, res_id, cr)
|
||||
except:
|
||||
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
|
||||
|
||||
# drop m2m relation tables
|
||||
for table in to_drop_table:
|
||||
cr.execute('DROP TABLE %s CASCADE'% (table),)
|
||||
_logger.info('Dropped table %s', table)
|
||||
|
||||
def unlink_if_refcount(to_unlink):
|
||||
for model, res_id in to_unlink:
|
||||
external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
|
||||
if (set(external_ids)-ids_set):
|
||||
# if other modules have defined this record, we must not delete it
|
||||
return
|
||||
_logger.info('Deleting %s@%s', res_id, model)
|
||||
try:
|
||||
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
|
||||
except:
|
||||
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
|
||||
|
||||
# Remove non-model records first, then model fields, and finish with models
|
||||
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
|
||||
if model not in ('ir.model','ir.model.fields'))
|
||||
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
|
||||
if model == 'ir.model.fields')
|
||||
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
|
||||
if model == 'ir.model')
|
||||
|
||||
cr.commit()
|
||||
|
||||
def _process_end(self, cr, uid, modules):
|
||||
""" Clear records removed from updated module data.
|
||||
|
||||
This method is called at the end of the module loading process.
|
||||
It is meant to removed records that are no longer present in the
|
||||
updated data. Such records are recognised as the one with an xml id
|
||||
and a module in ir_model_data and noupdate set to false, but not
|
||||
present in self.loads.
|
||||
|
||||
"""
|
||||
if not modules:
|
||||
return True
|
||||
modules = list(modules)
|
||||
module_in = ",".join(["%s"] * len(modules))
|
||||
cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
|
||||
wkf_todo = []
|
||||
to_unlink = []
|
||||
for (id, name, model, res_id,module) in cr.fetchall():
|
||||
cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
|
||||
WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
|
||||
(tuple(modules), False))
|
||||
for (id, name, model, res_id, module) in cr.fetchall():
|
||||
if (module,name) not in self.loads:
|
||||
to_unlink.append((model,res_id))
|
||||
if model=='workflow.activity':
|
||||
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
|
||||
wkf_todo.extend(cr.fetchall())
|
||||
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
|
||||
cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
|
||||
|
||||
for model,id in wkf_todo:
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
wf_service.trg_write(uid, model, id, cr)
|
||||
|
||||
cr.commit()
|
||||
if not config.get('import_partial'):
|
||||
for (model, res_id) in to_unlink:
|
||||
if self.pool.get(model):
|
||||
_logger.info('Deleting %s@%s', res_id, model)
|
||||
try:
|
||||
self.pool.get(model).unlink(cr, uid, [res_id])
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.rollback()
|
||||
_logger.warning(
|
||||
'Could not delete obsolete record with id: %d of model %s\n'
|
||||
'There should be some relation that points to this resource\n'
|
||||
'You should manually fix this and restart with --update=module',
|
||||
res_id, model)
|
||||
return True
|
||||
ir_model_data()
|
||||
self.pool.get(model).unlink(cr, uid, [res_id])
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -15,19 +15,18 @@
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv
|
||||
from tools import graph
|
||||
import netsvc
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import netsvc
|
||||
|
||||
class workflow(osv.osv):
|
||||
_name = "workflow"
|
||||
_table = "wkf"
|
||||
_order = "name"
|
||||
# _log_access = False
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'osv': fields.char('Resource Object', size=64, required=True,select=True),
|
||||
@@ -46,78 +45,33 @@ class workflow(osv.osv):
|
||||
return super(workflow, self).write(cr, user, ids, vals, context=context)
|
||||
|
||||
def get_active_workitems(self, cr, uid, res, res_id, context=None):
|
||||
|
||||
cr.execute('select * from wkf where osv=%s limit 1',(res,))
|
||||
wkfinfo = cr.dictfetchone()
|
||||
workitems = []
|
||||
|
||||
if wkfinfo:
|
||||
cr.execute('SELECT id FROM wkf_instance \
|
||||
WHERE res_id=%s AND wkf_id=%s \
|
||||
ORDER BY state LIMIT 1',
|
||||
(res_id, wkfinfo['id']))
|
||||
inst_id = cr.fetchone()
|
||||
|
||||
|
||||
cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,))
|
||||
workitems = dict(cr.fetchall())
|
||||
|
||||
workitems = dict(cr.fetchall())
|
||||
return {'wkf': wkfinfo, 'workitems': workitems}
|
||||
|
||||
|
||||
#
|
||||
# scale = (vertical-distance, horizontal-distance, min-node-width(optional), min-node-height(optional), margin(default=20))
|
||||
#
|
||||
|
||||
|
||||
# def graph_get(self, cr, uid, id, scale, context={}):
|
||||
#
|
||||
# nodes= []
|
||||
# nodes_name = []
|
||||
# transitions = []
|
||||
# start = []
|
||||
# tres = {}
|
||||
# no_ancester = []
|
||||
# workflow = self.browse(cr, uid, id, context)
|
||||
# for a in workflow.activities:
|
||||
# nodes_name.append((a.id,a.name))
|
||||
# nodes.append(a.id)
|
||||
# if a.flow_start:
|
||||
# start.append(a.id)
|
||||
# else:
|
||||
# if not a.in_transitions:
|
||||
# no_ancester.append(a.id)
|
||||
#
|
||||
# for t in a.out_transitions:
|
||||
# transitions.append((a.id, t.act_to.id))
|
||||
# tres[t.id] = (a.id, t.act_to.id)
|
||||
#
|
||||
#
|
||||
# g = graph(nodes, transitions, no_ancester)
|
||||
# g.process(start)
|
||||
# g.scale(*scale)
|
||||
# result = g.result_get()
|
||||
# results = {}
|
||||
#
|
||||
# for node in nodes_name:
|
||||
# results[str(node[0])] = result[node[0]]
|
||||
# results[str(node[0])]['name'] = node[1]
|
||||
#
|
||||
# return {'nodes': results, 'transitions': tres}
|
||||
|
||||
|
||||
def create(self, cr, user, vals, context=None):
|
||||
if not context:
|
||||
context={}
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
wf_service.clear_cache(cr, user)
|
||||
return super(workflow, self).create(cr, user, vals, context=context)
|
||||
|
||||
workflow()
|
||||
|
||||
class wkf_activity(osv.osv):
|
||||
_name = "workflow.activity"
|
||||
_table = "wkf_activity"
|
||||
_order = "name"
|
||||
# _log_access = False
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'wkf_id': fields.many2one('workflow', 'Workflow', required=True, select=True, ondelete='cascade'),
|
||||
@@ -138,22 +92,29 @@ class wkf_activity(osv.osv):
|
||||
'join_mode': lambda *a: 'XOR',
|
||||
'split_mode': lambda *a: 'XOR',
|
||||
}
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
if context is None: context = {}
|
||||
if not context.get('_force_unlink') and self.pool.get('workflow.workitem').search(cr, uid, [('act_id', 'in', ids)]):
|
||||
raise osv.except_osv(_('Operation forbidden'),
|
||||
_('Please make sure no workitems refer to an activity before deleting it!'))
|
||||
super(wkf_activity, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
wkf_activity()
|
||||
|
||||
class wkf_transition(osv.osv):
|
||||
_table = "wkf_transition"
|
||||
_name = "workflow.transition"
|
||||
# _log_access = False
|
||||
_rec_name = 'signal'
|
||||
_columns = {
|
||||
'trigger_model': fields.char('Trigger Object', size=128),
|
||||
'trigger_expr_id': fields.char('Trigger Expression', size=128),
|
||||
'signal': fields.char('Signal (button Name)', size=64,
|
||||
'signal': fields.char('Signal (button Name)', size=64,
|
||||
help="When the operation of transition comes from a button pressed in the client form, "\
|
||||
"signal tests the name of the pressed button. If signal is NULL, no button is necessary to validate this transition."),
|
||||
'group_id': fields.many2one('res.groups', 'Group Required',
|
||||
'group_id': fields.many2one('res.groups', 'Group Required',
|
||||
help="The group that a user must have to be authorized to validate this transition."),
|
||||
'condition': fields.char('Condition', required=True, size=128,
|
||||
'condition': fields.char('Condition', required=True, size=128,
|
||||
help="Expression to be satisfied if we want the transition done."),
|
||||
'act_from': fields.many2one('workflow.activity', 'Source Activity', required=True, select=True, ondelete='cascade',
|
||||
help="Source activity. When this activity is over, the condition is tested to determine if we can start the ACT_TO activity."),
|
||||
@@ -194,7 +155,7 @@ class wkf_workitem(osv.osv):
|
||||
_log_access = False
|
||||
_rec_name = 'state'
|
||||
_columns = {
|
||||
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="restrict", select=True),
|
||||
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
|
||||
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
|
||||
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
|
||||
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -19,26 +18,16 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import base64
|
||||
import cStringIO
|
||||
import imp
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import StringIO
|
||||
import urllib
|
||||
import zipfile
|
||||
import zipimport
|
||||
|
||||
import openerp.modules as addons
|
||||
import pooler
|
||||
import release
|
||||
import tools
|
||||
|
||||
from tools.parse_version import parse_version
|
||||
from tools.translate import _
|
||||
|
||||
from osv import fields, osv, orm
|
||||
from openerp import modules, pooler, release, tools
|
||||
from openerp.tools.parse_version import parse_version
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import fields, osv, orm
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -95,7 +84,7 @@ class module(osv.osv):
|
||||
def get_module_info(cls, name):
|
||||
info = {}
|
||||
try:
|
||||
info = addons.load_information_from_description_file(name)
|
||||
info = modules.load_information_from_description_file(name)
|
||||
info['version'] = release.major_version + '.' + info['version']
|
||||
except Exception:
|
||||
_logger.debug('Error when trying to fetch informations for '
|
||||
@@ -123,7 +112,7 @@ class module(osv.osv):
|
||||
if field_name is None or 'menus_by_module' in field_name:
|
||||
dmodels.append('ir.ui.menu')
|
||||
assert dmodels, "no models for %s" % field_name
|
||||
|
||||
|
||||
for module_rec in self.browse(cr, uid, ids, context=context):
|
||||
res[module_rec.id] = {
|
||||
'menus_by_module': [],
|
||||
@@ -165,7 +154,7 @@ class module(osv.osv):
|
||||
except Exception, e:
|
||||
_logger.warning('Unknown error while fetching data of %s',
|
||||
module_rec.name, exc_info=True)
|
||||
for key, value in res.iteritems():
|
||||
for key, _ in res.iteritems():
|
||||
for k, v in res[key].iteritems():
|
||||
res[key][k] = "\n".join(sorted(v))
|
||||
return res
|
||||
@@ -184,7 +173,7 @@ class module(osv.osv):
|
||||
# installed_version refer the latest version (the one on disk)
|
||||
# latest_version refer the installed version (the one in database)
|
||||
# published_version refer the version available on the repository
|
||||
'installed_version': fields.function(_get_latest_version,
|
||||
'installed_version': fields.function(_get_latest_version,
|
||||
string='Latest version', type='char'),
|
||||
'latest_version': fields.char('Installed version', size=64, readonly=True),
|
||||
'published_version': fields.char('Published Version', size=64, readonly=True),
|
||||
@@ -276,7 +265,7 @@ class module(osv.osv):
|
||||
while parts:
|
||||
part = parts.pop()
|
||||
try:
|
||||
f, path, descr = imp.find_module(part, path and [path] or None)
|
||||
_, path, _ = imp.find_module(part, path and [path] or None)
|
||||
except ImportError:
|
||||
raise ImportError('No module named %s' % (pydep,))
|
||||
|
||||
@@ -343,7 +332,6 @@ class module(osv.osv):
|
||||
# Mark them to be installed.
|
||||
if to_install_ids:
|
||||
self.button_install(cr, uid, to_install_ids, context=context)
|
||||
|
||||
return dict(ACTION_DICT, name=_('Install'))
|
||||
|
||||
def button_immediate_install(self, cr, uid, ids, context=None):
|
||||
@@ -356,7 +344,7 @@ class module(osv.osv):
|
||||
"""
|
||||
self.button_install(cr, uid, ids, context=context)
|
||||
cr.commit()
|
||||
db, pool = pooler.restart_pool(cr.dbname, update_module=True)
|
||||
_, pool = pooler.restart_pool(cr.dbname, update_module=True)
|
||||
|
||||
config = pool.get('res.config').next(cr, uid, [], context=context) or {}
|
||||
if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'):
|
||||
@@ -376,20 +364,49 @@ class module(osv.osv):
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
|
||||
return True
|
||||
|
||||
def module_uninstall(self, cr, uid, ids, context=None):
|
||||
"""Perform the various steps required to uninstall a module completely
|
||||
including the deletion of all database structures created by the module:
|
||||
tables, columns, constraints, etc."""
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
|
||||
data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)])
|
||||
ir_model_data._module_data_uninstall(cr, uid, data_ids, context)
|
||||
ir_model_data.unlink(cr, uid, data_ids, context)
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled'})
|
||||
return True
|
||||
|
||||
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
|
||||
exclude_states=['uninstalled','uninstallable','to remove'],
|
||||
context=None):
|
||||
"""Return the ids of all modules that directly or indirectly depend
|
||||
on the given module `ids`, and that satisfy the `exclude_states`
|
||||
filter"""
|
||||
if not ids: return []
|
||||
known_dep_ids = set(known_dep_ids or [])
|
||||
cr.execute('''SELECT DISTINCT m.id
|
||||
FROM
|
||||
ir_module_module_dependency d
|
||||
JOIN
|
||||
ir_module_module m ON (d.module_id=m.id)
|
||||
WHERE
|
||||
d.name IN (SELECT name from ir_module_module where id in %s) AND
|
||||
m.state NOT IN %s AND
|
||||
m.id NOT IN %s ''',
|
||||
(tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
|
||||
new_dep_ids = set([m[0] for m in cr.fetchall()])
|
||||
missing_mod_ids = new_dep_ids - known_dep_ids
|
||||
known_dep_ids |= new_dep_ids
|
||||
if missing_mod_ids:
|
||||
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
|
||||
known_dep_ids, exclude_states,context))
|
||||
return list(known_dep_ids)
|
||||
|
||||
def button_uninstall(self, cr, uid, ids, context=None):
|
||||
for module in self.browse(cr, uid, ids):
|
||||
cr.execute('''select m.state,m.name
|
||||
from
|
||||
ir_module_module_dependency d
|
||||
join
|
||||
ir_module_module m on (d.module_id=m.id)
|
||||
where
|
||||
d.name=%s and
|
||||
m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,))
|
||||
res = cr.fetchall()
|
||||
if res:
|
||||
raise orm.except_orm(_('Error'), _('Some installed modules depend on the module you plan to Uninstall :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res)))
|
||||
self.write(cr, uid, ids, {'state': 'to remove'})
|
||||
if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
|
||||
raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
|
||||
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
|
||||
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
|
||||
return dict(ACTION_DICT, name=_('Uninstall'))
|
||||
|
||||
def button_uninstall_cancel(self, cr, uid, ids, context=None):
|
||||
@@ -463,7 +480,7 @@ class module(osv.osv):
|
||||
known_mods_names = dict([(m.name, m) for m in known_mods])
|
||||
|
||||
# iterate through detected modules and update/create them in db
|
||||
for mod_name in addons.get_modules():
|
||||
for mod_name in modules.get_modules():
|
||||
mod = known_mods_names.get(mod_name)
|
||||
terp = self.get_module_info(mod_name)
|
||||
values = self.get_values_from_terp(terp)
|
||||
@@ -472,7 +489,7 @@ class module(osv.osv):
|
||||
updated_values = {}
|
||||
for key in values:
|
||||
old = getattr(mod, key)
|
||||
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
|
||||
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
|
||||
if not old == updated:
|
||||
updated_values[key] = values[key]
|
||||
if terp.get('installable', True) and mod.state == 'uninstallable':
|
||||
@@ -482,7 +499,7 @@ class module(osv.osv):
|
||||
if updated_values:
|
||||
self.write(cr, uid, mod.id, updated_values)
|
||||
else:
|
||||
mod_path = addons.get_module_path(mod_name)
|
||||
mod_path = modules.get_module_path(mod_name)
|
||||
if not mod_path:
|
||||
continue
|
||||
if not terp or not terp.get('installable', True):
|
||||
@@ -511,7 +528,7 @@ class module(osv.osv):
|
||||
if not download:
|
||||
continue
|
||||
zip_content = urllib.urlopen(mod.url).read()
|
||||
fname = addons.get_module_path(str(mod.name)+'.zip', downloaded=True)
|
||||
fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True)
|
||||
try:
|
||||
with open(fname, 'wb') as fp:
|
||||
fp.write(zip_content)
|
||||
@@ -581,17 +598,17 @@ class module(osv.osv):
|
||||
for mod in self.browse(cr, uid, ids):
|
||||
if mod.state != 'installed':
|
||||
continue
|
||||
modpath = addons.get_module_path(mod.name)
|
||||
modpath = modules.get_module_path(mod.name)
|
||||
if not modpath:
|
||||
# unable to find the module. we skip
|
||||
continue
|
||||
for lang in filter_lang:
|
||||
iso_lang = tools.get_iso_codes(lang)
|
||||
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
context2 = context and context.copy() or {}
|
||||
if f and '_' in iso_lang:
|
||||
iso_lang2 = iso_lang.split('_')[0]
|
||||
f2 = addons.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
|
||||
f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
|
||||
if f2:
|
||||
_logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
|
||||
tools.trans_load(cr, f2, lang, verbose=False, context=context)
|
||||
@@ -601,7 +618,7 @@ class module(osv.osv):
|
||||
# like "en".
|
||||
if (not f) and '_' in iso_lang:
|
||||
iso_lang = iso_lang.split('_')[0]
|
||||
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
if f:
|
||||
_logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
|
||||
tools.trans_load(cr, f, lang, verbose=False, context=context2)
|
||||
@@ -660,8 +677,12 @@ class module_dependency(osv.osv):
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
# The dependency name
|
||||
'name': fields.char('Name', size=128, select=True),
|
||||
|
||||
# The module that depends on it
|
||||
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
|
||||
|
||||
'state': fields.function(_state, type='selection', selection=[
|
||||
('uninstallable','Uninstallable'),
|
||||
('uninstalled','Not Installed'),
|
||||
|
||||
@@ -153,7 +153,9 @@
|
||||
<group col="6" colspan="2">
|
||||
<button name="button_install" states="uninstalled" string="Install" icon="terp-gtk-jump-to-ltr" type="object"/>
|
||||
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
|
||||
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"/>
|
||||
<button name="button_uninstall" states="installed" string="Uninstall (beta)"
|
||||
icon="terp-dialog-close" type="object"
|
||||
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
|
||||
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" icon="gtk-cancel" type="object"/>
|
||||
<button name="button_upgrade" states="installed" string="Upgrade" icon="terp-gtk-go-back-rtl" type="object"/>
|
||||
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2004-2012 OpenERP SA (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -19,9 +19,9 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import pooler
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
from openerp import pooler
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class base_module_upgrade(osv.osv_memory):
|
||||
""" Module Upgrade """
|
||||
@@ -34,13 +34,6 @@ class base_module_upgrade(osv.osv_memory):
|
||||
}
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
""" Changes the view dynamically
|
||||
@param self: The object pointer.
|
||||
@param cr: A database cursor
|
||||
@param uid: ID of the user currently logged in
|
||||
@param context: A standard dictionary
|
||||
@return: New arch of view.
|
||||
"""
|
||||
res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
|
||||
if view_type != 'form':
|
||||
return res
|
||||
@@ -71,45 +64,39 @@ class base_module_upgrade(osv.osv_memory):
|
||||
return ids
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
This function checks for precondition before wizard executes
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param fields: List of fields for default value
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
mod_obj = self.pool.get('ir.module.module')
|
||||
ids = self.get_module_list(cr, uid, context=context)
|
||||
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
|
||||
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
|
||||
|
||||
def upgrade_module(self, cr, uid, ids, context=None):
|
||||
mod_obj = self.pool.get('ir.module.module')
|
||||
ids = mod_obj.search(cr, uid, [('state', 'in', ['to upgrade', 'to remove', 'to install'])])
|
||||
unmet_packages = []
|
||||
mod_dep_obj = self.pool.get('ir.module.module.dependency')
|
||||
for mod in mod_obj.browse(cr, uid, ids):
|
||||
depends_mod_ids = mod_dep_obj.search(cr, uid, [('module_id', '=', mod.id)])
|
||||
for dep_mod in mod_dep_obj.browse(cr, uid, depends_mod_ids):
|
||||
if dep_mod.state in ('unknown','uninstalled'):
|
||||
unmet_packages.append(dep_mod.name)
|
||||
if len(unmet_packages):
|
||||
raise osv.except_osv(_('Unmet dependency !'), _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
|
||||
mod_obj.download(cr, uid, ids, context=context)
|
||||
cr.commit()
|
||||
_db, pool = pooler.restart_pool(cr.dbname, update_module=True)
|
||||
ir_module = self.pool.get('ir.module.module')
|
||||
|
||||
data_obj = pool.get('ir.model.data')
|
||||
id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install')
|
||||
if id2:
|
||||
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
|
||||
# install/upgrade: double-check preconditions
|
||||
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])])
|
||||
if ids:
|
||||
cr.execute("""SELECT d.name FROM ir_module_module m
|
||||
JOIN ir_module_module_dependency d ON (m.id = d.module_id)
|
||||
LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
|
||||
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""",
|
||||
(tuple(ids), ('uninstalled',)))
|
||||
unmet_packages = [x[0] for x in cr.fetchall()]
|
||||
if unmet_packages:
|
||||
raise osv.except_osv(_('Unmet dependency !'),
|
||||
_('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
|
||||
|
||||
ir_module.download(cr, uid, ids, context=context)
|
||||
cr.commit() # save before re-creating cursor below
|
||||
|
||||
pooler.restart_pool(cr.dbname, update_module=True)
|
||||
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
__, res_id = ir_model_data.get_object_reference(cr, uid, 'base', 'view_base_module_upgrade_install')
|
||||
return {
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'base.module.upgrade',
|
||||
'views': [(id2, 'form')],
|
||||
'views': [(res_id, 'form')],
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
@@ -118,6 +105,5 @@ class base_module_upgrade(osv.osv_memory):
|
||||
def config(self, cr, uid, ids, context=None):
|
||||
return self.pool.get('res.config').next(cr, uid, [], context=context)
|
||||
|
||||
base_module_upgrade()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -113,21 +113,6 @@ class groups(osv.osv):
|
||||
aid.write({'groups_id': [(4, gid)]})
|
||||
return gid
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
group_users = []
|
||||
for record in self.read(cr, uid, ids, ['users'], context=context):
|
||||
if record['users']:
|
||||
group_users.extend(record['users'])
|
||||
if group_users:
|
||||
user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
|
||||
user_names = list(set(user_names))
|
||||
if len(user_names) >= 5:
|
||||
user_names = user_names[:5] + ['...']
|
||||
raise osv.except_osv(_('Warning !'),
|
||||
_('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
|
||||
', '.join(user_names))
|
||||
return super(groups, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def get_extended_interface_group(self, cr, uid, context=None):
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
extended_group_data_id = data_obj._get_id(cr, uid, 'base', 'group_extended')
|
||||
|
||||
+38
-55
@@ -24,41 +24,25 @@
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import imp
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import zipfile
|
||||
import zipimport
|
||||
|
||||
from cStringIO import StringIO
|
||||
from os.path import join as opj
|
||||
from zipfile import PyZipFile, ZIP_DEFLATED
|
||||
|
||||
|
||||
import openerp
|
||||
import openerp.modules.db
|
||||
import openerp.modules.graph
|
||||
import openerp.modules.migration
|
||||
import openerp.netsvc as netsvc
|
||||
import openerp.osv as osv
|
||||
import openerp.pooler as pooler
|
||||
import openerp.release as release
|
||||
import openerp.tools as tools
|
||||
import openerp.tools.osutil as osutil
|
||||
import openerp.tools.assertion_report as assertion_report
|
||||
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools.translate import _
|
||||
from openerp.modules.module import \
|
||||
get_modules, get_modules_with_version, \
|
||||
load_information_from_description_file, \
|
||||
get_module_resource, zip_directory, \
|
||||
get_module_path, initialize_sys_path, \
|
||||
from openerp.modules.module import initialize_sys_path, \
|
||||
load_openerp_module, init_module_models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -99,7 +83,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
threading.currentThread().testing = True
|
||||
_load_data(cr, module_name, idref, mode, 'test')
|
||||
return True
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
_logger.error(
|
||||
'module %s: an exception occurred in a test', module_name)
|
||||
return False
|
||||
@@ -178,7 +162,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
modobj = pool.get('ir.module.module')
|
||||
|
||||
if perform_checks:
|
||||
modobj.check(cr, 1, [module_id])
|
||||
modobj.check(cr, SUPERUSER_ID, [module_id])
|
||||
|
||||
idref = {}
|
||||
|
||||
@@ -189,7 +173,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
|
||||
if package.state=='to upgrade':
|
||||
# upgrading the module information
|
||||
modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
|
||||
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
|
||||
load_init_xml(module_name, idref, mode)
|
||||
load_update_xml(module_name, idref, mode)
|
||||
load_data(module_name, idref, mode)
|
||||
@@ -218,9 +202,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
|
||||
ver = release.major_version + '.' + package.data['version']
|
||||
# Set new modules and dependencies
|
||||
modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
|
||||
modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
|
||||
# Update translations for all installed languages
|
||||
modobj.update_translations(cr, 1, [module_id], None)
|
||||
modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
|
||||
|
||||
package.state = 'installed'
|
||||
for kind in ('init', 'demo', 'update'):
|
||||
@@ -257,7 +241,7 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m
|
||||
while True:
|
||||
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
|
||||
module_list = [name for (name,) in cr.fetchall() if name not in graph]
|
||||
new_modules_in_graph = graph.add_modules(cr, module_list, force)
|
||||
graph.add_modules(cr, module_list, force)
|
||||
_logger.debug('Updating graph with %d more modules', len(module_list))
|
||||
loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
|
||||
processed_modules.extend(processed)
|
||||
@@ -321,15 +305,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
|
||||
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
|
||||
if mods:
|
||||
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
|
||||
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
|
||||
if ids:
|
||||
modobj.button_install(cr, 1, ids)
|
||||
modobj.button_install(cr, SUPERUSER_ID, ids)
|
||||
|
||||
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
|
||||
if mods:
|
||||
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
|
||||
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
|
||||
if ids:
|
||||
modobj.button_upgrade(cr, 1, ids)
|
||||
modobj.button_upgrade(cr, SUPERUSER_ID, ids)
|
||||
|
||||
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
|
||||
|
||||
@@ -339,7 +323,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
# partially installed modules (i.e. installed/to upgrade), to
|
||||
# offer a consistent system to the second part: installing
|
||||
# newly selected modules.
|
||||
states_to_load = ['installed', 'to upgrade']
|
||||
# We include the modules 'to remove' in the first step, because
|
||||
# they are part of the "currently installed" modules. They will
|
||||
# be dropped in STEP 6 later, before restarting the loading
|
||||
# process.
|
||||
states_to_load = ['installed', 'to upgrade', 'to remove']
|
||||
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
|
||||
processed_modules.extend(processed)
|
||||
if update_module:
|
||||
@@ -350,9 +338,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
# load custom models
|
||||
cr.execute('select model from ir_model where state=%s', ('manual',))
|
||||
for model in cr.dictfetchall():
|
||||
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
|
||||
pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
|
||||
|
||||
# STEP 4: Finish and cleanup
|
||||
# STEP 4: Finish and cleanup installations
|
||||
if processed_modules:
|
||||
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
|
||||
for (model, name) in cr.fetchall():
|
||||
@@ -377,33 +365,18 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
_logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
|
||||
|
||||
# Cleanup orphan records
|
||||
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
|
||||
pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
|
||||
|
||||
for kind in ('init', 'demo', 'update'):
|
||||
tools.config[kind] = {}
|
||||
|
||||
cr.commit()
|
||||
if update_module:
|
||||
# Remove records referenced from ir_model_data for modules to be
|
||||
# removed (and removed the references from ir_model_data).
|
||||
cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
|
||||
for mod_id, mod_name in cr.fetchall():
|
||||
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
|
||||
for rmod, rid in cr.fetchall():
|
||||
uid = 1
|
||||
rmod_module= pool.get(rmod)
|
||||
if rmod_module:
|
||||
# TODO group by module so that we can delete multiple ids in a call
|
||||
rmod_module.unlink(cr, uid, [rid])
|
||||
else:
|
||||
_logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
|
||||
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
|
||||
cr.commit()
|
||||
|
||||
# Remove menu items that are not referenced by any of other
|
||||
# (child) menu item, ir_values, or ir_model_data.
|
||||
# This code could be a method of ir_ui_menu.
|
||||
# TODO: remove menu without actions of children
|
||||
# STEP 5: Cleanup menus
|
||||
# Remove menu items that are not referenced by any of other
|
||||
# (child) menu item, ir_values, or ir_model_data.
|
||||
# TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
|
||||
if update_module:
|
||||
while True:
|
||||
cr.execute('''delete from
|
||||
ir_ui_menu
|
||||
@@ -419,9 +392,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
else:
|
||||
_logger.info('removed %d unused menus', cr.rowcount)
|
||||
|
||||
# Pretend that modules to be removed are actually uninstalled.
|
||||
cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
|
||||
cr.commit()
|
||||
# STEP 6: Uninstall modules to remove
|
||||
if update_module:
|
||||
# Remove records referenced from ir_model_data for modules to be
|
||||
# removed (and removed the references from ir_model_data).
|
||||
cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
|
||||
mod_ids_to_remove = [x[0] for x in cr.fetchall()]
|
||||
if mod_ids_to_remove:
|
||||
pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
|
||||
# Recursive reload, should only happen once, because there should be no
|
||||
# modules to remove next time
|
||||
cr.commit()
|
||||
_logger.info('Reloading registry once more after uninstalling modules')
|
||||
return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
|
||||
|
||||
if report.failures:
|
||||
_logger.error('At least one test failed when loading the modules.')
|
||||
|
||||
+24
-11
@@ -70,6 +70,11 @@ _schema = logging.getLogger(__name__ + '.schema')
|
||||
# List of etree._Element subclasses that we choose to ignore when parsing XML.
|
||||
from openerp.tools import SKIPPED_ELEMENT_TYPES
|
||||
|
||||
# Prefixes for external IDs of schema elements
|
||||
EXT_ID_PREFIX_FK = "_foreign_key_"
|
||||
EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_"
|
||||
EXT_ID_PREFIX_CONSTRAINT = "_constraint_"
|
||||
|
||||
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
|
||||
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
|
||||
|
||||
@@ -100,10 +105,10 @@ def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False
|
||||
|
||||
if node.get('states'):
|
||||
if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
|
||||
# TODO combine with AND or OR, use implicit AND for now.
|
||||
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
|
||||
# TODO combine with AND or OR, use implicit AND for now.
|
||||
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
|
||||
else:
|
||||
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
|
||||
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
|
||||
|
||||
for a in ('invisible', 'readonly', 'required'):
|
||||
if node.get(a):
|
||||
@@ -905,6 +910,7 @@ class BaseModel(object):
|
||||
# If new class defines a constraint with
|
||||
# same function name, we let it override
|
||||
# the old one.
|
||||
|
||||
new[c2] = c
|
||||
exist = True
|
||||
break
|
||||
@@ -2715,6 +2721,14 @@ class BaseModel(object):
|
||||
_schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
|
||||
self._table, column['attname'])
|
||||
|
||||
# quick creation of ir.model.data entry to make uninstall of schema elements easier
|
||||
def _make_ext_id(self, cr, ext_id):
|
||||
cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module))
|
||||
if not cr.rowcount:
|
||||
cr.execute("""INSERT INTO ir_model_data (name,date_init,date_update,module,model)
|
||||
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', %s, %s)""",
|
||||
(ext_id, self._module, self._name))
|
||||
|
||||
# checked version: for direct m2o starting from `self`
|
||||
def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
|
||||
assert self.is_transient() or not dest_model.is_transient(), \
|
||||
@@ -2805,7 +2819,6 @@ class BaseModel(object):
|
||||
update_custom_fields = context.get('update_custom_fields', False)
|
||||
self._field_create(cr, context=context)
|
||||
create = not self._table_exist(cr)
|
||||
|
||||
if getattr(self, '_auto', True):
|
||||
|
||||
if create:
|
||||
@@ -3040,7 +3053,7 @@ class BaseModel(object):
|
||||
|
||||
cr.commit() # start a new transaction
|
||||
|
||||
self._add_sql_constraints(cr)
|
||||
self._add_sql_constraints(cr, context["module"])
|
||||
|
||||
if create:
|
||||
self._execute_sql(cr)
|
||||
@@ -3051,11 +3064,11 @@ class BaseModel(object):
|
||||
|
||||
return todo_end
|
||||
|
||||
|
||||
def _auto_end(self, cr, context=None):
|
||||
""" Create the foreign keys recorded by _auto_init. """
|
||||
for t, k, r, d in self._foreign_keys:
|
||||
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
|
||||
self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k))
|
||||
cr.commit()
|
||||
del self._foreign_keys
|
||||
|
||||
@@ -3134,6 +3147,7 @@ class BaseModel(object):
|
||||
|
||||
def _o2m_raise_on_missing_reference(self, cr, f):
|
||||
# TODO this check should be a method on fields.one2many.
|
||||
|
||||
other = self.pool.get(f._obj)
|
||||
if other:
|
||||
# TODO the condition could use fields_get_keys().
|
||||
@@ -3141,9 +3155,9 @@ class BaseModel(object):
|
||||
if f._fields_id not in other._inherit_fields.keys():
|
||||
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
|
||||
|
||||
|
||||
def _m2m_raise_or_create_relation(self, cr, f):
|
||||
m2m_tbl, col1, col2 = f._sql_names(self)
|
||||
self._make_ext_id(cr, EXT_ID_PREFIX_M2M_TABLE + m2m_tbl)
|
||||
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
|
||||
if not cr.dictfetchall():
|
||||
if not self.pool.get(f._obj):
|
||||
@@ -3151,7 +3165,6 @@ class BaseModel(object):
|
||||
dest_model = self.pool.get(f._obj)
|
||||
ref = dest_model._table
|
||||
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
|
||||
|
||||
# create foreign key references with ondelete=cascade, unless the targets are SQL views
|
||||
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
|
||||
if not cr.fetchall():
|
||||
@@ -3167,7 +3180,7 @@ class BaseModel(object):
|
||||
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
|
||||
|
||||
|
||||
def _add_sql_constraints(self, cr):
|
||||
def _add_sql_constraints(self, cr, module):
|
||||
"""
|
||||
|
||||
Modify this model's database table constraints so they match the one in
|
||||
@@ -3180,9 +3193,9 @@ class BaseModel(object):
|
||||
for (key, con, _) in self._sql_constraints:
|
||||
conname = '%s_%s' % (self._table, key)
|
||||
|
||||
self._make_ext_id(cr, EXT_ID_PREFIX_CONSTRAINT + conname)
|
||||
cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
|
||||
existing_constraints = cr.dictfetchall()
|
||||
|
||||
sql_actions = {
|
||||
'drop': {
|
||||
'execute': False,
|
||||
@@ -3735,7 +3748,7 @@ class BaseModel(object):
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
for oid in ids:
|
||||
wf_service.trg_delete(uid, self._name, oid, cr)
|
||||
|
||||
|
||||
|
||||
self.check_access_rule(cr, uid, ids, 'unlink', context=context)
|
||||
pool_model_data = self.pool.get('ir.model.data')
|
||||
|
||||
Reference in New Issue
Block a user