OCA reporting engine fork for dev and update.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
6.5 KiB

  1. # Copyright 2015 Tecnativa - Antonio Espinosa
  2. # Copyright 2017 Tecnativa - Pedro M. Baeza
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import base64
  5. from contextlib import closing
  6. import os
  7. import subprocess
  8. import tempfile
  9. import time
  10. from odoo import models, api, _
  11. from odoo.exceptions import UserError, AccessError
  12. from odoo.tools.safe_eval import safe_eval
  13. import logging
  14. _logger = logging.getLogger(__name__)
  15. def _normalize_filepath(path):
  16. path = path or ''
  17. path = path.strip()
  18. if not os.path.isabs(path):
  19. me = os.path.dirname(__file__)
  20. path = '{}/../static/certificate/'.format(me) + path
  21. path = os.path.normpath(path)
  22. return path if os.path.exists(path) else False
  23. class IrActionsReport(models.Model):
  24. _inherit = 'ir.actions.report'
  25. def _certificate_get(self, res_ids):
  26. """Obtain the proper certificate for the report and the conditions."""
  27. if self.report_type != 'qweb-pdf':
  28. return False
  29. certificates = self.env['report.certificate'].search([
  30. ('company_id', '=', self.env.user.company_id.id),
  31. ('model_id', '=', self.model),
  32. ])
  33. if not certificates:
  34. return False
  35. for cert in certificates:
  36. # Check allow only one document
  37. if cert.allow_only_one and len(self) > 1:
  38. _logger.debug(
  39. "Certificate '%s' allows only one document, "
  40. "but printing %d documents",
  41. cert.name, len(res_ids))
  42. continue
  43. # Check domain
  44. if cert.domain:
  45. domain = [('id', 'in', tuple(res_ids))]
  46. domain = domain + safe_eval(cert.domain)
  47. docs = self.env[cert.model_id.model].search(domain)
  48. if not docs:
  49. _logger.debug(
  50. "Certificate '%s' domain not satisfied", cert.name)
  51. continue
  52. # Certificate match!
  53. return cert
  54. return False
  55. def _attach_filename_get(self, res_ids, certificate):
  56. if len(res_ids) != 1:
  57. return False
  58. doc = self.env[certificate.model_id.model].browse(res_ids[0])
  59. return safe_eval(certificate.attachment, {
  60. 'object': doc,
  61. 'time': time
  62. })
  63. def _attach_signed_read(self, res_ids, certificate):
  64. if len(res_ids) != 1:
  65. return False
  66. filename = self._attach_filename_get(res_ids, certificate)
  67. if not filename:
  68. return False
  69. attachment = self.env['ir.attachment'].search([
  70. ('datas_fname', '=', filename),
  71. ('res_model', '=', certificate.model_id.model),
  72. ('res_id', '=', res_ids[0]),
  73. ], limit=1)
  74. if attachment:
  75. return base64.decodestring(attachment.datas)
  76. return False
  77. def _attach_signed_write(self, res_ids, certificate, signed):
  78. if len(res_ids) != 1:
  79. return False
  80. filename = self._attach_filename_get(res_ids, certificate)
  81. if not filename:
  82. return False
  83. try:
  84. attachment = self.env['ir.attachment'].create({
  85. 'name': filename,
  86. 'datas': base64.encodestring(signed),
  87. 'datas_fname': filename,
  88. 'res_model': certificate.model_id.model,
  89. 'res_id': res_ids[0],
  90. })
  91. except AccessError:
  92. raise UserError(
  93. _('Saving signed report (PDF): '
  94. 'You do not have enough access rights to save attachments'))
  95. return attachment
  96. def _signer_bin(self, opts):
  97. me = os.path.dirname(__file__)
  98. irc_param = self.env['ir.config_parameter'].sudo()
  99. java_bin = 'java -jar'
  100. java_param = irc_param.get_param('report_qweb_signer.java_parameters')
  101. jar = '{}/../static/jar/jPdfSign.jar'.format(me)
  102. return '%s %s %s %s' % (java_bin, java_param, jar, opts)
  103. def pdf_sign(self, pdf, certificate):
  104. pdfsigned = pdf + '.signed.pdf'
  105. p12 = _normalize_filepath(certificate.path)
  106. passwd = _normalize_filepath(certificate.password_file)
  107. if not (p12 and passwd):
  108. raise UserError(
  109. _('Signing report (PDF): '
  110. 'Certificate or password file not found'))
  111. signer_opts = '"%s" "%s" "%s" "%s"' % (p12, pdf, pdfsigned, passwd)
  112. signer = self._signer_bin(signer_opts)
  113. process = subprocess.Popen(
  114. signer, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  115. out, err = process.communicate()
  116. if process.returncode:
  117. raise UserError(
  118. _('Signing report (PDF): jPdfSign failed (error code: %s). '
  119. 'Message: %s. Output: %s') %
  120. (process.returncode, err, out))
  121. return pdfsigned
  122. @api.multi
  123. def render_qweb_pdf(self, res_ids=None, data=None):
  124. certificate = self._certificate_get(res_ids)
  125. if certificate and certificate.attachment:
  126. signed_content = self._attach_signed_read(res_ids, certificate)
  127. if signed_content:
  128. _logger.debug(
  129. "The signed PDF document '%s/%s' was loaded from the "
  130. "database", self.report_name, res_ids,
  131. )
  132. return signed_content, 'pdf'
  133. content, ext = super(IrActionsReport, self).render_qweb_pdf(res_ids,
  134. data)
  135. if certificate:
  136. # Creating temporary origin PDF
  137. pdf_fd, pdf = tempfile.mkstemp(
  138. suffix='.pdf', prefix='report.tmp.')
  139. with closing(os.fdopen(pdf_fd, 'wb')) as pf:
  140. pf.write(content)
  141. _logger.debug(
  142. "Signing PDF document '%s' for IDs %s with certificate '%s'",
  143. self.report_name, res_ids, certificate.name,
  144. )
  145. signed = self.pdf_sign(pdf, certificate)
  146. # Read signed PDF
  147. if os.path.exists(signed):
  148. with open(signed, 'rb') as pf:
  149. content = pf.read()
  150. # Manual cleanup of the temporary files
  151. for fname in (pdf, signed):
  152. try:
  153. os.unlink(fname)
  154. except (OSError, IOError):
  155. _logger.error('Error when trying to remove file %s', fname)
  156. if certificate.attachment:
  157. self._attach_signed_write(res_ids, certificate, content)
  158. return content, ext