diff --git a/openerp/addons/base/ir/ir_translation_view.xml b/openerp/addons/base/ir/ir_translation_view.xml index 64a02576091..f3f9c4d0fac 100644 --- a/openerp/addons/base/ir/ir_translation_view.xml +++ b/openerp/addons/base/ir/ir_translation_view.xml @@ -34,6 +34,7 @@ + diff --git a/openerp/addons/base/module/wizard/base_module_import.py b/openerp/addons/base/module/wizard/base_module_import.py index 61694d5f8c3..6d6f0e1ef8d 100644 --- a/openerp/addons/base/module/wizard/base_module_import.py +++ b/openerp/addons/base/module/wizard/base_module_import.py @@ -47,33 +47,8 @@ class base_module_import(osv.osv_memory): } def importzip(self, cr, uid, ids, context): - (data,) = self.browse(cr, uid, ids , context=context) - module_data = data.module_file - zip_data = base64.decodestring(module_data) - fp = StringIO() - fp.write(zip_data) - try: - file_data = zipfile.ZipFile(fp, 'r') - except zipfile.BadZipfile: - raise osv.except_osv(_('Error!'), _('File is not a zip file!')) - init_file_name = sorted(file_data.namelist())[0] - module_name = os.path.split(init_file_name)[0] - - file_path = os.path.join(ADDONS_PATH, '%s.zip' % module_name) - try: - zip_file = open(file_path, 'wb') - except IOError: - raise osv.except_osv(_('Error!'), - _('Can not create the module file: %s!') % \ - (file_path,) ) - zip_file.write(zip_data) - zip_file.close() - - self.pool.get('ir.module.module').update_list(cr, uid, - {'module_name': module_name,}) - self.write(cr, uid, ids, {'state':'done', 'module_name': module_name}, - context) - return False + #TODO: drop this model and the corresponding view/action in trunk + raise NotImplementedError('This feature is not available') def action_module_open(self, cr, uid, ids, context): (data,) = self.browse(cr, uid, ids , context=context) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index e865287a8ea..6622128f234 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -116,8 +116,7 @@ class _column(object): self.groups = False # CSV list of ext IDs of groups that can access this field self.deprecated = False # Optional deprecation warning for a in args: - if args[a]: - setattr(self, a, args[a]) + setattr(self, a, args[a]) def restart(self): pass diff --git a/openerp/tests/test_mail.py b/openerp/tests/test_mail.py index c8bbc0c862b..df1b5e15a11 100755 --- a/openerp/tests/test_mail.py +++ b/openerp/tests/test_mail.py @@ -149,6 +149,59 @@ class TestCleaner(unittest2.TestCase): for text in out_lst: self.assertNotIn(text, new_html, 'html_email_cleaner did not remove unwanted content') + def test_05_shorten(self): + # TEST: shorten length + test_str = '''
+ + +

Hello, Raoul + You are + pretty

+Really +
+''' + # shorten at 'H' of Hello -> should shorten after Hello, + html = html_email_clean(test_str, shorten=True, max_length=1, remove=True) + self.assertIn('Hello,', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('Raoul', html, 'html_email_cleaner: shorten error or too long') + self.assertIn('read more', html, 'html_email_cleaner: shorten error about read more inclusion') + # shorten at 'are' -> should shorten after are + html = html_email_clean(test_str, shorten=True, max_length=17, remove=True) + self.assertIn('Hello,', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('Raoul', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('are', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('pretty', html, 'html_email_cleaner: shorten error or too long') + self.assertNotIn('Really', html, 'html_email_cleaner: shorten error or too long') + self.assertIn('read more', html, 'html_email_cleaner: shorten error about read more inclusion') + + # TEST: shorten in quote + test_str = '''
Blahble + bluih blouh +
This is a quote + And this is quite a long quote, after all. +
+
''' + # shorten in the quote + html = html_email_clean(test_str, shorten=True, max_length=25, remove=True) + self.assertIn('Blahble', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('bluih', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('blouh', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('quote', html, 'html_email_cleaner: shorten error or too long') + self.assertIn('read more', html, 'html_email_cleaner: shorten error about read more inclusion') + # shorten in second word + html = html_email_clean(test_str, shorten=True, max_length=9, remove=True) + self.assertIn('Blahble', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('bluih', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('blouh', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('quote', html, 'html_email_cleaner: shorten error or too long') + self.assertIn('read more', html, 'html_email_cleaner: shorten error about read more inclusion') + # shorten waaay too large + html = html_email_clean(test_str, shorten=True, max_length=900, remove=True) + self.assertIn('Blahble', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('bluih', html, 'html_email_cleaner: shorten error or too short') + self.assertIn('blouh', html, 'html_email_cleaner: shorten error or too short') + self.assertNotIn('quote', html, 'html_email_cleaner: shorten error or too long') + def test_10_email_text(self): """ html_email_clean test for text-based emails """ new_html = html_email_clean(test_mail_examples.TEXT_1, remove=True) @@ -230,7 +283,7 @@ class TestCleaner(unittest2.TestCase): 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) + new_html = html_email_clean(test_mail_examples.BUG2, remove=True, shorten=True, max_length=250) 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: diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py index b287ded9d6d..ff73a3e0c72 100644 --- a/openerp/tools/mail.py +++ b/openerp/tools/mail.py @@ -175,26 +175,45 @@ 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): + def _truncate_node(node, position, simplify_whitespaces=True): + """ Truncate a node text at a given position. This algorithm will shorten + at the end of the word whose ending character exceeds position. + + :param bool simplify_whitespaces: whether to try to count all successive + whitespaces as one character. This + option should not be True when trying + to keep 'pre' consistency. + """ 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) + + truncate_idx = -1 + if simplify_whitespaces: + cur_char_nbr = 0 + word = None + node_words = node.text.strip(' \t\r\n').split() + for word in node_words: + cur_char_nbr += len(word) + if cur_char_nbr >= position: + break + if word: + truncate_idx = node.text.find(word) + len(word) else: - stop_idx = 0 - node.text = innertext + outertext[0:stop_idx] + truncate_idx = position + if truncate_idx == -1 or truncate_idx > len(node.text): + truncate_idx = len(node.text) + + # compose new text bits + innertext = node.text[0:truncate_idx] + outertext = node.text[truncate_idx:] + node.text = innertext + # create ... read more 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:]) + overtext_node = _create_node('span', outertext) # tag node overtext_node.set('in_overlength', '1') # add newly created nodes in dom @@ -223,17 +242,16 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300): html = '
%s
' % html root = lxml.html.fromstring(html) - # remove all tails and replace them by a span element, because managing text and tails can be a pain in the ass - for node in root.getiterator(): + quote_tags = re.compile(r'(\n(>)+[^\n\r]*)') + signature = re.compile(r'([-]{2,}[\s]?[\r\n]{1,2}[\s\S]+)') + for node in root.iter(): + # remove all tails and replace them by a span element, because managing text and tails can be a pain in the ass if node.tail: tail_node = _create_node('span', node.tail) node.tail = None node.addnext(tail_node) - # 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}[\s\S]+)') - for node in root.getiterator(): + # form node and tag text-based quotes and signature _tag_matching_regex_in_text(quote_tags, node, 'span', {'text_quote': '1'}) _tag_matching_regex_in_text(signature, node, 'span', {'text_signature': '1'}) @@ -245,7 +263,10 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300): quote_begin = False overlength = False cur_char_nbr = 0 - for node in root.getiterator(): + for node in root.iter(): + # do not take into account multiple spaces that are displayed as max 1 space in html + node_text = ' '.join((node.text and node.text.strip(' \t\r\n') or '').split()) + # root: try to tag the client used to write the html if 'WordSection1' in node.get('class', '') or 'MsoNormal' in node.get('class', ''): root.set('msoffice', '1') @@ -277,27 +298,30 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300): # 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: + if shorten and not overlength and cur_char_nbr + len(node_text) > 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 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 '') + if node_to_truncate == node: + node_to_truncate.set('truncate_position', str(max_length - cur_char_nbr)) + else: + node_to_truncate.set('truncate_position', str(len(node.text or ''))) + cur_char_nbr += len(node_text) # Tree modification # ------------------------------------------------------------ for node in root.iter(): if node.get('truncate'): - _truncate_node(node, int(node.get('truncate_position', '0'))) + _truncate_node(node, int(node.get('truncate_position', '0')), node.tag != 'pre') # Post processing # ------------------------------------------------------------ to_remove = [] - for node in root.getiterator(): + for node in root.iter(): if node.get('in_quote') or node.get('in_overlength'): # copy the node tail into parent text if node.tail and not node.get('tail_remove'): @@ -306,17 +330,20 @@ def html_email_clean(html, remove=False, shorten=False, max_length=300): to_remove.append(node) if node.get('tail_remove'): node.tail = '' + # clean node + for attribute_name in ['in_quote', 'tail_remove', 'in_overlength', 'msoffice', 'hotmail', 'truncate', 'truncate_position']: + node.attrib.pop(attribute_name, None) for node in to_remove: if remove: node.getparent().remove(node) else: if not 'oe_mail_expand' in node.get('class', ''): # trick: read more link should be displayed even if it's in overlength - node_class = node.get('class', '') + ' ' + 'oe_mail_cleaned' + node_class = node.get('class', '') + ' oe_mail_cleaned' node.set('class', node_class) # html: \n that were tail of elements have been encapsulated into -> back to \n html = etree.tostring(root, pretty_print=False) - linebreaks = re.compile(r'([\s]*[\r\n]+[\s]*)<\/span>', re.IGNORECASE | re.DOTALL) + linebreaks = re.compile(r']*>([\s]*[\r\n]+[\s]*)<\/span>', re.IGNORECASE | re.DOTALL) html = _replace_matching_regex(linebreaks, html, '\n') return html