[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:
Olivier Dony 2013-10-16 13:18:00 +02:00
commit 54f740960e
7 changed files with 78 additions and 52 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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 = []