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:
parent
fb9c56e91b
commit
3cd6f17146
10
__init__.py
10
__init__.py
|
@ -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,
|
||||
|
|
16
engine.py
16
engine.py
|
@ -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
|
||||
|
|
58
generator.py
58
generator.py
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
||||
|
|
10
message.xml
10
message.xml
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ extras_depend:
|
|||
stock_valued
|
||||
production
|
||||
xml:
|
||||
html.xml
|
||||
template.xml
|
||||
action.xml
|
||||
message.xml
|
||||
templates/base.xml
|
||||
|
|
Loading…
Reference in New Issue