diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 704488f6938..dfa6338fbdd 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -261,10 +261,14 @@ class ir_attachment(osv.osv): return len(result) if count else list(result) 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) return super(ir_attachment, self).read(cr, uid, ids, fields_to_read, context, load) 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) if 'file_size' in vals: del vals['file_size'] @@ -275,6 +279,8 @@ class ir_attachment(osv.osv): return super(ir_attachment, self).copy(cr, uid, id, default, context) def unlink(self, cr, uid, ids, context=None): + if isinstance(ids, (int, long)): + ids = [ids] self.check(cr, uid, ids, 'unlink', context=context) location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') if location: diff --git a/openerp/addons/base/ir/ir_fields.py b/openerp/addons/base/ir/ir_fields.py index 302b11eb526..09622dadd17 100644 --- a/openerp/addons/base/ir/ir_fields.py +++ b/openerp/addons/base/ir/ir_fields.py @@ -184,7 +184,7 @@ class ir_fields_converter(orm.Model): def _str_id(self, cr, uid, model, column, value, context=None): 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): try: diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 3b709934a27..6d5ac6cc80b 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -198,6 +198,7 @@ class ir_model(osv.osv): select=vals.get('select_level', '0'), update_custom_fields=True) 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) return res @@ -354,6 +355,7 @@ class ir_model_fields(osv.osv): select=vals.get('select_level', '0'), update_custom_fields=True) 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) return res @@ -468,6 +470,7 @@ class ir_model_fields(osv.osv): for col_name, col_prop, val in patch_struct[1]: setattr(obj._columns[col_name], col_prop, val) obj._auto_init(cr, ctx) + obj._auto_end(cr, ctx) # actually create FKs! openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname) return res diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index 0f9b6ea3886..6cbd90c3072 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -161,18 +161,18 @@ class ir_translation(osv.osv): ''' if context is None: context = {} - res = {} + res = dict.fromkeys(ids, False) for record in self.browse(cr, uid, ids, context=context): if record.type != 'model': res[record.id] = record.src else: model_name, field = record.name.split(',') model = self.pool.get(model_name) - #We need to take the context without the language information, because we want to read the - #value store in db and not on the one associate with current language. - context_wo_lang = context.copy() - context_wo_lang.pop('lang', None) - res[record.id] = model.read(cr, uid, record.res_id, [field], context=context_wo_lang)[field] + if model and model.exists(cr, uid, record.res_id, context=context): + # Pass context without lang, need to read real stored field, not translation + context_no_lang = dict(context, lang=None) + result = model.read(cr, uid, record.res_id, [field], context=context_no_lang) + res[record.id] = result[field] if result else False return res def _set_src(self, cr, uid, id, name, value, args, context=None): diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index 9d599ba3aac..d58f4b3186b 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -305,18 +305,16 @@ class RegistryManager(object): r, c = cr.fetchone() # Check if the model registry must be reloaded (e.g. after the # 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 _logger.info("Reloading the model registry after database signaling.") registry = cls.new(db_name) - registry.base_registry_signaling_sequence = r # Check if the model caches must be invalidated (e.g. after a write # occured on another process). Don't clear right after a registry # 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 _logger.info("Invalidating all model caches after database signaling.") - registry.base_cache_signaling_sequence = c registry.clear_caches() registry.reset_any_cache_cleared() # One possible reason caches have been invalidated is the @@ -326,6 +324,8 @@ class RegistryManager(object): for column in model._columns.values(): if hasattr(column, 'digits_change'): column.digits_change(cr) + registry.base_registry_signaling_sequence = r + registry.base_cache_signaling_sequence = c finally: cr.close() return changed diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 3f0642c9229..fc6c890bb07 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -207,27 +207,29 @@ class reference(_column): return model.name_get(cr, uid, [int(res_id)], context=context)[0][1] 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): _type = 'char' def __init__(self, string="unknown", size=None, **args): _column.__init__(self, string=string, size=size or None, **args) - self._symbol_set = (self._symbol_c, self._symbol_set_char) - - # 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') + # self._symbol_set_char defined to keep the backward compatibility + self._symbol_f = self._symbol_set_char = lambda x: _symbol_set_char(self, x) + self._symbol_set = (self._symbol_c, self._symbol_f) class text(_column): @@ -1116,6 +1118,11 @@ class function(_column): self._symbol_f = integer._symbol_f 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): if self._type == 'float': if self.digits_compute: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index b5440c2be44..cf96c8d27bd 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1010,8 +1010,10 @@ class BaseModel(object): 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))) 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)) - self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4])) + t = (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length) + 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: 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 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') cr.execute(""" SELECT 1 FROM ir_model_constraint, ir_module_module @@ -4583,9 +4589,9 @@ class BaseModel(object): return browse_null() 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``, - obtained by calling the 'store' functions of these fields, + obtained by calling the 'store' triggers of these fields, as setup by their 'store' attribute. :return: [(priority, model_name, [record_ids,], [function_fields,])] @@ -4594,42 +4600,46 @@ class BaseModel(object): stored_functions = self.pool._store_function.get(self._name, []) # 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. - 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_]))] - mapping = {} - for function in to_compute: - # use admin user for accessing objects having rules defined on store fields - target_ids = [id for id in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id] + to_compute_map = {} + target_id_results = {} + for store_trigger in triggers_to_compute: + 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 - key = (function[priority_], function[model_name_]) + key = (store_trigger[priority_], store_trigger[model_name_]) 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: - # { (10, 'model_a') : { target_id1: [ (function_1_tuple, function_2_tuple) ], ... } - # (20, 'model_a') : { target_id2: [ (function_3_tuple, function_4_tuple) ], ... } - # (99, 'model_a') : { target_id1: [ (function_5_tuple, function_6_tuple) ], ... } + # Here to_compute_map looks like: + # { (10, 'model_a') : { target_id1: [ (trigger_1_tuple, trigger_2_tuple) ], ... } + # (20, 'model_a') : { target_id2: [ (trigger_3_tuple, trigger_4_tuple) ], ... } + # (99, 'model_a') : { target_id1: [ (trigger_5_tuple, trigger_6_tuple) ], ... } # } # Now we need to generate the batch function calls list # call_map = # { (10, 'model_a') : [(10, 'model_a', [record_ids,], [function_fields,])] } call_map = {} - for ((priority,model), id_map) in mapping.iteritems(): - functions_ids_maps = {} + for ((priority,model), id_map) in to_compute_map.iteritems(): + trigger_ids_maps = {} # function_ids_maps = # { (function_1_tuple, function_2_tuple) : [target_id1, target_id2, ..] } - for id, functions in id_map.iteritems(): - functions_ids_maps.setdefault(tuple(functions), []).append(id) - for functions, ids in functions_ids_maps.iteritems(): - call_map.setdefault((priority,model),[]).append((priority, model, ids, - [f[func_field_to_compute_] for f in functions])) + for target_id, triggers in id_map.iteritems(): + trigger_ids_maps.setdefault(tuple(triggers), []).append(target_id) + for triggers, target_ids in trigger_ids_maps.iteritems(): + call_map.setdefault((priority,model),[]).append((priority, model, target_ids, + [t[func_field_to_compute_] for t in triggers])) ordered_keys = call_map.keys() ordered_keys.sort() result = []