[MERGE] Forward-port of latest saas-1 bugfixes, up to rev. 4912 rev-id: odo@openerp.com-20131016110621-36vvlpn8dgsabyt1
bzr revid: odo@openerp.com-20131016111800-jjybreg62bwz61zn
This commit is contained in:
commit
54f740960e
|
@ -261,10 +261,14 @@ class ir_attachment(osv.osv):
|
||||||
return len(result) if count else list(result)
|
return len(result) if count else list(result)
|
||||||
|
|
||||||
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
|
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
|
||||||
|
if isinstance(ids, (int, long)):
|
||||||
|
ids = [ids]
|
||||||
self.check(cr, uid, ids, 'read', context=context)
|
self.check(cr, uid, ids, 'read', context=context)
|
||||||
return super(ir_attachment, self).read(cr, uid, ids, fields_to_read, context, load)
|
return super(ir_attachment, self).read(cr, uid, ids, fields_to_read, context, load)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
|
if isinstance(ids, (int, long)):
|
||||||
|
ids = [ids]
|
||||||
self.check(cr, uid, ids, 'write', context=context, values=vals)
|
self.check(cr, uid, ids, 'write', context=context, values=vals)
|
||||||
if 'file_size' in vals:
|
if 'file_size' in vals:
|
||||||
del vals['file_size']
|
del vals['file_size']
|
||||||
|
@ -275,6 +279,8 @@ class ir_attachment(osv.osv):
|
||||||
return super(ir_attachment, self).copy(cr, uid, id, default, context)
|
return super(ir_attachment, self).copy(cr, uid, id, default, context)
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
def unlink(self, cr, uid, ids, context=None):
|
||||||
|
if isinstance(ids, (int, long)):
|
||||||
|
ids = [ids]
|
||||||
self.check(cr, uid, ids, 'unlink', context=context)
|
self.check(cr, uid, ids, 'unlink', context=context)
|
||||||
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location')
|
||||||
if location:
|
if location:
|
||||||
|
|
|
@ -184,7 +184,7 @@ class ir_fields_converter(orm.Model):
|
||||||
|
|
||||||
def _str_id(self, cr, uid, model, column, value, context=None):
|
def _str_id(self, cr, uid, model, column, value, context=None):
|
||||||
return value, []
|
return value, []
|
||||||
_str_to_char = _str_to_text = _str_to_binary = _str_id
|
_str_to_reference = _str_to_char = _str_to_text = _str_to_binary = _str_id
|
||||||
|
|
||||||
def _str_to_date(self, cr, uid, model, column, value, context=None):
|
def _str_to_date(self, cr, uid, model, column, value, context=None):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -198,6 +198,7 @@ class ir_model(osv.osv):
|
||||||
select=vals.get('select_level', '0'),
|
select=vals.get('select_level', '0'),
|
||||||
update_custom_fields=True)
|
update_custom_fields=True)
|
||||||
self.pool[vals['model']]._auto_init(cr, ctx)
|
self.pool[vals['model']]._auto_init(cr, ctx)
|
||||||
|
self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
|
||||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -354,6 +355,7 @@ class ir_model_fields(osv.osv):
|
||||||
select=vals.get('select_level', '0'),
|
select=vals.get('select_level', '0'),
|
||||||
update_custom_fields=True)
|
update_custom_fields=True)
|
||||||
self.pool[vals['model']]._auto_init(cr, ctx)
|
self.pool[vals['model']]._auto_init(cr, ctx)
|
||||||
|
self.pool[vals['model']]._auto_end(cr, ctx) # actually create FKs!
|
||||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -468,6 +470,7 @@ class ir_model_fields(osv.osv):
|
||||||
for col_name, col_prop, val in patch_struct[1]:
|
for col_name, col_prop, val in patch_struct[1]:
|
||||||
setattr(obj._columns[col_name], col_prop, val)
|
setattr(obj._columns[col_name], col_prop, val)
|
||||||
obj._auto_init(cr, ctx)
|
obj._auto_init(cr, ctx)
|
||||||
|
obj._auto_end(cr, ctx) # actually create FKs!
|
||||||
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
|
@ -161,18 +161,18 @@ class ir_translation(osv.osv):
|
||||||
'''
|
'''
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
res = {}
|
res = dict.fromkeys(ids, False)
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
for record in self.browse(cr, uid, ids, context=context):
|
||||||
if record.type != 'model':
|
if record.type != 'model':
|
||||||
res[record.id] = record.src
|
res[record.id] = record.src
|
||||||
else:
|
else:
|
||||||
model_name, field = record.name.split(',')
|
model_name, field = record.name.split(',')
|
||||||
model = self.pool.get(model_name)
|
model = self.pool.get(model_name)
|
||||||
#We need to take the context without the language information, because we want to read the
|
if model and model.exists(cr, uid, record.res_id, context=context):
|
||||||
#value store in db and not on the one associate with current language.
|
# Pass context without lang, need to read real stored field, not translation
|
||||||
context_wo_lang = context.copy()
|
context_no_lang = dict(context, lang=None)
|
||||||
context_wo_lang.pop('lang', None)
|
result = model.read(cr, uid, record.res_id, [field], context=context_no_lang)
|
||||||
res[record.id] = model.read(cr, uid, record.res_id, [field], context=context_wo_lang)[field]
|
res[record.id] = result[field] if result else False
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _set_src(self, cr, uid, id, name, value, args, context=None):
|
def _set_src(self, cr, uid, id, name, value, args, context=None):
|
||||||
|
|
|
@ -305,18 +305,16 @@ class RegistryManager(object):
|
||||||
r, c = cr.fetchone()
|
r, c = cr.fetchone()
|
||||||
# Check if the model registry must be reloaded (e.g. after the
|
# Check if the model registry must be reloaded (e.g. after the
|
||||||
# database has been updated by another process).
|
# database has been updated by another process).
|
||||||
if registry.base_registry_signaling_sequence != r:
|
if registry.base_registry_signaling_sequence > 1 and registry.base_registry_signaling_sequence != r:
|
||||||
changed = True
|
changed = True
|
||||||
_logger.info("Reloading the model registry after database signaling.")
|
_logger.info("Reloading the model registry after database signaling.")
|
||||||
registry = cls.new(db_name)
|
registry = cls.new(db_name)
|
||||||
registry.base_registry_signaling_sequence = r
|
|
||||||
# Check if the model caches must be invalidated (e.g. after a write
|
# Check if the model caches must be invalidated (e.g. after a write
|
||||||
# occured on another process). Don't clear right after a registry
|
# occured on another process). Don't clear right after a registry
|
||||||
# has been reload.
|
# has been reload.
|
||||||
elif registry.base_cache_signaling_sequence != c:
|
elif registry.base_cache_signaling_sequence > 1 and registry.base_cache_signaling_sequence != c:
|
||||||
changed = True
|
changed = True
|
||||||
_logger.info("Invalidating all model caches after database signaling.")
|
_logger.info("Invalidating all model caches after database signaling.")
|
||||||
registry.base_cache_signaling_sequence = c
|
|
||||||
registry.clear_caches()
|
registry.clear_caches()
|
||||||
registry.reset_any_cache_cleared()
|
registry.reset_any_cache_cleared()
|
||||||
# One possible reason caches have been invalidated is the
|
# One possible reason caches have been invalidated is the
|
||||||
|
@ -326,6 +324,8 @@ class RegistryManager(object):
|
||||||
for column in model._columns.values():
|
for column in model._columns.values():
|
||||||
if hasattr(column, 'digits_change'):
|
if hasattr(column, 'digits_change'):
|
||||||
column.digits_change(cr)
|
column.digits_change(cr)
|
||||||
|
registry.base_registry_signaling_sequence = r
|
||||||
|
registry.base_cache_signaling_sequence = c
|
||||||
finally:
|
finally:
|
||||||
cr.close()
|
cr.close()
|
||||||
return changed
|
return changed
|
||||||
|
|
|
@ -207,27 +207,29 @@ class reference(_column):
|
||||||
return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
|
return model.name_get(cr, uid, [int(res_id)], context=context)[0][1]
|
||||||
return tools.ustr(value)
|
return tools.ustr(value)
|
||||||
|
|
||||||
|
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
|
||||||
|
def _symbol_set_char(self, symb):
|
||||||
|
|
||||||
|
#TODO:
|
||||||
|
# * we need to remove the "symb==False" from the next line BUT
|
||||||
|
# for now too many things rely on this broken behavior
|
||||||
|
# * the symb==None test should be common to all data types
|
||||||
|
if symb is None or symb == False:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# we need to convert the string to a unicode object to be able
|
||||||
|
# to evaluate its length (and possibly truncate it) reliably
|
||||||
|
u_symb = tools.ustr(symb)
|
||||||
|
return u_symb[:self.size].encode('utf8')
|
||||||
|
|
||||||
class char(_column):
|
class char(_column):
|
||||||
_type = 'char'
|
_type = 'char'
|
||||||
|
|
||||||
def __init__(self, string="unknown", size=None, **args):
|
def __init__(self, string="unknown", size=None, **args):
|
||||||
_column.__init__(self, string=string, size=size or None, **args)
|
_column.__init__(self, string=string, size=size or None, **args)
|
||||||
self._symbol_set = (self._symbol_c, self._symbol_set_char)
|
# self._symbol_set_char defined to keep the backward compatibility
|
||||||
|
self._symbol_f = self._symbol_set_char = lambda x: _symbol_set_char(self, x)
|
||||||
# takes a string (encoded in utf8) and returns a string (encoded in utf8)
|
self._symbol_set = (self._symbol_c, self._symbol_f)
|
||||||
def _symbol_set_char(self, symb):
|
|
||||||
#TODO:
|
|
||||||
# * we need to remove the "symb==False" from the next line BUT
|
|
||||||
# for now too many things rely on this broken behavior
|
|
||||||
# * the symb==None test should be common to all data types
|
|
||||||
if symb is None or symb == False:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# we need to convert the string to a unicode object to be able
|
|
||||||
# to evaluate its length (and possibly truncate it) reliably
|
|
||||||
u_symb = tools.ustr(symb)
|
|
||||||
|
|
||||||
return u_symb[:self.size].encode('utf8')
|
|
||||||
|
|
||||||
|
|
||||||
class text(_column):
|
class text(_column):
|
||||||
|
@ -1116,6 +1118,11 @@ class function(_column):
|
||||||
self._symbol_f = integer._symbol_f
|
self._symbol_f = integer._symbol_f
|
||||||
self._symbol_set = integer._symbol_set
|
self._symbol_set = integer._symbol_set
|
||||||
|
|
||||||
|
if type == 'char':
|
||||||
|
self._symbol_c = char._symbol_c
|
||||||
|
self._symbol_f = lambda x: _symbol_set_char(self, x)
|
||||||
|
self._symbol_set = (self._symbol_c, self._symbol_f)
|
||||||
|
|
||||||
def digits_change(self, cr):
|
def digits_change(self, cr):
|
||||||
if self._type == 'float':
|
if self._type == 'float':
|
||||||
if self.digits_compute:
|
if self.digits_compute:
|
||||||
|
|
|
@ -1010,8 +1010,10 @@ class BaseModel(object):
|
||||||
raise except_orm('Error',
|
raise except_orm('Error',
|
||||||
('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
|
('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
|
||||||
self.pool._store_function.setdefault(object, [])
|
self.pool._store_function.setdefault(object, [])
|
||||||
self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
|
t = (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length)
|
||||||
self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
|
if not t in self.pool._store_function[object]:
|
||||||
|
self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
|
||||||
|
self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
|
||||||
|
|
||||||
for (key, _, msg) in self._sql_constraints:
|
for (key, _, msg) in self._sql_constraints:
|
||||||
self.pool._sql_error[self._table+'_'+key] = msg
|
self.pool._sql_error[self._table+'_'+key] = msg
|
||||||
|
@ -2887,8 +2889,12 @@ class BaseModel(object):
|
||||||
"""
|
"""
|
||||||
Record the creation of a constraint for this model, to make it possible
|
Record the creation of a constraint for this model, to make it possible
|
||||||
to delete it later when the module is uninstalled. Type can be either
|
to delete it later when the module is uninstalled. Type can be either
|
||||||
'f' or 'u' depending on the constraing being a foreign key or not.
|
'f' or 'u' depending on the constraint being a foreign key or not.
|
||||||
"""
|
"""
|
||||||
|
if not self._module:
|
||||||
|
# no need to save constraints for custom models as they're not part
|
||||||
|
# of any module
|
||||||
|
return
|
||||||
assert type in ('f', 'u')
|
assert type in ('f', 'u')
|
||||||
cr.execute("""
|
cr.execute("""
|
||||||
SELECT 1 FROM ir_model_constraint, ir_module_module
|
SELECT 1 FROM ir_model_constraint, ir_module_module
|
||||||
|
@ -4583,9 +4589,9 @@ class BaseModel(object):
|
||||||
return browse_null()
|
return browse_null()
|
||||||
|
|
||||||
def _store_get_values(self, cr, uid, ids, fields, context):
|
def _store_get_values(self, cr, uid, ids, fields, context):
|
||||||
"""Returns an ordered list of fields.functions to call due to
|
"""Returns an ordered list of fields.function to call due to
|
||||||
an update operation on ``fields`` of records with ``ids``,
|
an update operation on ``fields`` of records with ``ids``,
|
||||||
obtained by calling the 'store' functions of these fields,
|
obtained by calling the 'store' triggers of these fields,
|
||||||
as setup by their 'store' attribute.
|
as setup by their 'store' attribute.
|
||||||
|
|
||||||
:return: [(priority, model_name, [record_ids,], [function_fields,])]
|
:return: [(priority, model_name, [record_ids,], [function_fields,])]
|
||||||
|
@ -4594,42 +4600,46 @@ class BaseModel(object):
|
||||||
stored_functions = self.pool._store_function.get(self._name, [])
|
stored_functions = self.pool._store_function.get(self._name, [])
|
||||||
|
|
||||||
# use indexed names for the details of the stored_functions:
|
# use indexed names for the details of the stored_functions:
|
||||||
model_name_, func_field_to_compute_, id_mapping_fnct_, trigger_fields_, priority_ = range(5)
|
model_name_, func_field_to_compute_, target_ids_func_, trigger_fields_, priority_ = range(5)
|
||||||
|
|
||||||
# only keep functions that should be triggered for the ``fields``
|
# only keep store triggers that should be triggered for the ``fields``
|
||||||
# being written to.
|
# being written to.
|
||||||
to_compute = [f for f in stored_functions \
|
triggers_to_compute = [f for f in stored_functions \
|
||||||
if ((not f[trigger_fields_]) or set(fields).intersection(f[trigger_fields_]))]
|
if ((not f[trigger_fields_]) or set(fields).intersection(f[trigger_fields_]))]
|
||||||
|
|
||||||
mapping = {}
|
to_compute_map = {}
|
||||||
for function in to_compute:
|
target_id_results = {}
|
||||||
# use admin user for accessing objects having rules defined on store fields
|
for store_trigger in triggers_to_compute:
|
||||||
target_ids = [id for id in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id]
|
target_func_id_ = id(store_trigger[target_ids_func_])
|
||||||
|
if not target_func_id_ in target_id_results:
|
||||||
|
# use admin user for accessing objects having rules defined on store fields
|
||||||
|
target_id_results[target_func_id_] = [i for i in store_trigger[target_ids_func_](self, cr, SUPERUSER_ID, ids, context) if i]
|
||||||
|
target_ids = target_id_results[target_func_id_]
|
||||||
|
|
||||||
# the compound key must consider the priority and model name
|
# the compound key must consider the priority and model name
|
||||||
key = (function[priority_], function[model_name_])
|
key = (store_trigger[priority_], store_trigger[model_name_])
|
||||||
for target_id in target_ids:
|
for target_id in target_ids:
|
||||||
mapping.setdefault(key, {}).setdefault(target_id,set()).add(tuple(function))
|
to_compute_map.setdefault(key, {}).setdefault(target_id,set()).add(tuple(store_trigger))
|
||||||
|
|
||||||
# Here mapping looks like:
|
# Here to_compute_map looks like:
|
||||||
# { (10, 'model_a') : { target_id1: [ (function_1_tuple, function_2_tuple) ], ... }
|
# { (10, 'model_a') : { target_id1: [ (trigger_1_tuple, trigger_2_tuple) ], ... }
|
||||||
# (20, 'model_a') : { target_id2: [ (function_3_tuple, function_4_tuple) ], ... }
|
# (20, 'model_a') : { target_id2: [ (trigger_3_tuple, trigger_4_tuple) ], ... }
|
||||||
# (99, 'model_a') : { target_id1: [ (function_5_tuple, function_6_tuple) ], ... }
|
# (99, 'model_a') : { target_id1: [ (trigger_5_tuple, trigger_6_tuple) ], ... }
|
||||||
# }
|
# }
|
||||||
|
|
||||||
# Now we need to generate the batch function calls list
|
# Now we need to generate the batch function calls list
|
||||||
# call_map =
|
# call_map =
|
||||||
# { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] }
|
# { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] }
|
||||||
call_map = {}
|
call_map = {}
|
||||||
for ((priority,model), id_map) in mapping.iteritems():
|
for ((priority,model), id_map) in to_compute_map.iteritems():
|
||||||
functions_ids_maps = {}
|
trigger_ids_maps = {}
|
||||||
# function_ids_maps =
|
# function_ids_maps =
|
||||||
# { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
|
# { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] }
|
||||||
for id, functions in id_map.iteritems():
|
for target_id, triggers in id_map.iteritems():
|
||||||
functions_ids_maps.setdefault(tuple(functions), []).append(id)
|
trigger_ids_maps.setdefault(tuple(triggers), []).append(target_id)
|
||||||
for functions, ids in functions_ids_maps.iteritems():
|
for triggers, target_ids in trigger_ids_maps.iteritems():
|
||||||
call_map.setdefault((priority,model),[]).append((priority, model, ids,
|
call_map.setdefault((priority,model),[]).append((priority, model, target_ids,
|
||||||
[f[func_field_to_compute_] for f in functions]))
|
[t[func_field_to_compute_] for t in triggers]))
|
||||||
ordered_keys = call_map.keys()
|
ordered_keys = call_map.keys()
|
||||||
ordered_keys.sort()
|
ordered_keys.sort()
|
||||||
result = []
|
result = []
|
||||||
|
|
Loading…
Reference in New Issue