diff --git a/modules/marketing_automation/marketing_automation.py b/modules/marketing_automation/marketing_automation.py index 3dba6f594a..f66cef3fe6 100644 --- a/modules/marketing_automation/marketing_automation.py +++ b/modules/marketing_automation/marketing_automation.py @@ -11,12 +11,6 @@ from urllib.parse import ( parse_qsl, quote, urlencode, urljoin, urlsplit, urlunsplit) from dateutil.relativedelta import relativedelta - -try: - import html2text -except ImportError: - html2text = None - from genshi.core import END, START, Attrs, QName from genshi.template import MarkupTemplate from genshi.template import TemplateError as GenshiTemplateError @@ -31,7 +25,7 @@ from trytond.model import ( fields) from trytond.pool import Pool from trytond.pyson import Eval, If, PYSONDecoder, TimeDelta -from trytond.report import Report +from trytond.report import Report, html_to_text from trytond.sendmail import SMTPDataManager, send_message_transactional from trytond.tools import grouped_slice, pairwise_longest, reduce_ids from trytond.tools.chart import sparkline @@ -750,9 +744,7 @@ class Activity( set_from_header(msg, from_, sender or from_) msg['To'] = to msg['Subject'] = subject - if html2text: - converter = html2text.HTML2Text() - content_text = converter.handle(content) + if content_text := html_to_text(content): msg.add_alternative(content_text, subtype='plain') if msg.is_multipart(): msg.add_alternative(content, subtype='html') diff --git a/modules/marketing_email/marketing.py b/modules/marketing_email/marketing.py index 92f73a2020..28cc93086c 100644 --- a/modules/marketing_email/marketing.py +++ b/modules/marketing_email/marketing.py @@ -14,11 +14,6 @@ from genshi.template import TemplateError as GenshiTemplateError from sql import Literal from sql.aggregate import Count -try: - import html2text -except ImportError: - html2text = None - import trytond.config as config from trytond.i18n import gettext from trytond.ir.session import token_hex @@ -26,7 +21,7 @@ from trytond.model import ( DeactivableMixin, Index, ModelSQL, ModelView, Unique, Workflow, fields) from trytond.pool import Pool from trytond.pyson import Eval -from trytond.report import Report, get_email +from trytond.report import Report, get_email, html_to_text from trytond.sendmail import SMTPDataManager, send_message_transactional from trytond.tools import grouped_slice, reduce_ids from trytond.tools.email_ import ( @@ -506,9 +501,7 @@ class Message(Workflow, ModelSQL, ModelView): msg['List-Unsubscribe'] = ( f'<{email.get_email_unsubscribe_url()}>') msg['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click' - if html2text: - converter = html2text.HTML2Text() - content_text = converter.handle(content) + if content_text := html_to_text(content): msg.add_alternative(content_text, subtype='plain') if msg.is_multipart(): msg.add_alternative(content, subtype='html') diff --git a/trytond/doc/ref/report.rst b/trytond/doc/ref/report.rst index a223763cf3..c6fa28605f 100644 --- a/trytond/doc/ref/report.rst +++ b/trytond/doc/ref/report.rst @@ -147,3 +147,10 @@ Email reports must have ``HTML`` as output format. .. warning:: It may return the content unconverted if some dependencies are missing. + +.. function:: html_to_text(content): + + Converts ``HTML`` content to plain text. + + .. warning:: + It may return ``None`` if some dependencies are missing. diff --git a/trytond/trytond/ir/email_.py b/trytond/trytond/ir/email_.py index 607d0be4e3..b792431475 100644 --- a/trytond/trytond/ir/email_.py +++ b/trytond/trytond/ir/email_.py @@ -6,10 +6,6 @@ import re from email.message import EmailMessage from email.utils import getaddresses -try: - import html2text -except ImportError: - html2text = None from genshi.template import TextTemplate import trytond.config as config @@ -18,7 +14,7 @@ from trytond.model import EvalEnvironment, ModelSQL, ModelView, fields from trytond.model.exceptions import AccessError, ValidationError from trytond.pool import Pool from trytond.pyson import Bool, Eval, PYSONDecoder -from trytond.report import Report +from trytond.report import Report, html_to_text from trytond.rpc import RPC from trytond.sendmail import send_message_transactional from trytond.tools import escape_wildcard, slugify @@ -107,16 +103,14 @@ class Email(ResourceAccessMixin, ModelSQL, ModelView): 'body': body, 'signature': user.signature or '', } - if html2text: - body_text = HTML_EMAIL % { - 'subject': subject, - 'body': body, - 'signature': '', - } - converter = html2text.HTML2Text() - body_text = converter.handle(body_text) + body_text = HTML_EMAIL % { + 'subject': subject, + 'body': body, + 'signature': '', + } + if body_text := html_to_text(body_text): if user.signature: - body_text += '\n-- \n' + converter.handle(user.signature) + body_text += '\n-- \n' + html_to_text(user.signature) msg.add_alternative(body_text, subtype='plain') if msg.is_multipart(): msg.add_alternative(body_html, subtype='html') diff --git a/trytond/trytond/report/__init__.py b/trytond/trytond/report/__init__.py index b29485fb47..6a258fc02e 100644 --- a/trytond/trytond/report/__init__.py +++ b/trytond/trytond/report/__init__.py @@ -1,5 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from .report import Report, get_email, mjml_to_html +from .report import Report, get_email, html_to_text, mjml_to_html -__all__ = [Report, get_email, mjml_to_html] +__all__ = [Report, get_email, html_to_text, mjml_to_html] diff --git a/trytond/trytond/report/report.py b/trytond/trytond/report/report.py index a713931f88..e2af1c3a6a 100644 --- a/trytond/trytond/report/report.py +++ b/trytond/trytond/report/report.py @@ -21,17 +21,6 @@ from io import BytesIO from itertools import groupby import dateutil.tz - -try: - import html2text -except ImportError: - html2text = None - -try: - import weasyprint -except ImportError: - weasyprint = None - from genshi.filters import Translator from genshi.template.text import TextTemplate @@ -422,10 +411,10 @@ class Report(URLMixin, PoolBase): input_format = report.template_extension output_format = report.extension or report.template_extension - if (weasyprint - and input_format in {'html', 'xhtml'} + if (input_format in {'html', 'xhtml'} and output_format == 'pdf'): - return output_format, weasyprint.HTML(string=data).write_pdf() + if weasyprint := _lazy_import('weasyprint'): + return output_format, weasyprint.HTML(string=data).write_pdf() if (input_format == 'xml' and output_format == 'html' @@ -612,7 +601,6 @@ def get_email(report, record, languages): else: report_name = report Report_ = pool.get(report_name, type='report') - converter = None title = None msg = EmailMessage() header_factory = msg.policy.header_factory @@ -631,13 +619,11 @@ def get_email(report, record, languages): break else: maintype, subtype = 'application', ext - if maintype == 'text' and subtype == 'html' and html2text: - if not converter: - converter = html2text.HTML2Text() - content_text = converter.handle(content) - msg.add_alternative(content_text, subtype='plain', headers=[ - header_factory('Content-Language', language.code), - ]) + if maintype == 'text' and subtype == 'html': + if content_text := html_to_text(content): + msg.add_alternative(content_text, subtype='plain', headers=[ + header_factory('Content-Language', language.code), + ]) types = { 'subtype': subtype, } @@ -663,3 +649,8 @@ def mjml_to_html(content): if mrml := _lazy_import('mrml'): content = mrml.to_html(content).content return content + + +def html_to_text(content): + if html2text := _lazy_import('html2text'): + return html2text.HTML2Text().handle(content) diff --git a/trytond/trytond/tests/test_report.py b/trytond/trytond/tests/test_report.py index 2bb7ace205..74b4a4700a 100644 --- a/trytond/trytond/tests/test_report.py +++ b/trytond/trytond/tests/test_report.py @@ -136,9 +136,9 @@ class ReportTestCase(TestCase): 'Test') @with_transaction() - @patch('trytond.report.report.html2text') - def test_get_email_html_without_html2text(self, html2text): - html2text.__bool__.side_effect = lambda: False + @patch('trytond.report.report.html_to_text') + def test_get_email_html_without_html2text(self, html_to_text): + html_to_text.side_effect = lambda content: None class FakeReport(Report): @classmethod