Execute reports by multiprocessing queue (timeout) (#19)

* Execute reports by multiprocessing queue (timeout)
#158319

* Raise UserError when queue was empty

* Use subprocess and script to create report pdf | #158319

---------

Co-authored-by: Jared Esparza <jared.esparza@nan-tic.com>
This commit is contained in:
Raimon Esteve 2023-12-11 12:26:24 +01:00 committed by Jared Esparza
parent fb9c56e91b
commit 3cd6f17146
13 changed files with 134 additions and 16 deletions

View File

@ -5,7 +5,7 @@ from trytond.pool import Pool
from trytond.report import Report
from . import action
from . import translation
from . import html
from . import template
from . import engine
from . import product
from . import invoice
@ -20,10 +20,10 @@ def register():
Pool.register(
action.ActionReport,
action.HTMLTemplateTranslation,
html.Signature,
html.Template,
html.TemplateUsage,
html.ReportTemplate,
template.Signature,
template.Template,
template.TemplateUsage,
template.ReportTemplate,
module=module, type_='model')
Pool.register(
translation.ReportTranslationSet,

View File

@ -424,9 +424,10 @@ class HTMLReportMixin:
return pdf_data
@classmethod
def execute(cls, ids, data):
def __execute(cls, ids, data, queue=None):
cls.check_access()
action, model = cls.get_action(data)
# in case is not jinja, call super()
if action.template_extension != 'jinja':
return super().execute(ids, data)
@ -485,7 +486,6 @@ class HTMLReportMixin:
if action.html_copies and action.html_copies > 1:
content = cls.merge_pdfs([content] * action.html_copies)
Printer = None
try:
Printer = Pool().get('printer')
@ -494,7 +494,15 @@ class HTMLReportMixin:
if Printer:
return Printer.send_report(oext, content,
action_name, action)
return oext, content, cls.get_direct_print(action), filename
return oext, content, cls.get_direct_print(action), filename
@classmethod
def execute(cls, ids, data):
cls.check_access()
action, model = cls.get_action(data)
if action.template_extension != 'jinja':
return super().execute(ids, data)
return cls.__execute(ids, data, queue=None)
@classmethod
def _execute_html_report(cls, records, data, action, side_margin=2,
@ -551,7 +559,7 @@ class HTMLReportMixin:
last_footer_html=last_footer,
side_margin=side_margin,
extra_vertical_margin=extra_vertical_margin
).render_html().write_pdf()
).render_pdf()
else:
document = content
return extension, document

View File

@ -1,5 +1,11 @@
from weasyprint import HTML, CSS
from trytond.i18n import gettext
from trytond.exceptions import UserError
from trytond.transaction import Transaction
import os
import json
import tempfile
import subprocess
class PdfGenerator:
"""
@ -182,6 +188,35 @@ class PdfGenerator:
return main_doc
def render_pdf(self):
context = Transaction().context
timeout_report = context.get('timeout_report', None)
if timeout_report:
path = os.path.dirname(os.path.abspath(__file__)) + '/'
json_path = self.to_json_file()
process = subprocess.Popen(['python3', path+'generator_script.py', json_path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
encoding='utf-8', errors='ignore')
out = None
try:
out, err = process.communicate(timeout=timeout_report)
except subprocess.TimeoutExpired:
process.kill()
out, err = process.communicate()
raise UserError(gettext('html_report.msg_error_timeout', seconds=timeout_report))
finally:
os.remove(json_path)
document = None
if out and os.path.exists(out.strip()):
with open(out.strip(), 'rb') as file:
document = file.read()
os.remove(out.strip())
else:
document = self.render_html().write_pdf()
return document
@staticmethod
def get_element(boxes, element):
"""
@ -196,3 +231,24 @@ class PdfGenerator:
box_children = PdfGenerator.get_element(box.all_children(), element)
if box_children:
return box_children
def to_json_file(self):
"""
Write the PdfGenerator properties to a JSON file.
Parameters:
- filepath: The path to the JSON file.
"""
data = {
"main_html": self.main_html,
"header_html": self.header_html,
"footer_html": self.footer_html,
"last_footer_html": self.last_footer_html,
"base_url": self.base_url,
"side_margin": self.side_margin,
"extra_vertical_margin": self.extra_vertical_margin
}
with tempfile.NamedTemporaryFile(mode='w', delete=False) as file:
filepath = file.name
json.dump(data, file)
return filepath

44
generator_script.py Normal file
View File

@ -0,0 +1,44 @@
import sys
import json
import tempfile
from generator import PdfGenerator
def from_json_file(filepath):
"""
Read the generated JSON file and instantiate a PdfGenerator with the properties.
Parameters:
- filepath: The path to the JSON file.
Returns:
- An instance of PdfGenerator with the properties from the JSON file.
"""
with open(filepath, "r") as file:
data = json.load(file)
reuslt = PdfGenerator(
main_html=data["main_html"],
header_html=data["header_html"],
footer_html=data["footer_html"],
last_footer_html=data["last_footer_html"],
base_url=data["base_url"],
side_margin=data["side_margin"],
extra_vertical_margin=data["extra_vertical_margin"]
)
return reuslt
def main(argv):
json_filepath = argv[1]
pdf_generator = from_json_file(json_filepath)
result = pdf_generator.render_html().write_pdf()
with tempfile.NamedTemporaryFile(delete=False) as temp:
temp.write(result)
return temp.name
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Debe proporcionar la ruta del archivo JSON como argumento.")
sys.exit(1)
print(main(sys.argv))
sys.exit(0)

View File

@ -1,7 +1,7 @@
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.modules.html_report.html import HTMLPartyInfoMixin
from trytond.modules.html_report.template import HTMLPartyInfoMixin
from trytond.modules.html_report.engine import HTMLReportMixin

View File

@ -22,5 +22,15 @@
<record model="ir.message" id="nullslat_incorrect_format">
<field name="text">The nullslast filter only accept a list of tuples.</field>
</record>
<record model="ir.message" id="msg_error_timeout">
<field name="text">The process was interrupted by %(seconds)s seconds timeout.</field>
</record>
<record model="ir.message" id="msg_queue_empty">
<field name="text">The queue (timeout) was empty and can not render the report.</field>
</record>
<record model="ir.message" id="msg_exception_timeout">
<field name="text">There was an exception rendering the report:
%(result)s</field>
</record>
</data>
</tryton>

View File

@ -1,7 +1,7 @@
from trytond.model import fields
from trytond.pool import PoolMeta
from trytond.pyson import Eval
from trytond.modules.html_report.html import HTMLPartyInfoMixin
from trytond.modules.html_report.template import HTMLPartyInfoMixin
class Production(HTMLPartyInfoMixin, metaclass=PoolMeta):

View File

@ -1,6 +1,6 @@
from trytond.pool import PoolMeta
from trytond.pyson import Eval
from trytond.modules.html_report.html import HTMLPartyInfoMixin
from trytond.modules.html_report.template import HTMLPartyInfoMixin
class Purchase(HTMLPartyInfoMixin, metaclass=PoolMeta):

View File

@ -1,6 +1,6 @@
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.modules.html_report.html import HTMLPartyInfoMixin
from trytond.modules.html_report.template import HTMLPartyInfoMixin
from trytond.modules.html_report.engine import HTMLReportMixin

View File

@ -1,7 +1,7 @@
from trytond.model import fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.modules.html_report.html import HTMLPartyInfoMixin
from trytond.modules.html_report.template import HTMLPartyInfoMixin
from trytond.modules.html_report.engine import HTMLReportMixin

View File

@ -18,7 +18,7 @@ extras_depend:
stock_valued
production
xml:
html.xml
template.xml
action.xml
message.xml
templates/base.xml