[MERGE] saas2
bzr revid: nicolas.vanhoren@openerp.com-20131018103501-sns9zca0nmpm9efn
This commit is contained in:
commit
5696282656
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -637,3 +637,322 @@ MSOFFICE_3 = """<div>
|
|||
|
||||
MSOFFICE_3_IN = ['I saw your boss yesterday']
|
||||
MSOFFICE_3_OUT = ['I noticed you recently downloaded OpenERP.', 'You indicated that you wish', 'Belgium: +32.81.81.37.00']
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Test cases coming from bugs
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# bug: read more not apparent, strange message in read more span
|
||||
BUG1 = """<pre>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</pre>"""
|
||||
|
||||
BUG_1_IN = [
|
||||
'Hi Migration Team',
|
||||
'Paragraph 1'
|
||||
]
|
||||
BUG_1_OUT = [
|
||||
'Olivier Laurent',
|
||||
'Chaussée de Namur',
|
||||
'81.81.37.00',
|
||||
'openerp.com',
|
||||
]
|
||||
|
||||
|
||||
BUG2 = """
|
||||
<div>
|
||||
<br>
|
||||
<div class="moz-forward-container"><br>
|
||||
<br>
|
||||
-------- Original Message --------
|
||||
<table class="moz-email-headers-table" border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Subject:
|
||||
</th>
|
||||
<td>Fwd: TR: OpenERP S.A. Payment Reminder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Date: </th>
|
||||
<td>Wed, 16 Oct 2013 14:11:13 +0200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">From: </th>
|
||||
<td>Christine Herrmann <a class="moz-txt-link-rfc2396E" href="mailto:che@openerp.com"><che@openerp.com></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">To: </th>
|
||||
<td><a class="moz-txt-link-abbreviated" href="mailto:online@openerp.com">online@openerp.com</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<div class="moz-forward-container"><br>
|
||||
<br>
|
||||
-------- Message original --------
|
||||
<table class="moz-email-headers-table" border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Sujet:
|
||||
</th>
|
||||
<td>TR: OpenERP S.A. Payment Reminder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Date :
|
||||
</th>
|
||||
<td>Wed, 16 Oct 2013 10:34:45 -0000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">De : </th>
|
||||
<td>Ida Siwatala <a class="moz-txt-link-rfc2396E" href="mailto:infos@inzoservices.com"><infos@inzoservices.com></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Répondre
|
||||
|
||||
à : </th>
|
||||
<td><a class="moz-txt-link-abbreviated" href="mailto:catchall@openerp.my.openerp.com">catchall@openerp.my.openerp.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th nowrap="" valign="BASELINE" align="RIGHT">Pour :
|
||||
</th>
|
||||
<td>Christine Herrmann (che) <a class="moz-txt-link-rfc2396E" href="mailto:che@openerp.com"><che@openerp.com></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<br>
|
||||
<div>
|
||||
<div class="WordSection1">
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Bonjour,</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Pourriez-vous
|
||||
|
||||
me faire un retour sur ce point.</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Cordialement</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<div>
|
||||
<div style="border:none;border-top:solid #B5C4DF
|
||||
1.0pt;padding:3.0pt 0cm 0cm 0cm">
|
||||
<p class="MsoNormal"><b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif"">De :</span></b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif"">
|
||||
Ida Siwatala [<a class="moz-txt-link-freetext" href="mailto:infos@inzoservices.com">mailto:infos@inzoservices.com</a>]
|
||||
<br>
|
||||
<b>Envoyé :</b> vendredi 4 octobre 2013 20:03<br>
|
||||
<b>À :</b> 'Followers of
|
||||
INZO-services-8-all-e-Maxime-Lisbonne-77176-Savigny-le-temple-France'<br>
|
||||
<b>Objet :</b> RE: OpenERP S.A. Payment Reminder</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Bonsoir,</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">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.</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">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). </span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">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).</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Pourriez-vous
|
||||
|
||||
me faire un retour sur ce point.</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Cordialement,</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D">Ida
|
||||
|
||||
Siwatala</span></p>
|
||||
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D"></span></p>
|
||||
<p> </p>
|
||||
<p class="MsoNormal"><b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif"">De :</span></b><span style="font-size:10.0pt;font-family:"Tahoma","sans-serif"">
|
||||
<a href="mailto:che@openerp.com">che@openerp.com</a>
|
||||
[<a href="mailto:che@openerp.com">mailto:che@openerp.com</a>]
|
||||
<br>
|
||||
<b>Envoyé :</b> vendredi 4 octobre 2013 17:41<br>
|
||||
<b>À :</b> <a href="mailto:infos@inzoservices.com">infos@inzoservices.com</a><br>
|
||||
<b>Objet :</b> OpenERP S.A. Payment Reminder</span></p>
|
||||
<p> </p>
|
||||
<div>
|
||||
<p style="background:white"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222">Dear
|
||||
|
||||
INZO services,</span></p>
|
||||
<p style="background:white"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222">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. </span></p>
|
||||
<p class="MsoNormal" style="background:white"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222"></span></p>
|
||||
<p> </p>
|
||||
<table class="MsoNormalTable" style="width:100.0%;border:outset 1.5pt" width="100%" border="1" cellpadding="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Date de facturation</p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Description</p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Reference</p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Due Date</p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Amount (€)</p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal">Lit.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal"><b>2013-09-24</b></p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal"><b>2013/1121</b></p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal"><b>Enterprise - Inzo Services
|
||||
- Juillet 2013</b></p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal"><b>2013-09-24</b></p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt">
|
||||
<p class="MsoNormal"><b>420.0</b></p>
|
||||
</td>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt"><br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:.75pt .75pt .75pt .75pt"><br>
|
||||
</td>
|
||||
<td style="border:none;padding:.75pt .75pt .75pt
|
||||
.75pt"><br>
|
||||
</td>
|
||||
<td style="border:none;padding:.75pt .75pt .75pt
|
||||
.75pt"><br>
|
||||
</td>
|
||||
<td style="border:none;padding:.75pt .75pt .75pt
|
||||
.75pt"><br>
|
||||
</td>
|
||||
<td style="border:none;padding:.75pt .75pt .75pt
|
||||
.75pt"><br>
|
||||
</td>
|
||||
<td style="border:none;padding:.75pt .75pt .75pt
|
||||
.75pt"><br>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="MsoNormal" style="text-align:center;background:white" align="center"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222">Amount
|
||||
|
||||
due : 420.00 € </span></p>
|
||||
<p style="background:white"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222">Would
|
||||
|
||||
your payment have been carried out after this mail was
|
||||
sent, please ignore this message. Do not hesitate to
|
||||
contact our accounting department. </span></p>
|
||||
<p class="MsoNormal" style="background:white"><span style="font-size:9.0pt;font-family:"Arial","sans-serif";color:#222222"><br>
|
||||
Best Regards, <br>
|
||||
Aurore Lesage <br>
|
||||
OpenERP<br>
|
||||
Chaussée de Namur, 40 <br>
|
||||
B-1367 Grand Rosières <br>
|
||||
Tel: +32.81.81.37.00 - Fax: +32.81.73.35.01 <br>
|
||||
E-mail : <a href="mailto:ale@openerp.com">ale@openerp.com</a> <br>
|
||||
Web: <a href="http://www.openerp.com">http://www.openerp.com</a></span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
--<br>
|
||||
INZO services <small>Sent by <a style="color:inherit" href="http://www.openerp.com">OpenERP
|
||||
S.A.</a> using <a style="color:inherit" href="https://www.openerp.com/">OpenERP</a>.</small>
|
||||
<small>Access your messages and documents <a style="color:inherit" href="https://accounts.openerp.com?db=openerp#action=mail.action_mail_redirect&login=che&message_id=5750830">in
|
||||
|
||||
OpenERP</a></small> <br>
|
||||
<pre class="moz-signature" cols="72">--
|
||||
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: <a class="moz-txt-link-freetext" href="http://www.openerp.com">http://www.openerp.com</a> </pre>
|
||||
<br>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
</div>"""
|
||||
|
||||
BUG_2_IN = [
|
||||
'read more',
|
||||
'...',
|
||||
]
|
||||
BUG_2_OUT = [
|
||||
'Fwd: TR: OpenERP S.A'
|
||||
'fait un mois'
|
||||
]
|
||||
|
|
|
@ -175,6 +175,32 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
|
|||
iteration += 1
|
||||
new_node = _insert_new_node(node, -1, new_node_tag, text[idx:] + (cur_node.tail or ''), None, {})
|
||||
|
||||
def _truncate_node(node, position, find_first_blank=True):
|
||||
if node.text is None:
|
||||
node.text = ''
|
||||
# truncate text
|
||||
end_position = position if len(node.text) >= position else len(node.text)
|
||||
innertext = node.text[0:end_position]
|
||||
outertext = node.text[end_position:]
|
||||
if find_first_blank:
|
||||
stop_idx = outertext.find(' ')
|
||||
if stop_idx == -1:
|
||||
stop_idx = len(outertext)
|
||||
else:
|
||||
stop_idx = 0
|
||||
node.text = innertext + outertext[0:stop_idx]
|
||||
# create <span> ... <a href="#">read more</a></span> node
|
||||
read_more_node = _create_node('span', ' ... ', None, {'class': 'oe_mail_expand'})
|
||||
read_more_link_node = _create_node('a', 'read more', None, {'href': '#', 'class': 'oe_mail_expand'})
|
||||
read_more_node.append(read_more_link_node)
|
||||
# create outertext node
|
||||
overtext_node = _create_node('span', outertext[stop_idx:])
|
||||
# tag node
|
||||
overtext_node.set('in_overlength', '1')
|
||||
# add newly created nodes in dom
|
||||
node.append(read_more_node)
|
||||
node.append(overtext_node)
|
||||
|
||||
if not html or not isinstance(html, basestring):
|
||||
return html
|
||||
html = ustr(html)
|
||||
|
@ -206,7 +232,7 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
|
|||
|
||||
# form node and tag text-based quotes and signature
|
||||
quote_tags = re.compile(r'(\n(>)+[^\n\r]*)')
|
||||
signature = re.compile(r'([-]{2,}[\s]?[\r\n]{1,2}[^.]+)')
|
||||
signature = re.compile(r'([-]{2,}[\s]?[\r\n]{1,2}[\s\S]+)')
|
||||
for node in root.getiterator():
|
||||
_tag_matching_regex_in_text(quote_tags, node, 'span', {'text_quote': '1'})
|
||||
_tag_matching_regex_in_text(signature, node, 'span', {'text_signature': '1'})
|
||||
|
@ -226,14 +252,16 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
|
|||
if 'SkyDrivePlaceholder' in node.get('class', '') or 'SkyDrivePlaceholder' in node.get('id', ''):
|
||||
root.set('hotmail', '1')
|
||||
|
||||
# state of the parsing
|
||||
# state of the parsing: flag quotes and tails to remove
|
||||
if quote_begin:
|
||||
node.set('in_quote', '1')
|
||||
node.set('tail_remove', '1')
|
||||
# state of the parsing: flag when being in over-length content
|
||||
if overlength:
|
||||
node.set('in_overlength', '1')
|
||||
node.set('tail_remove', '1')
|
||||
|
||||
# find quote in msoffice / hotmail / blockquote / text quote and signatures
|
||||
if root.get('msoffice') and node.tag == 'div' and 'border-top:solid' in node.get('style', ''):
|
||||
quote_begin = True
|
||||
node.set('in_quote', '1')
|
||||
|
@ -242,36 +270,28 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300):
|
|||
quote_begin = True
|
||||
node.set('in_quote', '1')
|
||||
node.set('tail_remove', '1')
|
||||
if node.tag == 'blockquote' or node.get('text_quote') or node.get('text_signature'):
|
||||
node.set('in_quote', '1')
|
||||
|
||||
# shorten:
|
||||
# 1/ truncate the text at the next available space
|
||||
# 2/ create a 'read more' node, next to current node
|
||||
# 3/ add the truncated text in a new node, next to 'read more' node
|
||||
if shorten and not overlength and cur_char_nbr + len(node.text or '') > max_length:
|
||||
node_to_truncate = node
|
||||
while node_to_truncate.get('in_quote') and node_to_truncate.getparent() is not None:
|
||||
node_to_truncate = node_to_truncate.getparent()
|
||||
overlength = True
|
||||
# truncate text
|
||||
innertext = node.text[0:(max_length - cur_char_nbr)]
|
||||
outertext = node.text[(max_length - cur_char_nbr):]
|
||||
stop_idx = outertext.find(' ')
|
||||
if stop_idx == -1:
|
||||
stop_idx = len(outertext)
|
||||
node.text = innertext + outertext[0:stop_idx]
|
||||
# create <span> ... <a href="#">read more</a></span> node
|
||||
read_more_node = _create_node('span', ' ... ', None, {'class': 'oe_mail_expand'})
|
||||
read_more_link_node = _create_node('a', 'read more', None, {'href': '#', 'class': 'oe_mail_expand'})
|
||||
read_more_node.append(read_more_link_node)
|
||||
# create outertext node
|
||||
new_node = _create_node('span', outertext[stop_idx:])
|
||||
# add newly created nodes in dom
|
||||
node.addnext(new_node)
|
||||
node.addnext(read_more_node)
|
||||
# tag node
|
||||
new_node.set('in_overlength', '1')
|
||||
node_to_truncate.set('truncate', '1')
|
||||
node_to_truncate.set('truncate_position', str(max_length - cur_char_nbr))
|
||||
cur_char_nbr += len(node.text or '')
|
||||
|
||||
cur_char_nbr += len(node.text or '')
|
||||
# Tree modification
|
||||
# ------------------------------------------------------------
|
||||
|
||||
if node.tag == 'blockquote' or node.get('text_quote') or node.get('text_signature'):
|
||||
node.set('in_quote', '1')
|
||||
for node in root.iter():
|
||||
if node.get('truncate'):
|
||||
_truncate_node(node, int(node.get('truncate_position', '0')))
|
||||
|
||||
# Post processing
|
||||
# ------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue