650 lines
28 KiB
Python
650 lines
28 KiB
Python
# See LICENSE file for full copyright and licensing details.
|
|
import base64
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
from contextlib import closing
|
|
|
|
from odoo.tools.pdf import PdfFileReader, PdfFileWriter
|
|
from reportlab.graphics.barcode import createBarcodeDrawing
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.misc import find_in_path
|
|
from odoo.tools.safe_eval import safe_eval
|
|
from odoo.tools.translate import _
|
|
|
|
try:
|
|
createBarcodeDrawing(
|
|
"Code128",
|
|
value="foo",
|
|
format="png",
|
|
width=100,
|
|
height=100,
|
|
humanReadable=1,
|
|
).asString("png")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Helpers
|
|
# --------------------------------------------------------------------------
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_wkhtmltopdf_bin():
|
|
return find_in_path("wkhtmltopdf")
|
|
|
|
|
|
class ReportBackgroundLine(models.Model):
|
|
_name = "report.background.line"
|
|
_description = "Report Background Line"
|
|
|
|
page_number = fields.Integer()
|
|
type = fields.Selection(
|
|
[
|
|
("fixed", "Fixed Page"),
|
|
("expression", "Expression"),
|
|
("first_page", "First Page"),
|
|
("last_page", "Last Page"),
|
|
("remaining", "Remaining pages"),
|
|
]
|
|
)
|
|
background_pdf = fields.Binary(string="Background PDF")
|
|
# New field. #22260
|
|
file_name = fields.Char(string="File Name")
|
|
report_id = fields.Many2one("ir.actions.report", string="Report")
|
|
page_expression = fields.Char()
|
|
fall_back_to_company = fields.Boolean()
|
|
# New fields. #22260
|
|
lang_id = fields.Many2one(
|
|
"res.lang",
|
|
string="Language",
|
|
)
|
|
# Added new field #T5211
|
|
company_id = fields.Many2one(comodel_name="res.company", string="Company")
|
|
|
|
|
|
class IrActionsReport(models.Model):
|
|
_inherit = "ir.actions.report"
|
|
|
|
custom_report_background = fields.Boolean(string="Custom Report Background")
|
|
custom_report_background_image = fields.Binary(string="Background Image")
|
|
custom_report_type = fields.Selection(
|
|
[
|
|
("company", "From Company"),
|
|
("report", "From Report Fixed"),
|
|
("dynamic", "From Report Dynamic"),
|
|
]
|
|
)
|
|
|
|
background_ids = fields.One2many(
|
|
"report.background.line", "report_id", "Background Configuration"
|
|
)
|
|
# New fields. #22260
|
|
bg_per_lang_ids = fields.One2many(
|
|
"report.background.lang",
|
|
"report_id",
|
|
string="Background Per Language",
|
|
)
|
|
is_bg_per_lang = fields.Boolean(
|
|
string="Is Background Per Language",
|
|
)
|
|
|
|
def get_company_without_custom_bg(self):
|
|
"""New method for search and get company in which custom bg per language is not
|
|
set. #22260"""
|
|
res_company_env = self.env["res.company"].search([])
|
|
# Filtered company in which is_bg_per_lang is not set and
|
|
# attachment is not set.
|
|
company = res_company_env.filtered(
|
|
lambda c: not c.is_bg_per_lang or not c.bg_per_lang_ids
|
|
)
|
|
return company
|
|
|
|
@api.constrains(
|
|
"is_bg_per_lang", "bg_per_lang_ids", "custom_report_type", "background_ids"
|
|
)
|
|
def _check_report_custom_bg_config(self):
|
|
"""New constrains method for check custom bg per company is set or not when for
|
|
'report' & 'dynamic' type. #22260"""
|
|
# If is_bg_per_lang is false then return.
|
|
if not self.is_bg_per_lang:
|
|
return
|
|
# If type is 'report' and custom bg per lang is not set then raise warning.
|
|
if self.custom_report_type == "report" and not self.bg_per_lang_ids:
|
|
raise UserError(
|
|
_("Please configure Custom Background Per Language for Report type!")
|
|
)
|
|
# If type is 'dynamic' and custom bg per lang is not set then raise warning.
|
|
elif self.custom_report_type == "dynamic" and not self.background_ids:
|
|
raise UserError(
|
|
_("Please configure Custom Background Per Language for Dynamic type!")
|
|
)
|
|
# Check type is dynamic and background_ids is set or not.
|
|
elif self.custom_report_type == "dynamic" and self.background_ids:
|
|
# Filter fall_back_to_company true records.
|
|
fbc = self.background_ids.filtered(lambda bg: bg.fall_back_to_company)
|
|
# If fbc and custom bg not set at company level then raise warning.
|
|
if fbc:
|
|
company = self.get_company_without_custom_bg()
|
|
# If any attachment not set in the any company then raise warning.
|
|
if company:
|
|
raise UserError(
|
|
_(
|
|
"Please configure Custom Background Per Language in every "
|
|
"company!"
|
|
)
|
|
)
|
|
# If type is 'company' or type is not set then search
|
|
# configuration in all company.
|
|
elif self.custom_report_type == "company" or not self.custom_report_type:
|
|
company = self.get_company_without_custom_bg()
|
|
# If any attachment not set in the any company then raise warning.
|
|
if company:
|
|
raise UserError(
|
|
_(
|
|
"Please configure Custom Background Per Language in every "
|
|
"company!"
|
|
)
|
|
)
|
|
|
|
def _render_qweb_pdf(self, res_ids=None, data=None):
|
|
Model = self.env[self.model]
|
|
record_ids = Model.browse(res_ids)
|
|
company_id = False
|
|
if record_ids[:1]._name == "res.company":
|
|
company_id = record_ids[:1]
|
|
# Fix test cases error. #22107
|
|
elif hasattr(record_ids[:1], "company_id"):
|
|
# If in record company is not set then consider current log in
|
|
# user's company. #22476
|
|
company_id = record_ids[:1].company_id or self.env.user.company_id
|
|
else:
|
|
company_id = self.env.company
|
|
|
|
# Add custom_bg_res_ids in context. #22260
|
|
return super(
|
|
IrActionsReport,
|
|
self.with_context(custom_bg_res_ids=res_ids, background_company=company_id),
|
|
)._render_qweb_pdf(res_ids=res_ids, data=data)
|
|
|
|
def add_pdf_watermarks(self, custom_background_data, page):
|
|
"""create a temp file and set datas and added in report page. #T4209"""
|
|
temp_back_id, temp_back_path = tempfile.mkstemp(
|
|
suffix=".pdf", prefix="back_report.tmp."
|
|
)
|
|
back_data = base64.b64decode(custom_background_data)
|
|
with closing(os.fdopen(temp_back_id, "wb")) as back_file:
|
|
back_file.write(back_data)
|
|
pdf_reader_watermark = PdfFileReader(temp_back_path, "rb")
|
|
watermark_page = pdf_reader_watermark.getPage(0)
|
|
watermark_page.mergePage(page)
|
|
return watermark_page
|
|
|
|
def get_lang(self):
|
|
"""New method for return language, if partner_id is available in model and
|
|
partner is set in that model, else set current logged in user's language.
|
|
#22260"""
|
|
res_record_ids = self._context.get("custom_bg_res_ids")
|
|
model = self.env[self.model]
|
|
record_ids = model.browse(res_record_ids)
|
|
lang_code = False
|
|
# If partner_id field in the model and partner is set in the model the consider
|
|
# partner's language.
|
|
# NOTE: Used "record_ids[-1]" to avoid loop, if use loop then always set last
|
|
# record partner's language.
|
|
if "partner_id" in model._fields and record_ids[-1].partner_id:
|
|
partner_lang = record_ids[-1].partner_id.lang
|
|
lang_code = partner_lang if partner_lang else "en_US"
|
|
else:
|
|
# If partner_id field is not in model or partner_id is not set then consider
|
|
# current user's language.
|
|
lang_code = self._context.get("lang")
|
|
return lang_code
|
|
|
|
def get_bg_per_lang(self):
|
|
"""New method for get custom background based on the partner languages for
|
|
report type and company type. #22260"""
|
|
company_background = self._context.get("background_company")
|
|
lang_code = self.get_lang()
|
|
# If custom_report_type is dynamic then set language related domains.
|
|
if self.custom_report_type == "dynamic":
|
|
# If is_bg_per_lang true then set lang_code related domain.
|
|
if self.is_bg_per_lang:
|
|
lang_domain = [
|
|
("lang_id.code", "=", lang_code),
|
|
]
|
|
else:
|
|
# If is_bg_per_lang false then set lang_id related domain.
|
|
lang_domain = [
|
|
("lang_id", "=", False),
|
|
]
|
|
return lang_domain
|
|
|
|
# If custom_report_type is report then set report(self) id.
|
|
if self.custom_report_type == "report":
|
|
custom_bg_from = self
|
|
# If custom_report_type is company then set current company id from context.
|
|
if self.custom_report_type == "company" or not self.custom_report_type:
|
|
custom_bg_from = company_background
|
|
|
|
# Filter records from report_background_lang model based on the languages.
|
|
# custom_bg_from: company_id or report_id(self).
|
|
custom_bg_lang = custom_bg_from.bg_per_lang_ids.filtered(
|
|
lambda l: l.lang_id.code == lang_code
|
|
)
|
|
|
|
# Set 1st custom background.
|
|
custom_background = custom_bg_lang[:1].background_pdf
|
|
return custom_background
|
|
|
|
@api.model
|
|
def _run_wkhtmltopdf(
|
|
self,
|
|
bodies,
|
|
header=None,
|
|
footer=None,
|
|
landscape=False,
|
|
specific_paperformat_args=None,
|
|
set_viewport_size=False,
|
|
):
|
|
"""Execute wkhtmltopdf as a subprocess in order to convert html given
|
|
in input into a pdf document.
|
|
|
|
:param bodies: The html bodies of the report, one per page.
|
|
:param header: The html header of the report containing all headers.
|
|
:param footer: The html footer of the report containing all footers.
|
|
:param landscape: Force the pdf to be rendered under a landscape
|
|
format.
|
|
:param specific_paperformat_args: dict of prioritized paperformat
|
|
arguments.
|
|
:param set_viewport_size: Enable a viewport sized '1024x1280' or
|
|
'1280x1024' depending of landscape arg.
|
|
:return: Content of the pdf as a string
|
|
"""
|
|
|
|
# call default odoo standard function of paperformat #19896
|
|
# https://github.com/odoo/odoo/blob/13.0/odoo/addons/base/models
|
|
# /ir_actions_report.py#L243
|
|
paperformat_id = self.get_paperformat()
|
|
|
|
# Build the base command args for wkhtmltopdf bin
|
|
command_args = self._build_wkhtmltopdf_args(
|
|
paperformat_id,
|
|
landscape,
|
|
specific_paperformat_args=specific_paperformat_args,
|
|
set_viewport_size=set_viewport_size,
|
|
)
|
|
|
|
files_command_args = []
|
|
temporary_files = []
|
|
if header:
|
|
head_file_fd, head_file_path = tempfile.mkstemp(
|
|
suffix=".html", prefix="report.header.tmp."
|
|
)
|
|
with closing(os.fdopen(head_file_fd, "wb")) as head_file:
|
|
head_file.write(header)
|
|
temporary_files.append(head_file_path)
|
|
files_command_args.extend(["--header-html", head_file_path])
|
|
if footer:
|
|
foot_file_fd, foot_file_path = tempfile.mkstemp(
|
|
suffix=".html", prefix="report.footer.tmp."
|
|
)
|
|
with closing(os.fdopen(foot_file_fd, "wb")) as foot_file:
|
|
foot_file.write(footer)
|
|
temporary_files.append(foot_file_path)
|
|
files_command_args.extend(["--footer-html", foot_file_path])
|
|
|
|
paths = []
|
|
for i, body in enumerate(bodies):
|
|
prefix = "%s%d." % ("report.body.tmp.", i)
|
|
body_file_fd, body_file_path = tempfile.mkstemp(
|
|
suffix=".html", prefix=prefix
|
|
)
|
|
with closing(os.fdopen(body_file_fd, "wb")) as body_file:
|
|
body_file.write(body)
|
|
paths.append(body_file_path)
|
|
temporary_files.append(body_file_path)
|
|
|
|
pdf_report_fd, pdf_report_path = tempfile.mkstemp(
|
|
suffix=".pdf", prefix="report.tmp."
|
|
)
|
|
os.close(pdf_report_fd)
|
|
temporary_files.append(pdf_report_path)
|
|
try:
|
|
wkhtmltopdf = (
|
|
[_get_wkhtmltopdf_bin()]
|
|
+ command_args
|
|
+ files_command_args
|
|
+ paths
|
|
+ [pdf_report_path]
|
|
)
|
|
process = subprocess.Popen(
|
|
wkhtmltopdf, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
)
|
|
out, err = process.communicate()
|
|
|
|
if process.returncode not in [0, 1]:
|
|
if process.returncode == -11:
|
|
message = _(
|
|
"Wkhtmltopdf failed (error code: %s). Memory limit too low or "
|
|
"maximum file number of subprocess reached. Message : %s"
|
|
)
|
|
else:
|
|
message = _("Wkhtmltopdf failed (error code: %s). Message: %s")
|
|
_logger.warning(message, process.returncode, err[-1000:])
|
|
raise UserError(message % (str(process.returncode), err[-1000:]))
|
|
else:
|
|
if err:
|
|
_logger.warning("wkhtmltopdf: %s" % err)
|
|
# Dynamic Type.
|
|
if (
|
|
self
|
|
and self.custom_report_background
|
|
and self.custom_report_type == "dynamic"
|
|
):
|
|
temp_report_id, temp_report_path = tempfile.mkstemp(
|
|
suffix=".pdf", prefix="with_back_report.tmp."
|
|
)
|
|
output = PdfFileWriter()
|
|
pdf_reader_content = PdfFileReader(pdf_report_path, "rb")
|
|
|
|
# Call method for get domain related to the languages. #22260
|
|
lang_domain = self.get_bg_per_lang()
|
|
|
|
# Added lang_domain in all search methods. #22260
|
|
first_page = self.background_ids.search(
|
|
lang_domain
|
|
+ [
|
|
("type", "=", "first_page"),
|
|
("report_id", "=", self.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
last_page = self.background_ids.search(
|
|
lang_domain
|
|
+ [
|
|
("type", "=", "last_page"),
|
|
("report_id", "=", self.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
fixed_pages = self.background_ids.search(
|
|
lang_domain
|
|
+ [
|
|
("type", "=", "fixed"),
|
|
("report_id", "=", self.id),
|
|
]
|
|
)
|
|
remaining_pages = self.background_ids.search(
|
|
lang_domain
|
|
+ [
|
|
("type", "=", "remaining"),
|
|
("report_id", "=", self.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
expression = self.background_ids.search(
|
|
lang_domain
|
|
+ [
|
|
("type", "=", "expression"),
|
|
("report_id", "=", self.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
|
|
company_background = self._context.get("background_company")
|
|
company_background_img = (
|
|
company_background.custom_report_background_image
|
|
)
|
|
company_background_dynamic = company_background.background_ids
|
|
# Start. #22260
|
|
if self.is_bg_per_lang:
|
|
lang_code = self.get_lang()
|
|
custom_bg_lang = company_background.bg_per_lang_ids.filtered(
|
|
lambda l: l.lang_id.code == lang_code
|
|
)
|
|
# End. #22260
|
|
for i in range(pdf_reader_content.getNumPages()):
|
|
watermark = ""
|
|
if first_page and i == 0:
|
|
if first_page.fall_back_to_company and company_background:
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = company_background_dynamic.filtered(
|
|
lambda a: a.type == "first_page"
|
|
)
|
|
if company_watermark:
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
# Fix page 1st issue. #22260
|
|
elif first_page.background_pdf:
|
|
watermark = first_page.background_pdf
|
|
elif last_page and i == pdf_reader_content.getNumPages() - 1:
|
|
if last_page.fall_back_to_company and company_background:
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = company_background_dynamic.filtered(
|
|
lambda a: a.type == "last_page"
|
|
)
|
|
if company_watermark:
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
elif last_page.background_pdf:
|
|
watermark = last_page.background_pdf
|
|
elif i + 1 in fixed_pages.mapped("page_number"):
|
|
fixed_page = fixed_pages.search(
|
|
[
|
|
("page_number", "=", i + 1),
|
|
("report_id", "=", self.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
if (
|
|
fixed_page
|
|
and fixed_page.fall_back_to_company
|
|
and company_background
|
|
):
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = company_background_dynamic.filtered(
|
|
lambda a: a.type == "fixed"
|
|
and a.page_number == i + 1
|
|
)
|
|
if company_watermark:
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
elif fixed_page and fixed_page.background_pdf:
|
|
watermark = fixed_page.background_pdf
|
|
elif expression and expression.page_expression:
|
|
eval_dict = {"page": i + 1}
|
|
safe_eval(
|
|
expression.page_expression,
|
|
eval_dict,
|
|
mode="exec",
|
|
nocopy=True,
|
|
)
|
|
if (
|
|
expression.fall_back_to_company
|
|
and company_background
|
|
and eval_dict.get("result", False)
|
|
):
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = company_background_dynamic.filtered(
|
|
lambda a: a.type == "expression"
|
|
and a.page_expression
|
|
)
|
|
if company_watermark:
|
|
company_eval_dict = {"page": i + 1}
|
|
safe_eval(
|
|
company_watermark.page_expression,
|
|
company_eval_dict,
|
|
mode="exec",
|
|
nocopy=True,
|
|
)
|
|
if company_eval_dict.get("result", False):
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
elif (
|
|
eval_dict.get("result", False) and expression.background_pdf
|
|
):
|
|
watermark = expression.background_pdf
|
|
else:
|
|
if remaining_pages:
|
|
if (
|
|
remaining_pages.fall_back_to_company
|
|
and company_background
|
|
):
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from
|
|
# the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = (
|
|
company_background_dynamic.filtered(
|
|
lambda a: a.type == "remaining"
|
|
)
|
|
)
|
|
if company_watermark:
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
elif remaining_pages.background_pdf:
|
|
watermark = remaining_pages.background_pdf
|
|
else:
|
|
if remaining_pages:
|
|
if (
|
|
remaining_pages.fall_back_to_company
|
|
and company_background
|
|
):
|
|
# Start. #22260
|
|
# If is_bg_per_lang then get custom bg from the company.
|
|
if self.is_bg_per_lang:
|
|
watermark = custom_bg_lang[:1].background_pdf
|
|
else:
|
|
company_watermark = (
|
|
company_background_dynamic.filtered(
|
|
lambda a: a.type == "remaining"
|
|
)
|
|
)
|
|
if company_watermark:
|
|
watermark = company_watermark.background_pdf
|
|
else:
|
|
watermark = company_background_img
|
|
# End. #22260
|
|
elif remaining_pages.background_pdf:
|
|
watermark = remaining_pages.background_pdf
|
|
if watermark:
|
|
page = self.add_pdf_watermarks(
|
|
watermark,
|
|
pdf_reader_content.getPage(i),
|
|
)
|
|
else:
|
|
page = pdf_reader_content.getPage(i)
|
|
output.addPage(page)
|
|
output.write(open(temp_report_path, "wb"))
|
|
pdf_report_path = temp_report_path
|
|
os.close(temp_report_id)
|
|
elif self.custom_report_background:
|
|
temp_back_id, temp_back_path = tempfile.mkstemp(
|
|
suffix=".pdf", prefix="back_report.tmp."
|
|
)
|
|
custom_background = False
|
|
# From Report Type.
|
|
if (
|
|
self
|
|
and self.custom_report_background
|
|
and self.custom_report_type == "report"
|
|
):
|
|
# 222760 Starts.If background per lang is True then call method for
|
|
# get custom background based on different languages.
|
|
if self.is_bg_per_lang:
|
|
custom_background = self.get_bg_per_lang()
|
|
# 222760 Ends.
|
|
else:
|
|
custom_background = self.custom_report_background_image
|
|
# 222760 Ends.
|
|
# From Company Type.
|
|
if (
|
|
self.custom_report_background
|
|
and not custom_background
|
|
and (
|
|
self.custom_report_type == "company"
|
|
or not self.custom_report_type
|
|
)
|
|
and self._context.get("background_company") # #19896
|
|
):
|
|
# report background will be displayed based on the current
|
|
# company #19896
|
|
company_id = self._context.get("background_company")
|
|
# 222760 Starts. If background per lang is True then call method for
|
|
# get custom background from company based on different languages.
|
|
if self.is_bg_per_lang:
|
|
custom_background = self.get_bg_per_lang()
|
|
# 222760 Ends.
|
|
else:
|
|
custom_background = company_id.custom_report_background_image
|
|
# If background found from any type then set that to the report.
|
|
if custom_background:
|
|
back_data = base64.b64decode(custom_background)
|
|
with closing(os.fdopen(temp_back_id, "wb")) as back_file:
|
|
back_file.write(back_data)
|
|
temp_report_id, temp_report_path = tempfile.mkstemp(
|
|
suffix=".pdf", prefix="with_back_report.tmp."
|
|
)
|
|
output = PdfFileWriter()
|
|
pdf_reader_content = PdfFileReader(pdf_report_path, "rb")
|
|
|
|
for i in range(pdf_reader_content.getNumPages()):
|
|
page = pdf_reader_content.getPage(i)
|
|
pdf_reader_watermark = PdfFileReader(temp_back_path, "rb")
|
|
watermark = pdf_reader_watermark.getPage(0)
|
|
watermark.mergePage(page)
|
|
output.addPage(watermark)
|
|
output.write(open(temp_report_path, "wb"))
|
|
pdf_report_path = temp_report_path
|
|
os.close(temp_report_id)
|
|
except Exception as ex:
|
|
logging.info("Error while PDF Background %s" % ex)
|
|
raise
|
|
|
|
with open(pdf_report_path, "rb") as pdf_document:
|
|
pdf_content = pdf_document.read()
|
|
|
|
# Manual cleanup of the temporary files
|
|
for temporary_file in temporary_files:
|
|
try:
|
|
os.unlink(temporary_file)
|
|
except (OSError, IOError):
|
|
_logger.error("Error when trying to remove file %s" % temporary_file)
|
|
|
|
return pdf_content
|