diff --git a/report_qweb_signer/README.rst b/report_qweb_signer/README.rst index 5a0c94c3..bd69d6b5 100644 --- a/report_qweb_signer/README.rst +++ b/report_qweb_signer/README.rst @@ -50,17 +50,15 @@ but signed PDF is automatically downloaded if this document model is configured as indicated above. If 'Save as attachment' is configured, signed PDF is saved as attachment and -next time saved one is downloaded without signing again. This is appropiate when -signing date is important, for example, when signing customer invoices. +next time saved one is downloaded without signing again. This is appropiate +when signing date is important, for example, when signing customer invoices. + +You can try the signing with the demo report that is included for customers +called "Test PDF certificate". .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/143/8.0 - -For further information, please visit: - -* https://www.odoo.com/forum/help-1 - + :target: https://runbot.odoo-community.org/runbot/143/10.0 Known issues / Roadmap ====================== @@ -69,6 +67,7 @@ Known issues / Roadmap then 'Save as attachment' is not applied and signed result is not saved as attachment. * To have a visible signature through an image embedded in the resulting PDF. +* Add tests. Bug Tracker @@ -76,8 +75,8 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed feedback -`here `_. +If you spotted it first, help us smashing it by providing a detailed and +welcomed feedback `here `_. Credits @@ -98,8 +97,9 @@ Icon Contributors ------------ -* Rafael Blasco -* Antonio Espinosa +* Rafael Blasco +* Antonio Espinosa +* Pedro M. Baeza Maintainer ---------- @@ -114,4 +114,4 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit http://odoo-community.org. +To contribute to this module, please visit https://odoo-community.org. diff --git a/report_qweb_signer/__init__.py b/report_qweb_signer/__init__.py index ba025733..a77a6fcb 100644 --- a/report_qweb_signer/__init__.py +++ b/report_qweb_signer/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models diff --git a/report_qweb_signer/__openerp__.py b/report_qweb_signer/__manifest__.py similarity index 68% rename from report_qweb_signer/__openerp__.py rename to report_qweb_signer/__manifest__.py index cb388620..246892ba 100644 --- a/report_qweb_signer/__openerp__.py +++ b/report_qweb_signer/__manifest__.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa +# Copyright 2015 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Qweb PDF reports signer", "summary": "Sign Qweb PDFs usign a PKCS#12 certificate", - "version": "8.0.1.0.0", + "version": "10.0.1.0.0", "category": "Reporting", - "website": "http://www.antiun.com", - "author": "Antiun Ingeniería S.L., " + "website": "https://www.tecnativa.com", + "author": "Tecnativa, " "Odoo Community Association (OCA)", "license": "AGPL-3", - "application": False, "installable": True, "depends": [ "report", @@ -25,7 +25,7 @@ "views/res_company_view.xml", ], "demo": [ - "demo/report_partner.xml", - "demo/report_certificate.xml", + "demo/report_partner_demo.xml", + "demo/report_certificate_demo.xml", ], } diff --git a/report_qweb_signer/demo/report_certificate.xml b/report_qweb_signer/demo/report_certificate.xml deleted file mode 100644 index fba19870..00000000 --- a/report_qweb_signer/demo/report_certificate.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - Test OCA certificate - test.p12 - test.passwd - - [('customer', '=', True)] - - 'test_' + (object.name or '').replace(' ', '_').lower() + '.signed.pdf' - - - - diff --git a/report_qweb_signer/demo/report_certificate_demo.xml b/report_qweb_signer/demo/report_certificate_demo.xml new file mode 100644 index 00000000..266be891 --- /dev/null +++ b/report_qweb_signer/demo/report_certificate_demo.xml @@ -0,0 +1,20 @@ + + + + + + + Test OCA certificate + test.p12 + test.passwd + + [('customer', '=', True)] + + 'test_' + (object.name or '').replace(' ', '_').lower() + '.signed.pdf' + + + diff --git a/report_qweb_signer/demo/report_partner.xml b/report_qweb_signer/demo/report_partner.xml deleted file mode 100644 index 90733b7a..00000000 --- a/report_qweb_signer/demo/report_partner.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - diff --git a/report_qweb_signer/demo/report_partner_demo.xml b/report_qweb_signer/demo/report_partner_demo.xml new file mode 100644 index 00000000..01744c9a --- /dev/null +++ b/report_qweb_signer/demo/report_partner_demo.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + diff --git a/report_qweb_signer/i18n/es.po b/report_qweb_signer/i18n/es.po index 7a7d0fe2..e1cdc7c6 100644 --- a/report_qweb_signer/i18n/es.po +++ b/report_qweb_signer/i18n/es.po @@ -168,9 +168,9 @@ msgstr "Salvar como adjunto" #: code:addons/report_qweb_signer/models/report.py:119 #, python-format msgid "" -"Saving signed report (PDF): You do not have enought access rights to save " +"Saving signed report (PDF): You do not have enough access rights to save " "attachments" -msgstr "Guardar informe firmado (PDF): no tienes suficientes permisos de acceso para guardar adjuntos" +msgstr "Guardar informe firmado (PDF): no tiene suficientes permisos de acceso para guardar adjuntos" #. module: report_qweb_signer #: field:report.certificate,sequence:0 diff --git a/report_qweb_signer/models/report.py b/report_qweb_signer/models/report.py index 8245cd39..e990bbb0 100644 --- a/report_qweb_signer/models/report.py +++ b/report_qweb_signer/models/report.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa +# Copyright 2015 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import base64 @@ -9,9 +10,9 @@ import subprocess import tempfile import time -from openerp import models, api, _ -from openerp.exceptions import Warning, AccessError -from openerp.tools.safe_eval import safe_eval +from odoo import models, api, _ +from odoo.exceptions import UserError, AccessError +from odoo.tools.safe_eval import safe_eval import logging _logger = logging.getLogger(__name__) @@ -30,99 +31,80 @@ def _normalize_filepath(path): class Report(models.Model): _inherit = 'report' - def _certificate_get(self, cr, uid, ids, report, context=None): + def _certificate_get(self, report, docids): + """Obtain the proper certificate for the report and the conditions.""" if report.report_type != 'qweb-pdf': - _logger.info( - "Can only sign qweb-pdf reports, this one is '%s' type", - report.report_type) return False - m_cert = self.pool['report.certificate'] - company_id = self.pool['res.users']._get_company(cr, uid) - certificate_ids = m_cert.search(cr, uid, [ - ('company_id', '=', company_id), - ('model_id', '=', report.model)], context=context) - if not certificate_ids: - _logger.info( - "No PDF certificate found for report '%s'", - report.report_name) + certificates = self.env['report.certificate'].search([ + ('company_id', '=', self.env.user.company_id.id), + ('model_id', '=', report.model), + ]) + if not certificates: return False - for cert in m_cert.browse(cr, uid, certificate_ids, context=context): + for cert in certificates: # Check allow only one document - if cert.allow_only_one and len(ids) > 1: - _logger.info( + if cert.allow_only_one and len(self) > 1: + _logger.debug( "Certificate '%s' allows only one document, " "but printing %d documents", - cert.name, len(ids)) + cert.name, len(docids)) continue # Check domain if cert.domain: - m_model = self.pool[cert.model_id.model] - domain = [('id', 'in', tuple(ids))] + domain = [('id', 'in', tuple(docids))] domain = domain + safe_eval(cert.domain) - doc_ids = m_model.search(cr, uid, domain, context=context) - if not doc_ids: - _logger.info( + docs = self.env[cert.model_id.model].search(domain) + if not docs: + _logger.debug( "Certificate '%s' domain not satisfied", cert.name) continue # Certificate match! return cert return False - def _attach_filename_get(self, cr, uid, ids, certificate, context=None): - if len(ids) != 1: + def _attach_filename_get(self, docids, certificate): + if len(docids) != 1: return False - obj = self.pool[certificate.model_id.model].browse(cr, uid, ids[0]) - filename = safe_eval(certificate.attachment, { - 'object': obj, + doc = self.env[certificate.model_id.model].browse(docids[0]) + return safe_eval(certificate.attachment, { + 'object': doc, 'time': time }) - return filename - def _attach_signed_read(self, cr, uid, ids, certificate, context=None): - if len(ids) != 1: + def _attach_signed_read(self, docids, certificate): + if len(docids) != 1: return False - filename = self._attach_filename_get( - cr, uid, ids, certificate, context=context) + filename = self._attach_filename_get(docids, certificate) if not filename: return False - signed = False - m_attachment = self.pool['ir.attachment'] - attach_ids = m_attachment.search(cr, uid, [ + attachment = self.env['ir.attachment'].search([ ('datas_fname', '=', filename), ('res_model', '=', certificate.model_id.model), - ('res_id', '=', ids[0]) - ]) - if attach_ids: - signed = m_attachment.browse(cr, uid, attach_ids[0]).datas - signed = base64.decodestring(signed) - return signed + ('res_id', '=', docids[0]), + ], limit=1) + if attachment: + return base64.decodestring(attachment.datas) + return False - def _attach_signed_write(self, cr, uid, ids, certificate, signed, - context=None): - if len(ids) != 1: + def _attach_signed_write(self, docids, certificate, signed): + if len(docids) != 1: return False - filename = self._attach_filename_get( - cr, uid, ids, certificate, context=context) + filename = self._attach_filename_get(docids, certificate) if not filename: return False - m_attachment = self.pool['ir.attachment'] try: - attach_id = m_attachment.create(cr, uid, { + attachment = self.env['ir.attachment'].create({ 'name': filename, 'datas': base64.encodestring(signed), 'datas_fname': filename, 'res_model': certificate.model_id.model, - 'res_id': ids[0], + 'res_id': docids[0], }) except AccessError: - raise Warning( + raise UserError( _('Saving signed report (PDF): ' - 'You do not have enought access rights to save attachments')) - else: - _logger.info( - "The signed PDF document '%s' is now saved in the database", - filename) - return attach_id + 'You do not have enough access rights to save attachments')) + return attachment def _signer_bin(self, opts): me = os.path.dirname(__file__) @@ -135,7 +117,7 @@ class Report(models.Model): p12 = _normalize_filepath(certificate.path) passwd = _normalize_filepath(certificate.password_file) if not (p12 and passwd): - raise Warning( + raise UserError( _('Signing report (PDF): ' 'Certificate or password file not found')) signer_opts = '"%s" "%s" "%s" "%s"' % (p12, pdf, pdfsigned, passwd) @@ -144,38 +126,37 @@ class Report(models.Model): signer, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) out, err = process.communicate() if process.returncode: - raise Warning( + raise UserError( _('Signing report (PDF): jPdfSign failed (error code: %s). ' 'Message: %s. Output: %s') % (process.returncode, err, out)) return pdfsigned - @api.v7 - def get_pdf(self, cr, uid, ids, report_name, html=None, data=None, - context=None): - signed_content = False - report = self._get_report_from_name(cr, uid, report_name) - certificate = self._certificate_get( - cr, uid, ids, report, context=context) + @api.model + def get_pdf(self, docids, report_name, html=None, data=None): + report = self._get_report_from_name(report_name) + certificate = self._certificate_get(report, docids) if certificate and certificate.attachment: - signed_content = self._attach_signed_read( - cr, uid, ids, certificate, context=context) + signed_content = self._attach_signed_read(docids, certificate) if signed_content: - _logger.info("The signed PDF document '%s/%s' was loaded from " - "the database", report_name, ids) + _logger.debug( + "The signed PDF document '%s/%s' was loaded from the " + "database", report_name, docids, + ) return signed_content content = super(Report, self).get_pdf( - cr, uid, ids, report_name, html=html, data=data, - context=context) + docids, report_name, html=html, data=data, + ) if certificate: # Creating temporary origin PDF pdf_fd, pdf = tempfile.mkstemp( suffix='.pdf', prefix='report.tmp.') with closing(os.fdopen(pdf_fd, 'w')) as pf: pf.write(content) - _logger.info( - "Signing PDF document '%s/%s' with certificate '%s'", - report_name, ids, certificate.name) + _logger.debug( + "Signing PDF document '%s' for IDs %s with certificate '%s'", + report_name, docids, certificate.name, + ) signed = self.pdf_sign(pdf, certificate) # Read signed PDF if os.path.exists(signed): @@ -188,6 +169,5 @@ class Report(models.Model): except (OSError, IOError): _logger.error('Error when trying to remove file %s', fname) if certificate.attachment: - self._attach_signed_write( - cr, uid, ids, certificate, content, context=context) + self._attach_signed_write(docids, certificate, content) return content diff --git a/report_qweb_signer/models/report_certificate.py b/report_qweb_signer/models/report_certificate.py index 5bbb5290..c2f000ba 100644 --- a/report_qweb_signer/models/report_certificate.py +++ b/report_qweb_signer/models/report_certificate.py @@ -2,7 +2,7 @@ # © 2015 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ReportCertificate(models.Model): diff --git a/report_qweb_signer/models/res_company.py b/report_qweb_signer/models/res_company.py index 5809175d..a71825f6 100644 --- a/report_qweb_signer/models/res_company.py +++ b/report_qweb_signer/models/res_company.py @@ -2,7 +2,7 @@ # © 2015 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields +from odoo import models, fields class ResCompany(models.Model): diff --git a/report_qweb_signer/tests/__init__.py b/report_qweb_signer/tests/__init__.py new file mode 100644 index 00000000..97f37fe5 --- /dev/null +++ b/report_qweb_signer/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_report_qweb_signer diff --git a/report_qweb_signer/tests/test_report_qweb_signer.py b/report_qweb_signer/tests/test_report_qweb_signer.py new file mode 100644 index 00000000..e285161d --- /dev/null +++ b/report_qweb_signer/tests/test_report_qweb_signer.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests import common + + +@common.at_install(False) +@common.post_install(True) +class TestReportQwebSigner(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestReportQwebSigner, cls).setUpClass() + cls.partner = cls.env['res.partner'].create({ + 'name': 'Test partner', + 'customer': True, + }) + cls.report = cls.env.ref('report_qweb_signer.partner_demo_report') + + def test_report_qweb_signer(self): + self.env['report'].get_pdf( + self.partner.ids, self.report.report_name, data={}, + ) + # Reprint again for taking the PDF from attachment + self.env['report'].get_pdf( + self.partner.ids, self.report.report_name, data={}, + ) diff --git a/report_qweb_signer/views/report_certificate_view.xml b/report_qweb_signer/views/report_certificate_view.xml index 219059cc..cdc73168 100644 --- a/report_qweb_signer/views/report_certificate_view.xml +++ b/report_qweb_signer/views/report_certificate_view.xml @@ -1,10 +1,10 @@ - - + report.certificate.form @@ -62,5 +62,4 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). parent="report.reporting_menuitem" action="action_report_certificate"/> - - + diff --git a/report_qweb_signer/views/res_company_view.xml b/report_qweb_signer/views/res_company_view.xml index cd8cac9e..983c2491 100644 --- a/report_qweb_signer/views/res_company_view.xml +++ b/report_qweb_signer/views/res_company_view.xml @@ -1,26 +1,26 @@ - - + - - Add PDF report certificates list - - res.company - - - - - - - - - - + + Add PDF report certificates list + + res.company + + + + + + + + + + - - +