[IMP][FIX]DOCX Report Generation : enhancements
* Fixes for assets ; * Handle HTML fields with basic rendering ; * Add Jinja filters ; * Add JSON Loads for TPL usage (context).
This commit is contained in:
parent
324810c3ff
commit
58cb82cdfa
@ -11,20 +11,20 @@
|
|||||||
|
|
||||||
This is the beta version, bugs may be present.
|
This is the beta version, bugs may be present.
|
||||||
""",
|
""",
|
||||||
"author": "RYDLAB",
|
"author": "RYDLAB, Yaltik",
|
||||||
"website": "https://rydlab.ru",
|
"website": "https://rydlab.ru",
|
||||||
"category": "Technical",
|
"category": "Technical",
|
||||||
"version": "16.0.2.0.0",
|
"version": "16.0.2.1.0",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"depends": ["base", "web", "custom_report_field", "report_monetary_helpers"],
|
"depends": ["base", "web", "custom_report_field", "report_monetary_helpers"],
|
||||||
"external_dependencies": {"python": ["docxcompose", "docxtpl", "bs4"]},
|
"external_dependencies": {"python": ["docxcompose", "docxtpl", "beautifulsoup4"]},
|
||||||
"data": [
|
"data": [
|
||||||
"views/ir_actions_report_views.xml",
|
"views/ir_actions_report_views.xml",
|
||||||
],
|
],
|
||||||
"assets": {
|
"assets": {
|
||||||
"web.assets_backend": [
|
"web.assets_backend": [
|
||||||
"docx_report/static/src/css/mimetypes.css",
|
"docx_report_generation/static/src/css/mimetypes.css",
|
||||||
"docx_report/static/src/js/action_manager_report.js",
|
"docx_report_generation/static/src/js/action_manager_report.js",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"images": ["static/description/banner.jpg"],
|
"images": ["static/description/banner.jpg"],
|
||||||
|
@ -2,11 +2,20 @@ from base64 import b64decode
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from functools import partial
|
||||||
|
from re import findall
|
||||||
|
from json import loads
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from docx import Document
|
from docx import Document
|
||||||
|
from docx.oxml import OxmlElement
|
||||||
|
from docx.oxml.ns import qn
|
||||||
|
from docx.shared import RGBColor
|
||||||
from docxcompose.composer import Composer
|
from docxcompose.composer import Composer
|
||||||
from docxtpl import DocxTemplate
|
from docxtpl import DocxTemplate
|
||||||
|
import jinja2
|
||||||
from requests import codes as codes_request, post as post_request
|
from requests import codes as codes_request, post as post_request
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
@ -14,6 +23,7 @@ from odoo import _, api, fields, models
|
|||||||
from odoo.exceptions import AccessError, UserError
|
from odoo.exceptions import AccessError, UserError
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.tools.safe_eval import safe_eval, time
|
from odoo.tools.safe_eval import safe_eval, time
|
||||||
|
from odoo.tools.misc import format_date
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from odoo.addons.gotenberg.service.utils import (
|
from odoo.addons.gotenberg.service.utils import (
|
||||||
@ -419,24 +429,77 @@ class IrActionsReport(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
record_to_render = values["docs"]
|
record_to_render = values["docs"]
|
||||||
|
# No more HTML / Markup stripping
|
||||||
docs = {
|
docs = {
|
||||||
key: record_to_render[key]
|
key: record_to_render[key]
|
||||||
for key in record_to_render._fields.keys()
|
for key in record_to_render._fields.keys()
|
||||||
if not isinstance(record_to_render[key], fields.Markup)
|
# if not isinstance(record_to_render[key], fields.Markup)
|
||||||
}
|
}
|
||||||
docs.update(
|
|
||||||
{
|
|
||||||
key: self._parse_markup(record_to_render[key])
|
|
||||||
for key in record_to_render._fields.keys()
|
|
||||||
if isinstance(record_to_render[key], fields.Markup)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
values["docs"] = docs
|
values["docs"] = docs
|
||||||
|
|
||||||
|
def _html_generate(field, tpl):
|
||||||
|
|
||||||
|
def _create_list(paragraph, list_type):
|
||||||
|
p = paragraph._p #access to xml paragraph element
|
||||||
|
pPr = p.get_or_add_pPr() #access paragraph properties
|
||||||
|
numPr = OxmlElement('w:numPr') #create number properties element
|
||||||
|
numId = OxmlElement('w:numId') #create numId element - sets bullet type
|
||||||
|
numId.set(qn('w:val'), list_type) #set list type/indentation
|
||||||
|
numPr.append(numId) #add bullet type to number properties list
|
||||||
|
pPr.append(numPr) #add number properties to paragraph
|
||||||
|
|
||||||
|
if isinstance(field, fields.Markup):
|
||||||
|
html_field = str(field).replace('<br>', '').replace(' ', ' ')
|
||||||
|
xml_tree = etree.fromstring('<root>%s</root>' % html_field)
|
||||||
|
md = tpl.new_subdoc()
|
||||||
|
for child in xml_tree.iter():
|
||||||
|
if child.tag in ('p', 'strong', 'em', 's', 'u', 'ul',
|
||||||
|
'ol', 'li', 'font', 'a'):
|
||||||
|
if child.tag == 'p':
|
||||||
|
p = md.add_paragraph(child.text)
|
||||||
|
elif child.tag == 'a':
|
||||||
|
p.add_run(child.text).style = 'Hyperlink'
|
||||||
|
elif child.tag == 'strong':
|
||||||
|
p.add_run(child.text).bold = True
|
||||||
|
p.add_run(' ')
|
||||||
|
elif child.tag == 'em':
|
||||||
|
p.add_run(child.text).italic = True
|
||||||
|
p.add_run(' ')
|
||||||
|
elif child.tag == 'u':
|
||||||
|
p.add_run(child.text).underline = True
|
||||||
|
p.add_run(' ')
|
||||||
|
elif child.tag == 's':
|
||||||
|
p.add_run(child.text).font.strike = True
|
||||||
|
p.add_run(' ')
|
||||||
|
elif child.tag == 'font' and 'style' in child.attrib:
|
||||||
|
if 'color' in child.get('style'):
|
||||||
|
rgb = [int(_v)
|
||||||
|
for _v in findall(r'\d+', child.get('style'))]
|
||||||
|
if len(rgb) == 3:
|
||||||
|
p.add_run(child.text).font.color.rgb = RGBColor(*rgb)
|
||||||
|
else:
|
||||||
|
p.add_run(child.text)
|
||||||
|
elif child.tag == 'ul':
|
||||||
|
list_type = '9' # 9/25/28 : •, 8 : -, 19 : v
|
||||||
|
elif child.tag == 'ol':
|
||||||
|
list_type = '30' # 27 : 1.2 sans indent, 30: 1.2
|
||||||
|
elif child.tag == 'li':
|
||||||
|
lp = md.add_paragraph(child.text, style='List Paragraph')
|
||||||
|
_create_list(lp, list_type)
|
||||||
|
elif child.tag == 'root':
|
||||||
|
continue
|
||||||
|
else: # Not handled, add text only
|
||||||
|
p.add_run(child.text)
|
||||||
|
return md
|
||||||
|
|
||||||
|
jinja_env = jinja2.Environment()
|
||||||
|
|
||||||
docx_content = BytesIO()
|
docx_content = BytesIO()
|
||||||
with BytesIO(b64decode(template)) as template_file:
|
with BytesIO(b64decode(template)) as template_file:
|
||||||
doc = DocxTemplate(template_file)
|
doc = DocxTemplate(template_file)
|
||||||
doc.render(values)
|
jinja_env.filters['htmlgen'] = partial(_html_generate, tpl=doc)
|
||||||
|
jinja_env.filters['datefmt'] = lambda dt: format_date(self.env, dt)
|
||||||
|
doc.render(values, jinja_env)
|
||||||
doc.save(docx_content)
|
doc.save(docx_content)
|
||||||
docx_content.seek(0)
|
docx_content.seek(0)
|
||||||
return docx_content
|
return docx_content
|
||||||
@ -476,3 +539,11 @@ class IrActionsReport(models.Model):
|
|||||||
_logger.exception(e)
|
_logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_rendering_context(self, report, docids, data):
|
||||||
|
data = super()._get_rendering_context(report, docids, data)
|
||||||
|
data.update({
|
||||||
|
'jloads': loads,
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
Loading…
x
Reference in New Issue
Block a user