[IMP] report_qweb_signer: Black python code

pull/522/head
Laurent-Corron 2021-06-29 10:10:17 +02:00
parent 4e321eb209
commit ae6d5a659c
16 changed files with 226 additions and 185 deletions

View File

@ -237,8 +237,8 @@ class BiSQLViewField(models.Model):
self.ensure_one()
res = ""
if self.field_description and self.is_group_by:
res = """<filter name="%s" string="%s"
context="{'group_by':'%s'}"/>""" % (
res = """<filter name="{}" string="{}"
context="{{'group_by':'{}'}}"/>""".format(
self.field_description.lower().replace(" ", "_"),
self.field_description,
self.name,

View File

@ -9,16 +9,11 @@
"version": "12.0.1.0.1",
"category": "Reporting",
"website": "https://github.com/oca/reporting-engine",
"author": "Tecnativa, "
"Odoo Community Association (OCA)",
"author": "Tecnativa, " "Odoo Community Association (OCA)",
"license": "AGPL-3",
"installable": True,
"depends": [
"web_editor",
],
"external_dependencies": {
"bin": ['/usr/bin/java'],
},
"depends": ["web_editor"],
"external_dependencies": {"bin": ["/usr/bin/java"]},
"data": [
"data/defaults.xml",
"security/ir.model.access.csv",

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.config_parameter" id="report_qweb_signer_java_param">
<field name="key">report_qweb_signer.java_parameters</field>

View File

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2015 Tecnativa - Antonio Espinosa
Copyright 2017 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo noupdate="1">
<record id="demo_certificate_test" model="report.certificate">
<field name="company_id" ref="base.main_company"/>
<field name="company_id" ref="base.main_company" />
<field name="name">Test OCA certificate</field>
<field name="path">test.p12</field>
<field name="password_file">test.passwd</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="model_id" ref="base.model_res_partner" />
<field name="domain">[('customer', '=', True)]</field>
<field name="allow_only_one" eval="True"/>
<field name="attachment">'test_' + (object.name or '').replace(' ', '_').lower() + '.signed.pdf'</field>
<field name="allow_only_one" eval="True" />
<field
name="attachment"
>'test_' + (object.name or '').replace(' ', '_').lower() + '.signed.pdf'</field>
</record>
</odoo>

View File

@ -1,36 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2015 Tecnativa - Antonio Espinosa
Copyright 2017 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<template id="report_partner_demo_document">
<t t-call="web.external_layout">
<div class="page">
<div class="row">
<div class="col-md-12">
<span>This is a sample report for testing PDF certificates.</span>
<span
>This is a sample report for testing PDF certificates.</span>
</div>
</div>
<div class="row">
<div class="col-md-12">
<strong>Partner:</strong> <span t-field="o.name"/>
<strong>Partner:</strong>
<span t-field="o.name" />
</div>
</div>
</div>
</t>
</template>
<template id="report_partner_demo">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report_qweb_signer.report_partner_demo_document" t-lang="o.lang"/>
<t
t-call="report_qweb_signer.report_partner_demo_document"
t-lang="o.lang"
/>
</t>
</t>
</template>
<report
id="partner_demo_report"
model="res.partner"
@ -41,5 +43,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
attachment_use="True"
attachment="'test_' + (object.name or '').replace(' ', '_').lower() + '.pdf'"
/>
</odoo>

View File

@ -3,41 +3,43 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
from contextlib import closing
import logging
import os
import subprocess
import tempfile
import time
from contextlib import closing
from odoo import models, api, _
from odoo.exceptions import UserError, AccessError
from odoo import _, api, models
from odoo.exceptions import AccessError, UserError
from odoo.tools.safe_eval import safe_eval
import logging
_logger = logging.getLogger(__name__)
def _normalize_filepath(path):
path = path or ''
path = path or ""
path = path.strip()
if not os.path.isabs(path):
me = os.path.dirname(__file__)
path = '{}/../static/certificate/'.format(me) + path
path = "{}/../static/certificate/".format(me) + path
path = os.path.normpath(path)
return path if os.path.exists(path) else False
class IrActionsReport(models.Model):
_inherit = 'ir.actions.report'
_inherit = "ir.actions.report"
def _certificate_get(self, res_ids):
"""Obtain the proper certificate for the report and the conditions."""
if self.report_type != 'qweb-pdf':
if self.report_type != "qweb-pdf":
return False
certificates = self.env['report.certificate'].search([
('company_id', '=', self.env.user.company_id.id),
('model_id', '=', self.model),
])
certificates = self.env["report.certificate"].search(
[
("company_id", "=", self.env.user.company_id.id),
("model_id", "=", self.model),
]
)
if not certificates:
return False
for cert in certificates:
@ -46,16 +48,19 @@ class IrActionsReport(models.Model):
_logger.debug(
"Certificate '%s' allows only one document, "
"but printing %d documents",
cert.name, len(res_ids))
cert.name,
len(res_ids),
)
continue
# Check domain
if cert.domain:
domain = [('id', 'in', tuple(res_ids))]
domain = [("id", "in", tuple(res_ids))]
domain = domain + safe_eval(cert.domain)
docs = self.env[cert.model_id.model].search(domain)
if not docs:
_logger.debug(
"Certificate '%s' domain not satisfied", cert.name)
"Certificate '%s' domain not satisfied", cert.name
)
continue
# Certificate match!
return cert
@ -65,10 +70,7 @@ class IrActionsReport(models.Model):
if len(res_ids) != 1:
return False
doc = self.env[certificate.model_id.model].browse(res_ids[0])
return safe_eval(certificate.attachment, {
'object': doc,
'time': time
})
return safe_eval(certificate.attachment, {"object": doc, "time": time})
def _attach_signed_read(self, res_ids, certificate):
if len(res_ids) != 1:
@ -76,11 +78,14 @@ class IrActionsReport(models.Model):
filename = self._attach_filename_get(res_ids, certificate)
if not filename:
return False
attachment = self.env['ir.attachment'].search([
('datas_fname', '=', filename),
('res_model', '=', certificate.model_id.model),
('res_id', '=', res_ids[0]),
], limit=1)
attachment = self.env["ir.attachment"].search(
[
("datas_fname", "=", filename),
("res_model", "=", certificate.model_id.model),
("res_id", "=", res_ids[0]),
],
limit=1,
)
if attachment:
return base64.decodestring(attachment.datas)
return False
@ -92,45 +97,57 @@ class IrActionsReport(models.Model):
if not filename:
return False
try:
attachment = self.env['ir.attachment'].create({
'name': filename,
'datas': base64.encodestring(signed),
'datas_fname': filename,
'res_model': certificate.model_id.model,
'res_id': res_ids[0],
})
attachment = self.env["ir.attachment"].create(
{
"name": filename,
"datas": base64.encodestring(signed),
"datas_fname": filename,
"res_model": certificate.model_id.model,
"res_id": res_ids[0],
}
)
except AccessError:
raise UserError(
_('Saving signed report (PDF): '
'You do not have enough access rights to save attachments'))
_(
"Saving signed report (PDF): "
"You do not have enough access rights to save attachments"
)
)
return attachment
def _signer_bin(self, opts):
me = os.path.dirname(__file__)
irc_param = self.env['ir.config_parameter'].sudo()
java_bin = 'java -jar'
java_param = irc_param.get_param('report_qweb_signer.java_parameters')
jar = '{}/../static/jar/jPdfSign.jar'.format(me)
return '%s %s %s %s' % (java_bin, java_param, jar, opts)
irc_param = self.env["ir.config_parameter"].sudo()
java_bin = "java -jar"
java_param = irc_param.get_param("report_qweb_signer.java_parameters")
jar = "{}/../static/jar/jPdfSign.jar".format(me)
return "{} {} {} {}".format(java_bin, java_param, jar, opts)
def pdf_sign(self, pdf, certificate):
pdfsigned = pdf + '.signed.pdf'
pdfsigned = pdf + ".signed.pdf"
p12 = _normalize_filepath(certificate.path)
passwd = _normalize_filepath(certificate.password_file)
if not (p12 and passwd):
raise UserError(
_('Signing report (PDF): '
'Certificate or password file not found'))
signer_opts = '"%s" "%s" "%s" "%s"' % (p12, pdf, pdfsigned, passwd)
_(
"Signing report (PDF): "
"Certificate or password file not found"
)
)
signer_opts = '"{}" "{}" "{}" "{}"'.format(p12, pdf, pdfsigned, passwd)
signer = self._signer_bin(signer_opts)
process = subprocess.Popen(
signer, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
signer, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
out, err = process.communicate()
if process.returncode:
raise UserError(
_('Signing report (PDF): jPdfSign failed (error code: %s). '
'Message: %s. Output: %s') %
(process.returncode, err, out))
_(
"Signing report (PDF): jPdfSign failed (error code: %s). "
"Message: %s. Output: %s"
)
% (process.returncode, err, out)
)
return pdfsigned
@api.multi
@ -141,32 +158,36 @@ class IrActionsReport(models.Model):
if signed_content:
_logger.debug(
"The signed PDF document '%s/%s' was loaded from the "
"database", self.report_name, res_ids,
"database",
self.report_name,
res_ids,
)
return signed_content, "pdf"
content, ext = super(IrActionsReport, self).render_qweb_pdf(
res_ids, data
)
return signed_content, 'pdf'
content, ext = super(IrActionsReport, self).render_qweb_pdf(res_ids,
data)
if certificate:
# Creating temporary origin PDF
pdf_fd, pdf = tempfile.mkstemp(
suffix='.pdf', prefix='report.tmp.')
with closing(os.fdopen(pdf_fd, 'wb')) as pf:
pdf_fd, pdf = tempfile.mkstemp(suffix=".pdf", prefix="report.tmp.")
with closing(os.fdopen(pdf_fd, "wb")) as pf:
pf.write(content)
_logger.debug(
"Signing PDF document '%s' for IDs %s with certificate '%s'",
self.report_name, res_ids, certificate.name,
self.report_name,
res_ids,
certificate.name,
)
signed = self.pdf_sign(pdf, certificate)
# Read signed PDF
if os.path.exists(signed):
with open(signed, 'rb') as pf:
with open(signed, "rb") as pf:
content = pf.read()
# Manual cleanup of the temporary files
for fname in (pdf, signed):
try:
os.unlink(fname)
except (OSError, IOError):
_logger.error('Error when trying to remove file %s', fname)
_logger.error("Error when trying to remove file %s", fname)
if certificate.attachment:
self._attach_signed_write(res_ids, certificate, content)
return content, ext

View File

@ -5,38 +5,51 @@ from odoo import api, fields, models
class ReportCertificate(models.Model):
_name = 'report.certificate'
_description = 'Report Certificate'
_order = 'sequence,id'
_name = "report.certificate"
_description = "Report Certificate"
_order = "sequence,id"
@api.model
def _default_company(self):
m_company = self.env['res.company']
return m_company._company_default_get('report.certificate')
m_company = self.env["res.company"]
return m_company._company_default_get("report.certificate")
sequence = fields.Integer(default=10)
name = fields.Char(required=True)
path = fields.Char(
string="Certificate file path", required=True,
help="Path to PKCS#12 certificate file")
string="Certificate file path",
required=True,
help="Path to PKCS#12 certificate file",
)
password_file = fields.Char(
string="Password file path", required=True,
help="Path to certificate password file")
string="Password file path",
required=True,
help="Path to certificate password file",
)
model_id = fields.Many2one(
string="Model", required=True,
comodel_name='ir.model',
help="Model where apply this certificate")
string="Model",
required=True,
comodel_name="ir.model",
help="Model where apply this certificate",
)
domain = fields.Char(
string="Domain",
help="Domain for filtering if sign or not the document")
help="Domain for filtering if sign or not the document",
)
allow_only_one = fields.Boolean(
string="Allow only one document", default=True,
string="Allow only one document",
default=True,
help="If True, this certificate can not be useb to sign "
"a PDF from several documents.")
"a PDF from several documents.",
)
attachment = fields.Char(
string="Save as attachment",
help="Filename used to store signed document as attachment. "
"Keep empty to not save signed document.")
"Keep empty to not save signed document.",
)
company_id = fields.Many2one(
string='Company', comodel_name='res.company',
required=True, default=_default_company)
string="Company",
comodel_name="res.company",
required=True,
default=_default_company,
)

View File

@ -1,13 +1,14 @@
# Copyright 2015 Tecnativa - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
_inherit = "res.company"
report_certificate_ids = fields.One2many(
string="PDF report certificates",
comodel_name='report.certificate',
inverse_name='company_id')
comodel_name="report.certificate",
inverse_name="company_id",
)

View File

@ -7,21 +7,20 @@ from odoo.tests.common import HttpCase
class TestReportQwebSigner(HttpCase):
def setUp(self):
super(TestReportQwebSigner, self).setUp()
self.partner = self.env['res.partner'].create({
'name': 'Test partner',
'customer': True,
})
self.partner = self.env["res.partner"].create(
{"name": "Test partner", "customer": True}
)
self.report = self.env.ref(
'report_qweb_signer.partner_demo_report'
"report_qweb_signer.partner_demo_report"
).with_context(force_report_rendering=True)
def test_report_qweb_signer(self):
self.report.render_qweb_pdf(self.partner.ids)
# Reprint again for taking the PDF from attachment
IrAttachment = self.env['ir.attachment']
IrAttachment = self.env["ir.attachment"]
domain = [
('res_id', '=', self.partner.id),
('res_model', '=', self.partner._name),
("res_id", "=", self.partner.id),
("res_model", "=", self.partner._name),
]
num_attachments = IrAttachment.search_count(domain)
self.report.render_qweb_pdf(self.partner.ids)

View File

@ -1,65 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2015 Tecnativa - Antonio Espinosa
Copyright 2017 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_report_certificate_form" model="ir.ui.view">
<record id="view_report_certificate_form" model="ir.ui.view">
<field name="name">report.certificate.form</field>
<field name="model">report.certificate</field>
<field name="arch" type="xml">
<form string="PDF report certificate">
<sheet string="PDF report certificate">
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" />
</h1>
</div>
<group>
<group>
<field name="path"/>
<field name="password_file"/>
<field name="model_id"/>
<field name="path" />
<field name="password_file" />
<field name="model_id" />
</group>
<group>
<field name="domain"/>
<field name="allow_only_one"/>
<field name="attachment"/>
<field name="company_id" widget="selection"
groups="base.group_multi_company"/>
<field name="domain" />
<field name="allow_only_one" />
<field name="attachment" />
<field
name="company_id"
widget="selection"
groups="base.group_multi_company"
/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_report_certificate_tree" model="ir.ui.view" >
</record>
<record id="view_report_certificate_tree" model="ir.ui.view">
<field name="name">report.certificate.tree</field>
<field name="model">report.certificate</field>
<field name="arch" type="xml">
<tree string="PDF report certificates">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="path"/>
<field name="model_id"/>
<field name="domain"/>
<field name="company_id"/>
<field name="sequence" widget="handle" />
<field name="name" />
<field name="path" />
<field name="model_id" />
<field name="domain" />
<field name="company_id" />
</tree>
</field>
</record>
<record id="action_report_certificate" model="ir.actions.act_window">
</record>
<record id="action_report_certificate" model="ir.actions.act_window">
<field name="name">PDF certificates</field>
<field name="res_model">report.certificate</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_report_certificate"
</record>
<menuitem
id="menu_report_certificate"
name="PDF certificates"
parent="base.reporting_menuitem"
action="action_report_certificate"/>
action="action_report_certificate"
/>
</odoo>

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2015 Tecnativa - Antonio Espinosa
Copyright 2017 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_company_form" model="ir.ui.view">
<field name="name">Add PDF report certificates list</field>
<field name="inherit_id" ref="base.view_company_form" />
@ -13,8 +12,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
<field name="arch" type="xml">
<data>
<notebook position="inside">
<page name="pdf_sign_certificate" string="Certificates (PDF signing)">
<field name="report_certificate_ids"
<page
name="pdf_sign_certificate"
string="Certificates (PDF signing)"
>
<field
name="report_certificate_ids"
context="{'default_company_id': active_id}"
/>
</page>
@ -22,5 +25,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
</data>
</field>
</record>
</odoo>

View File

@ -0,0 +1 @@
../../../../report_qweb_signer

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)