Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# Copyright 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import ir_actions_report
from . import py3o_pdf_options
from . import py3o_report
from . import py3o_server

View File

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

View File

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

View File

@@ -0,0 +1,106 @@
# © 2013 XCG Consulting <http://odoo.consulting>
# © 2016 ACSONE SA/NV
# © 2017 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json
import logging
import os
import tempfile
from contextlib import closing
from datetime import datetime
from io import BytesIO
import requests
from odoo import _, models
from odoo.exceptions import UserError
logger = logging.getLogger(__name__)
try:
from py3o.template import Template
from py3o.template.helpers import Py3oConvertor
except ImportError:
logger.debug("Cannot import py3o.template")
class Py3oReport(models.TransientModel):
_inherit = "py3o.report"
def _create_single_report(self, model_instance, data):
"""This function to generate our py3o report"""
self.ensure_one()
report_xml = self.ir_actions_report_id
filetype = report_xml.py3o_filetype
if not report_xml.py3o_server_id:
return super()._create_single_report(model_instance, data)
elif report_xml.py3o_is_local_fusion:
result_path = super(
Py3oReport, self.with_context(report_py3o_skip_conversion=True)
)._create_single_report(model_instance, data)
with closing(open(result_path, "rb")) as out_stream:
tmpl_data = out_stream.read()
datadict = {}
else:
result_fd, result_path = tempfile.mkstemp(
suffix="." + filetype, prefix="p3o.report.tmp."
)
tmpl_data = self.get_template(model_instance)
in_stream = BytesIO(tmpl_data)
with closing(os.fdopen(result_fd, "wb+")) as out_stream:
template = Template(in_stream, out_stream, escape_false=True)
localcontext = self._get_parser_context(model_instance, data)
expressions = template.get_all_user_python_expression()
py_expression = template.convert_py3o_to_python_ast(expressions)
convertor = Py3oConvertor()
data_struct = convertor(py_expression)
datadict = data_struct.render(localcontext)
# Call py3o.server to render the template in the desired format
files = {"tmpl_file": tmpl_data}
fields = {
"targetformat": filetype,
"datadict": json.dumps(datadict),
"image_mapping": "{}",
"escape_false": "on",
}
if report_xml.py3o_is_local_fusion:
fields["skipfusion"] = "1"
url = report_xml.py3o_server_id.url
logger.info(
"Connecting to %s to convert report %s to %s",
url,
report_xml.report_name,
filetype,
)
if filetype == "pdf":
options = (
report_xml.pdf_options_id or report_xml.py3o_server_id.pdf_options_id
)
if options:
pdf_options_dict = options.odoo2libreoffice_options()
fields["pdf_options"] = json.dumps(pdf_options_dict)
logger.debug("PDF Export options: %s", pdf_options_dict)
start_chrono = datetime.now()
r = requests.post(url, data=fields, files=files, timeout=10)
if r.status_code != 200:
# server says we have an issue... let's tell that to enduser
logger.error("Py3o fusion server error: %s", r.text)
raise UserError(_("Fusion server error %s") % r.text)
chunk_size = 1024
with open(result_path, "w+b") as fd:
for chunk in r.iter_content(chunk_size):
fd.write(chunk)
end_chrono = datetime.now()
convert_seconds = (end_chrono - start_chrono).total_seconds()
logger.info(
"Report %s converted to %s in %s seconds",
report_xml.report_name,
filetype,
convert_seconds,
)
if len(model_instance) == 1:
self._postprocess_report(model_instance, result_path)
return result_path

View File

@@ -0,0 +1,24 @@
# Copyright 2013 XCG Consulting (http://odoo.consulting)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class Py3oServer(models.Model):
_name = "py3o.server"
_description = "Py3o server"
_rec_name = "url"
url = fields.Char(
"Py3o Fusion Server URL",
required=True,
help="If your Py3o Fusion server is on the same machine and runs "
"on the default port, the URL is http://localhost:8765/form",
)
is_active = fields.Boolean("Active", default=True)
pdf_options_id = fields.Many2one(
"py3o.pdf.options",
string="PDF Options",
ondelete="restrict",
help="PDF options can be set per Py3o Server but also per report. "
"If both are defined, the options on the report are used.",
)