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 396de27903e..14b854f0f1a 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 @@ -356,6 +357,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 @@ -470,6 +472,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 372d7e463aa..e865287a8ea 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 = [] diff --git a/openerp/tests/test_mail.py b/openerp/tests/test_mail.py index f3d327b0281..c8bbc0c862b 100755 --- a/openerp/tests/test_mail.py +++ b/openerp/tests/test_mail.py @@ -223,6 +223,19 @@ class TestCleaner(unittest2.TestCase): for ext in test_mail_examples.THUNDERBIRD_1_OUT: self.assertNotIn(ext, new_html, 'html_email_cleaner did not erase signature / quoted content') + def test_70_read_more(self): + new_html = html_email_clean(test_mail_examples.BUG1, remove=True, shorten=True, max_length=100) + for ext in test_mail_examples.BUG_1_IN: + self.assertIn(ext, new_html, 'html_email_cleaner wrongly removed valid content') + for ext in test_mail_examples.BUG_1_OUT: + self.assertNotIn(ext, new_html, 'html_email_cleaner did not removed invalid content') + + new_html = html_email_clean(test_mail_examples.BUG2, remove=True, shorten=True, max_length=4000) + for ext in test_mail_examples.BUG_2_IN: + self.assertIn(ext, new_html, 'html_email_cleaner wrongly removed valid content') + for ext in test_mail_examples.BUG_2_OUT: + self.assertNotIn(ext, new_html, 'html_email_cleaner did not removed invalid content') + def test_90_misc(self): # False boolean for text must return empty string new_html = html_email_clean(False) diff --git a/openerp/tests/test_mail_examples.py b/openerp/tests/test_mail_examples.py index 7631a28e662..54671106334 100644 --- a/openerp/tests/test_mail_examples.py +++ b/openerp/tests/test_mail_examples.py @@ -637,3 +637,322 @@ MSOFFICE_3 = """
Hi Migration Team, + +Paragraph 1, blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah. + +Paragraph 2, blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah. + +Paragraph 3, blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah blah blah blah blah blah blah +blah blah blah blah blah blah blah blah. + +Thanks. + +Regards, + +-- +Olivier Laurent +Migration Manager +OpenERP SA +Chaussée de Namur, 40 +B-1367 Gérompont +Tel: +32.81.81.37.00 +Web: http://www.openerp.com""" + +BUG_1_IN = [ + 'Hi Migration Team', + 'Paragraph 1' +] +BUG_1_OUT = [ + 'Olivier Laurent', + 'Chaussée de Namur', + '81.81.37.00', + 'openerp.com', +] + + +BUG2 = """ +
| Subject: + | +Fwd: TR: OpenERP S.A. Payment Reminder | +
|---|---|
| Date: | +Wed, 16 Oct 2013 14:11:13 +0200 | +
| From: | +Christine Herrmann <che@openerp.com> | +
| To: | +online@openerp.com | +
| Sujet: + | +TR: OpenERP S.A. Payment Reminder | +
|---|---|
| Date : + | +Wed, 16 Oct 2013 10:34:45 -0000 | +
| De : | +Ida Siwatala <infos@inzoservices.com> | +
| Répondre + + à : | +catchall@openerp.my.openerp.com | +
| Pour : + | +Christine Herrmann (che) <che@openerp.com> | +
Bonjour,
++
+
Pourriez-vous + + me faire un retour sur ce point.
++
+
Cordialement
++
+
De :
+ Ida Siwatala [mailto:infos@inzoservices.com]
+
+ Envoyé : vendredi 4 octobre 2013 20:03
+ À : 'Followers of
+ INZO-services-8-all-e-Maxime-Lisbonne-77176-Savigny-le-temple-France'
+ Objet : RE: OpenERP S.A. Payment Reminder
+
Bonsoir,
++
+
Je + + me permets de revenir vers vous par écrit , car j’ai + fait 2 appels vers votre service en exposant mon + problème, mais je n’ai pas eu de retour.
+Cela + + fait un mois que j’ai fait la souscription de votre + produit, mais je me rends compte qu’il est pas adapté à + ma situation ( fonctionnalité manquante et surtout je + n’ai pas beaucoup de temps à passer à résoudre des + bugs).
+C’est + + pourquoi , j’ai demandé qu’un accord soit trouvé avec + vous pour annuler le contrat (tout en vous payant le + mois d’utilisation de septembre).
++
+
Pourriez-vous + + me faire un retour sur ce point.
++
+
Cordialement,
++
+
Ida + + Siwatala
++
+
De :
+ che@openerp.com
+ [mailto:che@openerp.com]
+
+ Envoyé : vendredi 4 octobre 2013 17:41
+ À : infos@inzoservices.com
+ Objet : OpenERP S.A. Payment Reminder
+
Dear + + INZO services,
+Exception + + made if there was a mistake of ours, it seems that the + following amount stays unpaid. Please, take + appropriate measures in order to carry out this + payment in the next 8 days.
++
+
|
+ Date de facturation + |
+
+ Description + |
+
+ Reference + |
+
+ Due Date + |
+
+ Amount (€) + |
+
+ Lit. + |
+
|
+ 2013-09-24 + |
+
+ 2013/1121 + |
+
+ Enterprise - Inzo Services + - Juillet 2013 + |
+
+ 2013-09-24 + |
+
+ 420.0 + |
+ + |
+
+ |
+ + |
+ + |
+ + |
+ + |
+ + |
+
Amount + + due : 420.00 €
+Would + + your payment have been carried out after this mail was + sent, please ignore this message. Do not hesitate to + contact our accounting department.
+
+ Best Regards,
+ Aurore Lesage
+ OpenERP
+ Chaussée de Namur, 40
+ B-1367 Grand Rosières
+ Tel: +32.81.81.37.00 - Fax: +32.81.73.35.01
+ E-mail : ale@openerp.com
+ Web: http://www.openerp.com
-- +Christine Herrmann + +OpenERP +Chaussée de Namur, 40 +B-1367 Grand Rosières +Tel: +32.81.81.37.00 - Fax: +32.81.73.35.01 + +Web: http://www.openerp.com+