[IMP] report_py3o, report_py3o_fusion_server: black, isort

pull/479/head
Laurent Mignon (ACSONE) 2019-11-19 14:36:37 +01:00 committed by Alexis de Lattre
parent 19961919e6
commit b2735878e4
6 changed files with 397 additions and 326 deletions

View File

@ -1,31 +1,21 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Py3o Report Engine - Fusion server support', "name": "Py3o Report Engine - Fusion server support",
'summary': 'Let the fusion server handle format conversion.', "summary": "Let the fusion server handle format conversion.",
'version': '12.0.1.0.0', "version": "12.0.1.0.0",
'category': 'Reporting', "category": "Reporting",
'license': 'AGPL-3', "license": "AGPL-3",
'author': 'XCG Consulting,' "author": "XCG Consulting," "ACSONE SA/NV," "Odoo Community Association (OCA)",
'ACSONE SA/NV,' "website": "https://github.com/OCA/reporting-engine",
'Odoo Community Association (OCA)', "depends": ["report_py3o"],
'website': 'https://github.com/OCA/reporting-engine', "external_dependencies": {"python": ["py3o.template", "py3o.formats"]},
'depends': ['report_py3o'], "demo": ["demo/report_py3o.xml", "demo/py3o_pdf_options.xml"],
'external_dependencies': { "data": [
'python': [
'py3o.template',
'py3o.formats',
],
},
'demo': [
"demo/report_py3o.xml",
"demo/py3o_pdf_options.xml",
],
'data': [
"views/ir_actions_report.xml", "views/ir_actions_report.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
'views/py3o_server.xml', "views/py3o_server.xml",
'views/py3o_pdf_options.xml', "views/py3o_pdf_options.xml",
], ],
'installable': True, "installable": True,
} }

View File

@ -2,14 +2,16 @@
# © 2017 Therp BV <http://therp.nl> # © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging import logging
from openerp import _, api, fields, models from openerp import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class IrActionsReport(models.Model): class IrActionsReport(models.Model):
_inherit = 'ir.actions.report' _inherit = "ir.actions.report"
@api.multi @api.multi
@api.constrains("py3o_is_local_fusion", "py3o_server_id") @api.constrains("py3o_is_local_fusion", "py3o_server_id")
@ -17,40 +19,52 @@ class IrActionsReport(models.Model):
for report in self: for report in self:
if report.report_type != "py3o": if report.report_type != "py3o":
continue continue
if (not report.py3o_is_local_fusion and not report.py3o_server_id): if not report.py3o_is_local_fusion and not report.py3o_server_id:
raise ValidationError(_( raise ValidationError(
_(
"You can not use remote fusion without Fusion server. " "You can not use remote fusion without Fusion server. "
"Please specify a Fusion Server")) "Please specify a Fusion Server"
)
)
py3o_is_local_fusion = fields.Boolean( py3o_is_local_fusion = fields.Boolean(
"Local Fusion", "Local Fusion",
help="Native formats will be processed without a server. " help="Native formats will be processed without a server. "
"You must use this mode if you call methods on your model into " "You must use this mode if you call methods on your model into "
"the template.", "the template.",
default=True) default=True,
py3o_server_id = fields.Many2one( )
"py3o.server", py3o_server_id = fields.Many2one("py3o.server", "Fusion Server")
"Fusion Server")
pdf_options_id = fields.Many2one( pdf_options_id = fields.Many2one(
'py3o.pdf.options', string='PDF Options', ondelete='restrict', "py3o.pdf.options",
string="PDF Options",
ondelete="restrict",
help="PDF options can be set per report, but also per Py3o Server. " help="PDF options can be set per report, but also per Py3o Server. "
"If both are defined, the options on the report are used.") "If both are defined, the options on the report are used.",
)
@api.depends("lo_bin_path", "is_py3o_native_format", "report_type", @api.depends(
"py3o_server_id") "lo_bin_path", "is_py3o_native_format", "report_type", "py3o_server_id"
)
@api.multi @api.multi
def _compute_py3o_report_not_available(self): def _compute_py3o_report_not_available(self):
for rec in self: for rec in self:
if not rec.report_type == "py3o": if not rec.report_type == "py3o":
continue continue
if (not rec.is_py3o_native_format and if (
not rec.lo_bin_path and not rec.py3o_server_id): not rec.is_py3o_native_format
and not rec.lo_bin_path
and not rec.py3o_server_id
):
rec.is_py3o_report_not_available = True rec.is_py3o_report_not_available = True
rec.msg_py3o_report_not_available = _( rec.msg_py3o_report_not_available = (
_(
"A fusion server or a libreoffice runtime are required " "A fusion server or a libreoffice runtime are required "
"to genereate the py3o report '%s'. If the libreoffice" "to genereate the py3o report '%s'. If the libreoffice"
"runtime is already installed and is not found by " "runtime is already installed and is not found by "
"Odoo, you can provide the full path to the runtime by " "Odoo, you can provide the full path to the runtime by "
"setting the key 'py3o.conversion_command' into the " "setting the key 'py3o.conversion_command' into the "
"configuration parameters." "configuration parameters."
) % rec.name )
% rec.name
)

View File

@ -2,223 +2,283 @@
# @author: Alexis de Lattre <alexis.delattre@akretion.com> # @author: Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
import logging import logging
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Py3oPdfOptions(models.Model): class Py3oPdfOptions(models.Model):
_name = 'py3o.pdf.options' _name = "py3o.pdf.options"
_description = 'Define PDF export options for Libreoffice' _description = "Define PDF export options for Libreoffice"
name = fields.Char(required=True) name = fields.Char(required=True)
# GENERAL TAB # GENERAL TAB
# UseLosslessCompression (bool) # UseLosslessCompression (bool)
image_compression = fields.Selection([ image_compression = fields.Selection(
('lossless', 'Lossless Compression'), [("lossless", "Lossless Compression"), ("jpeg", "JPEG Compression")],
('jpeg', 'JPEG Compression'), string="Image Compression",
], string='Image Compression', default='jpeg') default="jpeg",
)
# Quality (int) # Quality (int)
image_jpeg_quality = fields.Integer( image_jpeg_quality = fields.Integer(
string='Image JPEG Quality', default=90, string="Image JPEG Quality",
help="Enter a percentage between 0 and 100.") default=90,
help="Enter a percentage between 0 and 100.",
)
# ReduceImageResolution (bool) and MaxImageResolution (int) # ReduceImageResolution (bool) and MaxImageResolution (int)
image_reduce_resolution = fields.Selection([ image_reduce_resolution = fields.Selection(
('none', 'Disable'), [
('75', '75 DPI'), ("none", "Disable"),
('150', '150 DPI'), ("75", "75 DPI"),
('300', '300 DPI'), ("150", "150 DPI"),
('600', '600 DPI'), ("300", "300 DPI"),
('1200', '1200 DPI'), ("600", "600 DPI"),
], string='Reduce Image Resolution', default='300') ("1200", "1200 DPI"),
watermark = fields.Boolean('Sign With Watermark') ],
string="Reduce Image Resolution",
default="300",
)
watermark = fields.Boolean("Sign With Watermark")
# Watermark (string) # Watermark (string)
watermark_text = fields.Char('WaterMark Text') watermark_text = fields.Char("WaterMark Text")
# UseTaggedPDF (bool) # UseTaggedPDF (bool)
tagged_pdf = fields.Boolean('Tagged PDF (add document structure)') tagged_pdf = fields.Boolean("Tagged PDF (add document structure)")
# SelectPdfVersion (int) # SelectPdfVersion (int)
# 0 = PDF 1.4 (default selection). # 0 = PDF 1.4 (default selection).
# 1 = PDF/A-1 (ISO 19005-1:2005) # 1 = PDF/A-1 (ISO 19005-1:2005)
pdfa = fields.Boolean( pdfa = fields.Boolean(
'Archive PDF/A-1a (ISO 19005-1)', "Archive PDF/A-1a (ISO 19005-1)",
help="If you enable this option, you will not be able to " help="If you enable this option, you will not be able to "
"password-protect the document or apply other security settings.") "password-protect the document or apply other security settings.",
)
# ExportFormFields (bool) # ExportFormFields (bool)
pdf_form = fields.Boolean('Create PDF Form', default=True) pdf_form = fields.Boolean("Create PDF Form", default=True)
# FormsType (int) # FormsType (int)
pdf_form_format = fields.Selection([ pdf_form_format = fields.Selection(
('0', 'FDF'), [("0", "FDF"), ("1", "PDF"), ("2", "HTML"), ("3", "XML")],
('1', 'PDF'), string="Submit Format",
('2', 'HTML'), default="0",
('3', 'XML'), )
], string='Submit Format', default='0')
# AllowDuplicateFieldNames (bool) # AllowDuplicateFieldNames (bool)
pdf_form_allow_duplicate = fields.Boolean('Allow Duplicate Field Names') pdf_form_allow_duplicate = fields.Boolean("Allow Duplicate Field Names")
# ExportBookmarks (bool) # ExportBookmarks (bool)
export_bookmarks = fields.Boolean('Export Bookmarks', default=True) export_bookmarks = fields.Boolean("Export Bookmarks", default=True)
# ExportPlaceholders (bool) # ExportPlaceholders (bool)
export_placeholders = fields.Boolean('Export Placeholders', default=True) export_placeholders = fields.Boolean("Export Placeholders", default=True)
# ExportNotes (bool) # ExportNotes (bool)
export_comments = fields.Boolean('Export Comments') export_comments = fields.Boolean("Export Comments")
# ExportHiddenSlides (bool) ?? # ExportHiddenSlides (bool) ??
export_hidden_slides = fields.Boolean( export_hidden_slides = fields.Boolean("Export Automatically Insered Blank Pages")
'Export Automatically Insered Blank Pages')
# Doesn't make sense to have the option "View PDF after export" ! :) # Doesn't make sense to have the option "View PDF after export" ! :)
# INITIAL VIEW TAB # INITIAL VIEW TAB
# InitialView (int) # InitialView (int)
initial_view = fields.Selection([ initial_view = fields.Selection(
('0', 'Page Only'), [("0", "Page Only"), ("1", "Bookmarks and Page"), ("2", "Thumbnails and Page")],
('1', 'Bookmarks and Page'), string="Panes",
('2', 'Thumbnails and Page'), default="0",
], string='Panes', default='0') )
# InitialPage (int) # InitialPage (int)
initial_page = fields.Integer(string='Initial Page', default=1) initial_page = fields.Integer(string="Initial Page", default=1)
# Magnification (int) # Magnification (int)
magnification = fields.Selection([ magnification = fields.Selection(
('0', 'Default'), [
('1', 'Fit in Window'), ("0", "Default"),
('2', 'Fit Width'), ("1", "Fit in Window"),
('3', 'Fit Visible'), ("2", "Fit Width"),
('4', 'Zoom'), ("3", "Fit Visible"),
], string='Magnification', default='0') ("4", "Zoom"),
],
string="Magnification",
default="0",
)
# Zoom (int) # Zoom (int)
zoom = fields.Integer( zoom = fields.Integer(
string='Zoom Factor', default=100, string="Zoom Factor", default=100, help="Possible values: from 50 to 1600"
help='Possible values: from 50 to 1600') )
# PageLayout (int) # PageLayout (int)
page_layout = fields.Selection([ page_layout = fields.Selection(
('0', 'Default'), [
('1', 'Single Page'), ("0", "Default"),
('2', 'Continuous'), ("1", "Single Page"),
('3', 'Continuous Facing'), ("2", "Continuous"),
], string='Page Layout', default='0') ("3", "Continuous Facing"),
],
string="Page Layout",
default="0",
)
# USER INTERFACE TAB # USER INTERFACE TAB
# ResizeWindowToInitialPage (bool) # ResizeWindowToInitialPage (bool)
resize_windows_initial_page = fields.Boolean( resize_windows_initial_page = fields.Boolean(
string='Resize Windows to Initial Page') string="Resize Windows to Initial Page"
)
# CenterWindow (bool) # CenterWindow (bool)
center_window = fields.Boolean(string='Center Window on Screen') center_window = fields.Boolean(string="Center Window on Screen")
# OpenInFullScreenMode (bool) # OpenInFullScreenMode (bool)
open_fullscreen = fields.Boolean(string='Open in Full Screen Mode') open_fullscreen = fields.Boolean(string="Open in Full Screen Mode")
# DisplayPDFDocumentTitle (bool) # DisplayPDFDocumentTitle (bool)
display_document_title = fields.Boolean(string='Display Document Title') display_document_title = fields.Boolean(string="Display Document Title")
# HideViewerMenubar (bool) # HideViewerMenubar (bool)
hide_menubar = fields.Boolean(string='Hide Menubar') hide_menubar = fields.Boolean(string="Hide Menubar")
# HideViewerToolbar (bool) # HideViewerToolbar (bool)
hide_toolbar = fields.Boolean(string='Hide Toolbar') hide_toolbar = fields.Boolean(string="Hide Toolbar")
# HideViewerWindowControls (bool) # HideViewerWindowControls (bool)
hide_window_controls = fields.Boolean(string='Hide Windows Controls') hide_window_controls = fields.Boolean(string="Hide Windows Controls")
# OpenBookmarkLevels (int) -1 = all (default) from 1 to 10 # OpenBookmarkLevels (int) -1 = all (default) from 1 to 10
open_bookmark_levels = fields.Selection([ open_bookmark_levels = fields.Selection(
('-1', 'All Levels'), [
('1', '1'), ("-1", "All Levels"),
('2', '2'), ("1", "1"),
('3', '3'), ("2", "2"),
('4', '4'), ("3", "3"),
('5', '5'), ("4", "4"),
('6', '6'), ("5", "5"),
('7', '7'), ("6", "6"),
('8', '8'), ("7", "7"),
('9', '9'), ("8", "8"),
('10', '10'), ("9", "9"),
], default='-1', string='Visible Bookmark Levels') ("10", "10"),
],
default="-1",
string="Visible Bookmark Levels",
)
# LINKS TAB # LINKS TAB
# ExportBookmarksToPDFDestination (bool) # ExportBookmarksToPDFDestination (bool)
export_bookmarks_named_dest = fields.Boolean( export_bookmarks_named_dest = fields.Boolean(
string='Export Bookmarks as Named Destinations') string="Export Bookmarks as Named Destinations"
)
# ConvertOOoTargetToPDFTarget (bool) # ConvertOOoTargetToPDFTarget (bool)
convert_doc_ref_to_pdf_target = fields.Boolean( convert_doc_ref_to_pdf_target = fields.Boolean(
string='Convert Document References to PDF Targets') string="Convert Document References to PDF Targets"
)
# ExportLinksRelativeFsys (bool) # ExportLinksRelativeFsys (bool)
export_filesystem_urls = fields.Boolean( export_filesystem_urls = fields.Boolean(string="Export URLs Relative to Filesystem")
string='Export URLs Relative to Filesystem')
# PDFViewSelection -> mnDefaultLinkAction (int) # PDFViewSelection -> mnDefaultLinkAction (int)
cross_doc_link_action = fields.Selection([ cross_doc_link_action = fields.Selection(
('0', 'Default'), [
('1', 'Open with PDF Reader Application'), ("0", "Default"),
('2', 'Open with Internet Browser'), ("1", "Open with PDF Reader Application"),
], string='Cross-document Links', default='0') ("2", "Open with Internet Browser"),
],
string="Cross-document Links",
default="0",
)
# SECURITY TAB # SECURITY TAB
# EncryptFile (bool) # EncryptFile (bool)
encrypt = fields.Boolean('Encrypt') encrypt = fields.Boolean("Encrypt")
# DocumentOpenPassword (char) # DocumentOpenPassword (char)
document_password = fields.Char(string='Document Password') document_password = fields.Char(string="Document Password")
# RestrictPermissions (bool) # RestrictPermissions (bool)
restrict_permissions = fields.Boolean('Restrict Permissions') restrict_permissions = fields.Boolean("Restrict Permissions")
# PermissionPassword (char) # PermissionPassword (char)
permission_password = fields.Char(string='Permission Password') permission_password = fields.Char(string="Permission Password")
# TODO PreparedPasswords + PreparedPermissionPassword # TODO PreparedPasswords + PreparedPermissionPassword
# I don't see those fields in the LO interface ! # I don't see those fields in the LO interface !
# But they are used in the LO code... # But they are used in the LO code...
# Printing (int) # Printing (int)
printing = fields.Selection([ printing = fields.Selection(
('0', 'Not Permitted'), [
('1', 'Low Resolution (150 dpi)'), ("0", "Not Permitted"),
('2', 'High Resolution'), ("1", "Low Resolution (150 dpi)"),
], string='Printing', default='2') ("2", "High Resolution"),
],
string="Printing",
default="2",
)
# Changes (int) # Changes (int)
changes = fields.Selection([ changes = fields.Selection(
('0', 'Not Permitted'), [
('1', 'Inserting, Deleting and Rotating Pages'), ("0", "Not Permitted"),
('2', 'Filling in Form Fields'), ("1", "Inserting, Deleting and Rotating Pages"),
('3', 'Commenting, Filling in Form Fields'), ("2", "Filling in Form Fields"),
('4', 'Any Except Extracting Pages'), ("3", "Commenting, Filling in Form Fields"),
], string='Changes', default='4') ("4", "Any Except Extracting Pages"),
],
string="Changes",
default="4",
)
# EnableCopyingOfContent (bool) # EnableCopyingOfContent (bool)
content_copying_allowed = fields.Boolean( content_copying_allowed = fields.Boolean(
string='Enable Copying of Content', default=True) string="Enable Copying of Content", default=True
)
# EnableTextAccessForAccessibilityTools (bool) # EnableTextAccessForAccessibilityTools (bool)
text_access_accessibility_tools_allowed = fields.Boolean( text_access_accessibility_tools_allowed = fields.Boolean(
string='Enable Text Access for Accessibility Tools', default=True) string="Enable Text Access for Accessibility Tools", default=True
# DIGITAL SIGNATURE TAB )
# This will be possible but not easy
# Because the certificate parameter is a pointer to a certificate """
# already registered in LO DIGITAL SIGNATURE TAB
# On Linux LO reuses the Mozilla certificate store (on Windows the This will be possible but not easy
# one from Windows) Because the certificate parameter is a pointer to a certificate
# But there seems to be some possibilities to send this certificate via API already registered in LO
# It seems you can add temporary certificates during runtime: On Linux LO reuses the Mozilla certificate store (on Windows the
# https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1security_1_1XCertificateContainer.html one from Windows)
# Here is an API to retrieve the known certificates: But there seems to be some possibilities to send this certificate via API
# https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1xml_1_1crypto_1_1XSecurityEnvironment.html It seems you can add temporary certificates during runtime:
# Thanks to 'samuel_m' on libreoffice-dev IRC chan for pointing me to this https://api.libreoffice.org/docs/idl/ref/
interfacecom_1_1sun_1_1star_1_1security_1_1XCertificateContainer.html
Here is an API to retrieve the known certificates:
https://api.libreoffice.org/docs/idl/ref/
interfacecom_1_1sun_1_1star_1_1xml_1_1crypto_1_1XSecurityEnvironment.html
Thanks to 'samuel_m' on libreoffice-dev IRC chan for pointing me to this
"""
@api.constrains( @api.constrains(
'image_jpeg_quality', 'initial_page', 'pdfa', "image_jpeg_quality",
'cross_doc_link_action', 'magnification', 'zoom') "initial_page",
"pdfa",
"cross_doc_link_action",
"magnification",
"zoom",
)
def check_pdf_options(self): def check_pdf_options(self):
for opt in self: for opt in self:
if opt.image_jpeg_quality > 100 or opt.image_jpeg_quality < 1: if opt.image_jpeg_quality > 100 or opt.image_jpeg_quality < 1:
raise ValidationError(_( raise ValidationError(
_(
"The parameter Image JPEG Quality must be between 1 %%" "The parameter Image JPEG Quality must be between 1 %%"
" and 100 %% (current value: %s %%)") " and 100 %% (current value: %s %%)"
% opt.image_jpeg_quality) )
% opt.image_jpeg_quality
)
if opt.initial_page < 1: if opt.initial_page < 1:
raise ValidationError(_( raise ValidationError(
_(
"The initial page parameter must be strictly positive " "The initial page parameter must be strictly positive "
"(current value: %d)") % opt.initial_page) "(current value: %d)"
if opt.pdfa and opt.cross_doc_link_action == '1': )
raise ValidationError(_( % opt.initial_page
)
if opt.pdfa and opt.cross_doc_link_action == "1":
raise ValidationError(
_(
"The PDF/A option is not compatible with " "The PDF/A option is not compatible with "
"'Cross-document Links' = " "'Cross-document Links' = "
"'Open with PDF Reader Application'.")) "'Open with PDF Reader Application'."
if opt.magnification == '4' and (opt.zoom < 50 or opt.zoom > 1600): )
raise ValidationError(_( )
if opt.magnification == "4" and (opt.zoom < 50 or opt.zoom > 1600):
raise ValidationError(
_(
"The value of the zoom factor must be between 50 and 1600 " "The value of the zoom factor must be between 50 and 1600 "
"(current value: %d)") % opt.zoom) "(current value: %d)"
)
% opt.zoom
)
@api.onchange('encrypt') @api.onchange("encrypt")
def encrypt_change(self): def encrypt_change(self):
if not self.encrypt: if not self.encrypt:
self.document_password = False self.document_password = False
@api.onchange('restrict_permissions') @api.onchange("restrict_permissions")
def restrict_permissions_change(self): def restrict_permissions_change(self):
if not self.restrict_permissions: if not self.restrict_permissions:
self.permission_password = False self.permission_password = False
@api.onchange('pdfa') @api.onchange("pdfa")
def pdfa_change(self): def pdfa_change(self):
if self.pdfa: if self.pdfa:
self.pdf_form = False self.pdf_form = False
@ -229,87 +289,97 @@ class Py3oPdfOptions(models.Model):
self.ensure_one() self.ensure_one()
options = {} options = {}
# GENERAL TAB # GENERAL TAB
if self.image_compression == 'lossless': if self.image_compression == "lossless":
options['UseLosslessCompression'] = True options["UseLosslessCompression"] = True
else: else:
options['UseLosslessCompression'] = False options["UseLosslessCompression"] = False
options['Quality'] = self.image_jpeg_quality options["Quality"] = self.image_jpeg_quality
if self.image_reduce_resolution != 'none': if self.image_reduce_resolution != "none":
options['ReduceImageResolution'] = True options["ReduceImageResolution"] = True
options['MaxImageResolution'] = int(self.image_reduce_resolution) options["MaxImageResolution"] = int(self.image_reduce_resolution)
else: else:
options['ReduceImageResolution'] = False options["ReduceImageResolution"] = False
if self.watermark and self.watermark_text: if self.watermark and self.watermark_text:
options['Watermark'] = self.watermark_text options["Watermark"] = self.watermark_text
if self.pdfa: if self.pdfa:
options['SelectPdfVersion'] = 1 options["SelectPdfVersion"] = 1
options['UseTaggedPDF'] = self.tagged_pdf options["UseTaggedPDF"] = self.tagged_pdf
else: else:
options['SelectPdfVersion'] = 0 options["SelectPdfVersion"] = 0
if self.pdf_form and self.pdf_form_format and not self.pdfa: if self.pdf_form and self.pdf_form_format and not self.pdfa:
options['ExportFormFields'] = True options["ExportFormFields"] = True
options['FormsType'] = int(self.pdf_form_format) options["FormsType"] = int(self.pdf_form_format)
options['AllowDuplicateFieldNames'] = self.pdf_form_allow_duplicate options["AllowDuplicateFieldNames"] = self.pdf_form_allow_duplicate
else: else:
options['ExportFormFields'] = False options["ExportFormFields"] = False
options.update({ options.update(
'ExportBookmarks': self.export_bookmarks, {
'ExportPlaceholders': self.export_placeholders, "ExportBookmarks": self.export_bookmarks,
'ExportNotes': self.export_comments, "ExportPlaceholders": self.export_placeholders,
'ExportHiddenSlides': self.export_hidden_slides, "ExportNotes": self.export_comments,
}) "ExportHiddenSlides": self.export_hidden_slides,
}
)
# INITIAL VIEW TAB # INITIAL VIEW TAB
options.update({ options.update(
'InitialView': int(self.initial_view), {
'InitialPage': self.initial_page, "InitialView": int(self.initial_view),
'Magnification': int(self.magnification), "InitialPage": self.initial_page,
'PageLayout': int(self.page_layout), "Magnification": int(self.magnification),
}) "PageLayout": int(self.page_layout),
}
)
if self.magnification == '4': if self.magnification == "4":
options['Zoom'] = self.zoom options["Zoom"] = self.zoom
# USER INTERFACE TAB # USER INTERFACE TAB
options.update({ options.update(
'ResizeWindowToInitialPage': self.resize_windows_initial_page, {
'CenterWindow': self.center_window, "ResizeWindowToInitialPage": self.resize_windows_initial_page,
'OpenInFullScreenMode': self.open_fullscreen, "CenterWindow": self.center_window,
'DisplayPDFDocumentTitle': self.display_document_title, "OpenInFullScreenMode": self.open_fullscreen,
'HideViewerMenubar': self.hide_menubar, "DisplayPDFDocumentTitle": self.display_document_title,
'HideViewerToolbar': self.hide_toolbar, "HideViewerMenubar": self.hide_menubar,
'HideViewerWindowControls': self.hide_window_controls, "HideViewerToolbar": self.hide_toolbar,
}) "HideViewerWindowControls": self.hide_window_controls,
}
)
if self.open_bookmark_levels: if self.open_bookmark_levels:
options['OpenBookmarkLevels'] = int(self.open_bookmark_levels) options["OpenBookmarkLevels"] = int(self.open_bookmark_levels)
# LINKS TAB # LINKS TAB
options.update({ options.update(
'ExportBookmarksToPDFDestination': {
self.export_bookmarks_named_dest, "ExportBookmarksToPDFDestination": self.export_bookmarks_named_dest,
'ConvertOOoTargetToPDFTarget': self.convert_doc_ref_to_pdf_target, "ConvertOOoTargetToPDFTarget": self.convert_doc_ref_to_pdf_target,
'ExportLinksRelativeFsys': self.export_filesystem_urls, "ExportLinksRelativeFsys": self.export_filesystem_urls,
'PDFViewSelection': int(self.cross_doc_link_action), "PDFViewSelection": int(self.cross_doc_link_action),
}) }
)
# SECURITY TAB # SECURITY TAB
if not self.pdfa: if not self.pdfa:
if self.encrypt and self.document_password: if self.encrypt and self.document_password:
options['EncryptFile'] = True options["EncryptFile"] = True
options['DocumentOpenPassword'] = self.document_password options["DocumentOpenPassword"] = self.document_password
if self.restrict_permissions and self.permission_password: if self.restrict_permissions and self.permission_password:
options.update({ # fmt: off
'RestrictPermissions': True, options.update(
'PermissionPassword': self.permission_password, {
'Printing': int(self.printing), "RestrictPermissions": True,
'Changes': int(self.changes), "PermissionPassword": self.permission_password,
'EnableCopyingOfContent': self.content_copying_allowed, "Printing": int(self.printing),
'EnableTextAccessForAccessibilityTools': "Changes": int(self.changes),
"EnableCopyingOfContent": self.content_copying_allowed,
"EnableTextAccessForAccessibilityTools":
self.text_access_accessibility_tools_allowed, self.text_access_accessibility_tools_allowed,
}) }
)
# fmt: on
logger.debug( logger.debug("Py3o PDF options ID %s converted to %s", self.id, options)
'Py3o PDF options ID %s converted to %s', self.id, options)
return options return options

View File

@ -5,13 +5,14 @@
import json import json
import logging import logging
import os import os
import requests
import tempfile import tempfile
from datetime import datetime
from contextlib import closing from contextlib import closing
from datetime import datetime
from io import BytesIO
import requests
from openerp import _, api, models from openerp import _, api, models
from openerp.exceptions import UserError from openerp.exceptions import UserError
from io import BytesIO
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,11 +20,11 @@ try:
from py3o.template import Template from py3o.template import Template
from py3o.template.helpers import Py3oConvertor from py3o.template.helpers import Py3oConvertor
except ImportError: except ImportError:
logger.debug('Cannot import py3o.template') logger.debug("Cannot import py3o.template")
class Py3oReport(models.TransientModel): class Py3oReport(models.TransientModel):
_inherit = 'py3o.report' _inherit = "py3o.report"
@api.multi @api.multi
def _create_single_report(self, model_instance, data): def _create_single_report(self, model_instance, data):
@ -33,40 +34,32 @@ class Py3oReport(models.TransientModel):
report_xml = self.ir_actions_report_id report_xml = self.ir_actions_report_id
filetype = report_xml.py3o_filetype filetype = report_xml.py3o_filetype
if not report_xml.py3o_server_id: if not report_xml.py3o_server_id:
return super(Py3oReport, self)._create_single_report( return super(Py3oReport, self)._create_single_report(model_instance, data)
model_instance, data,
)
elif report_xml.py3o_is_local_fusion: elif report_xml.py3o_is_local_fusion:
result_path = super( result_path = super(
Py3oReport, self.with_context( Py3oReport, self.with_context(report_py3o_skip_conversion=True)
report_py3o_skip_conversion=True, )._create_single_report(model_instance, data)
) with closing(open(result_path, "rb")) as out_stream:
)._create_single_report(
model_instance, data
)
with closing(open(result_path, 'rb')) as out_stream:
tmpl_data = out_stream.read() tmpl_data = out_stream.read()
datadict = {} datadict = {}
else: else:
result_fd, result_path = tempfile.mkstemp( result_fd, result_path = tempfile.mkstemp(
suffix='.' + filetype, prefix='p3o.report.tmp.') suffix="." + filetype, prefix="p3o.report.tmp."
)
tmpl_data = self.get_template(model_instance) tmpl_data = self.get_template(model_instance)
in_stream = BytesIO(tmpl_data) in_stream = BytesIO(tmpl_data)
with closing(os.fdopen(result_fd, 'wb+')) as out_stream: with closing(os.fdopen(result_fd, "wb+")) as out_stream:
template = Template(in_stream, out_stream, escape_false=True) template = Template(in_stream, out_stream, escape_false=True)
localcontext = self._get_parser_context(model_instance, data) localcontext = self._get_parser_context(model_instance, data)
expressions = template.get_all_user_python_expression() expressions = template.get_all_user_python_expression()
py_expression = template.convert_py3o_to_python_ast( py_expression = template.convert_py3o_to_python_ast(expressions)
expressions)
convertor = Py3oConvertor() convertor = Py3oConvertor()
data_struct = convertor(py_expression) data_struct = convertor(py_expression)
datadict = data_struct.render(localcontext) datadict = data_struct.render(localcontext)
# Call py3o.server to render the template in the desired format # Call py3o.server to render the template in the desired format
files = { files = {"tmpl_file": tmpl_data}
'tmpl_file': tmpl_data,
}
fields = { fields = {
"targetformat": filetype, "targetformat": filetype,
"datadict": json.dumps(datadict), "datadict": json.dumps(datadict),
@ -74,37 +67,41 @@ class Py3oReport(models.TransientModel):
"escape_false": "on", "escape_false": "on",
} }
if report_xml.py3o_is_local_fusion: if report_xml.py3o_is_local_fusion:
fields['skipfusion'] = '1' fields["skipfusion"] = "1"
url = report_xml.py3o_server_id.url url = report_xml.py3o_server_id.url
logger.info( logger.info(
'Connecting to %s to convert report %s to %s', "Connecting to %s to convert report %s to %s",
url, report_xml.report_name, filetype) url,
if filetype == 'pdf': report_xml.report_name,
options = report_xml.pdf_options_id or\ filetype,
report_xml.py3o_server_id.pdf_options_id )
if filetype == "pdf":
options = (
report_xml.pdf_options_id or report_xml.py3o_server_id.pdf_options_id
)
if options: if options:
pdf_options_dict = options.odoo2libreoffice_options() pdf_options_dict = options.odoo2libreoffice_options()
fields['pdf_options'] = json.dumps(pdf_options_dict) fields["pdf_options"] = json.dumps(pdf_options_dict)
logger.debug('PDF Export options: %s', pdf_options_dict) logger.debug("PDF Export options: %s", pdf_options_dict)
start_chrono = datetime.now() start_chrono = datetime.now()
r = requests.post(url, data=fields, files=files) r = requests.post(url, data=fields, files=files)
if r.status_code != 200: if r.status_code != 200:
# server says we have an issue... let's tell that to enduser # server says we have an issue... let's tell that to enduser
logger.error('Py3o fusion server error: %s', r.text) logger.error("Py3o fusion server error: %s", r.text)
raise UserError( raise UserError(_("Fusion server error %s") % r.text)
_('Fusion server error %s') % r.text,
)
chunk_size = 1024 chunk_size = 1024
with open(result_path, 'w+b') as fd: with open(result_path, "w+b") as fd:
for chunk in r.iter_content(chunk_size): for chunk in r.iter_content(chunk_size):
fd.write(chunk) fd.write(chunk)
end_chrono = datetime.now() end_chrono = datetime.now()
convert_seconds = (end_chrono - start_chrono).total_seconds() convert_seconds = (end_chrono - start_chrono).total_seconds()
logger.info( logger.info(
'Report %s converted to %s in %s seconds', "Report %s converted to %s in %s seconds",
report_xml.report_name, filetype, convert_seconds) report_xml.report_name,
filetype,
convert_seconds,
)
if len(model_instance) == 1: if len(model_instance) == 1:
self._postprocess_report( self._postprocess_report(model_instance, result_path)
model_instance, result_path)
return result_path return result_path

View File

@ -4,16 +4,21 @@ from odoo import fields, models
class Py3oServer(models.Model): class Py3oServer(models.Model):
_name = 'py3o.server' _name = "py3o.server"
_description = 'Py3o server' _description = "Py3o server"
_rec_name = 'url' _rec_name = "url"
url = fields.Char( url = fields.Char(
"Py3o Fusion Server URL", required=True, "Py3o Fusion Server URL",
required=True,
help="If your Py3o Fusion server is on the same machine and runs " help="If your Py3o Fusion server is on the same machine and runs "
"on the default port, the URL is http://localhost:8765/form") "on the default port, the URL is http://localhost:8765/form",
)
is_active = fields.Boolean("Active", default=True) is_active = fields.Boolean("Active", default=True)
pdf_options_id = fields.Many2one( pdf_options_id = fields.Many2one(
'py3o.pdf.options', string='PDF Options', ondelete='restrict', "py3o.pdf.options",
string="PDF Options",
ondelete="restrict",
help="PDF options can be set per Py3o Server but also per report. " help="PDF options can be set per Py3o Server but also per report. "
"If both are defined, the options on the report are used.") "If both are defined, the options on the report are used.",
)

View File

@ -1,62 +1,55 @@
# Copyright 2017 Therp BV <http://therp.nl> # Copyright 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import mock import mock
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.report_py3o.models.ir_actions_report import \
PY3O_CONVERSION_COMMAND_PARAMETER from odoo.addons.report_py3o.models.ir_actions_report import (
PY3O_CONVERSION_COMMAND_PARAMETER,
)
from odoo.addons.report_py3o.tests import test_report_py3o from odoo.addons.report_py3o.tests import test_report_py3o
@mock.patch( @mock.patch(
'requests.post', mock.Mock( "requests.post",
mock.Mock(
return_value=mock.Mock( return_value=mock.Mock(
status_code=200, status_code=200, iter_content=mock.Mock(return_value=[b"test_result"])
iter_content=mock.Mock(return_value=[b'test_result']),
)
) )
),
) )
class TestReportPy3oFusionServer(test_report_py3o.TestReportPy3o): class TestReportPy3oFusionServer(test_report_py3o.TestReportPy3o):
def setUp(self): def setUp(self):
super(TestReportPy3oFusionServer, self).setUp() super(TestReportPy3oFusionServer, self).setUp()
py3o_server = self.env['py3o.server'].create({"url": "http://dummy"}) py3o_server = self.env["py3o.server"].create({"url": "http://dummy"})
# check the call to the fusion server # check the call to the fusion server
self.report.write({ self.report.write({"py3o_server_id": py3o_server.id, "py3o_filetype": "pdf"})
"py3o_server_id": py3o_server.id,
"py3o_filetype": 'pdf',
})
self.py3o_server = py3o_server self.py3o_server = py3o_server
def test_no_local_fusion_without_fusion_server(self): def test_no_local_fusion_without_fusion_server(self):
self.assertTrue(self.report.py3o_is_local_fusion) self.assertTrue(self.report.py3o_is_local_fusion)
# Fusion server is only required if not local... # Fusion server is only required if not local...
self.report.write({ self.report.write({"py3o_server_id": None, "py3o_is_local_fusion": True})
"py3o_server_id": None, self.report.write(
"py3o_is_local_fusion": True, {"py3o_server_id": self.py3o_server.id, "py3o_is_local_fusion": True}
}) )
self.report.write({ self.report.write(
"py3o_server_id": self.py3o_server.id, {"py3o_server_id": self.py3o_server.id, "py3o_is_local_fusion": False}
"py3o_is_local_fusion": True, )
})
self.report.write({
"py3o_server_id": self.py3o_server.id,
"py3o_is_local_fusion": False,
})
with self.assertRaises(ValidationError) as e: with self.assertRaises(ValidationError) as e:
self.report.write({ self.report.write({"py3o_server_id": None, "py3o_is_local_fusion": False})
"py3o_server_id": None,
"py3o_is_local_fusion": False,
})
self.assertEqual( self.assertEqual(
e.exception.name, e.exception.name,
"You can not use remote fusion without Fusion server. " "You can not use remote fusion without Fusion server. "
"Please specify a Fusion Server") "Please specify a Fusion Server",
)
def test_reports_no_local_fusion(self): def test_reports_no_local_fusion(self):
self.report.py3o_is_local_fusion = False self.report.py3o_is_local_fusion = False
self.test_reports() self.test_reports()
def test_odoo2libreoffice_options(self): def test_odoo2libreoffice_options(self):
for options in self.env['py3o.pdf.options'].search([]): for options in self.env["py3o.pdf.options"].search([]):
options_dict = options.odoo2libreoffice_options() options_dict = options.odoo2libreoffice_options()
self.assertIsInstance(options_dict, dict) self.assertIsInstance(options_dict, dict)
@ -73,8 +66,9 @@ class TestReportPy3oFusionServer(test_report_py3o.TestReportPy3o):
self.assertFalse(self.report.msg_py3o_report_not_available) self.assertFalse(self.report.msg_py3o_report_not_available)
# specify a wrong lo bin path and a non native format. # specify a wrong lo bin path and a non native format.
self.env['ir.config_parameter'].set_param( self.env["ir.config_parameter"].set_param(
PY3O_CONVERSION_COMMAND_PARAMETER, "/wrong_path") PY3O_CONVERSION_COMMAND_PARAMETER, "/wrong_path"
)
self.report.py3o_filetype = "pdf" self.report.py3o_filetype = "pdf"
self.report.refresh() self.report.refresh()
# no native and no bin path, everything is still OK since a fusion # no native and no bin path, everything is still OK since a fusion
@ -91,8 +85,9 @@ class TestReportPy3oFusionServer(test_report_py3o.TestReportPy3o):
self.assertTrue(self.report.msg_py3o_report_not_available) self.assertTrue(self.report.msg_py3o_report_not_available)
# if we set a libreffice runtime, the report is available again # if we set a libreffice runtime, the report is available again
self.env['ir.config_parameter'].set_param( self.env["ir.config_parameter"].set_param(
PY3O_CONVERSION_COMMAND_PARAMETER, "libreoffice") PY3O_CONVERSION_COMMAND_PARAMETER, "libreoffice"
)
self.report.refresh() self.report.refresh()
self.assertTrue(self.report.lo_bin_path) self.assertTrue(self.report.lo_bin_path)
self.assertFalse(self.report.is_py3o_report_not_available) self.assertFalse(self.report.is_py3o_report_not_available)