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.

280 lines
11 KiB

7 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
7 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Copyright (c) 2011 Camptocamp SA (http://www.camptocamp.com)
  5. #
  6. # Author: Guewen Baconnier (Camptocamp)
  7. #
  8. # WARNING: This program as such is intended to be used by professional
  9. # programmers who take the whole responsability of assessing all potential
  10. # consequences resulting from its eventual inadequacies and bugs
  11. # End users who are looking for a ready-to-use solution with commercial
  12. # garantees and support are strongly adviced to contract a Free Software
  13. # Service Company
  14. #
  15. # This program is Free Software; you can redistribute it and/or
  16. # modify it under the terms of the GNU General Public License
  17. # as published by the Free Software Foundation; either version 2
  18. # of the License, or (at your option) any later version.
  19. #
  20. # This program is distributed in the hope that it will be useful,
  21. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. # GNU General Public License for more details.
  24. #
  25. # You should have received a copy of the GNU General Public License
  26. # along with this program; if not, write to the Free Software
  27. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  28. #
  29. ##############################################################################
  30. from mako.template import Template
  31. from mako.lookup import TemplateLookup
  32. import os
  33. import subprocess
  34. import tempfile
  35. import logging
  36. from functools import partial
  37. from mako import exceptions
  38. from openerp.exceptions import except_orm
  39. from openerp.tools.translate import _
  40. from openerp.modules.registry import RegistryManager
  41. from openerp import tools
  42. from openerp.addons.report_webkit import webkit_report
  43. from openerp.addons.report_webkit.report_helper import WebKitHelper
  44. from openerp.modules.module import get_module_resource
  45. _logger = logging.getLogger('financial.reports.webkit')
  46. # Class used only as a workaround to bug:
  47. # http://code.google.com/p/wkhtmltopdf/issues/detail?id=656
  48. # html headers and footers do not work on big files (hundreds of pages) so we
  49. # replace them by text headers and footers passed as arguments to wkhtmltopdf
  50. # this class has to be removed once the bug is fixed
  51. # in your report class, to print headers and footers as text, you have to add
  52. # them in the localcontext with a key 'additional_args'
  53. # for instance:
  54. # header_report_name = _('PARTNER LEDGER')
  55. # footer_date_time = self.formatLang(str(datetime.today()),
  56. # date_time=True)
  57. # self.localcontext.update({
  58. # 'additional_args': [
  59. # ('--header-font-name', 'Helvetica'),
  60. # ('--footer-font-name', 'Helvetica'),
  61. # ('--header-font-size', '10'),
  62. # ('--footer-font-size', '7'),
  63. # ('--header-left', header_report_name),
  64. # ('--footer-left', footer_date_time),
  65. # ('--footer-right', ' '.join((_('Page'), '[page]', _('of'),
  66. # '[topage]'))),
  67. # ('--footer-line',),
  68. # ],
  69. # })
  70. # redefine mako_template as this is overriden by jinja since saas-1
  71. # from openerp.addons.report_webkit.webkit_report import mako_template
  72. def mako_template(text):
  73. """Build a Mako template.
  74. This template uses UTF-8 encoding
  75. """
  76. tmp_lookup = TemplateLookup(
  77. ) # we need it in order to allow inclusion and inheritance
  78. return Template(text, input_encoding='utf-8', output_encoding='utf-8',
  79. lookup=tmp_lookup)
  80. class HeaderFooterTextWebKitParser(webkit_report.WebKitParser):
  81. def generate_pdf(self, comm_path, report_xml, header, footer, html_list,
  82. webkit_header=False, parser_instance=False):
  83. """Call webkit in order to generate pdf"""
  84. if not webkit_header:
  85. webkit_header = report_xml.webkit_header
  86. fd, out_filename = tempfile.mkstemp(suffix=".pdf",
  87. prefix="webkit.tmp.")
  88. file_to_del = [out_filename]
  89. if comm_path:
  90. command = [comm_path]
  91. else:
  92. command = ['wkhtmltopdf']
  93. command.append('--quiet')
  94. # default to UTF-8 encoding. Use <meta charset="latin-1"> to override.
  95. command.extend(['--encoding', 'utf-8'])
  96. if webkit_header.margin_top:
  97. command.extend(
  98. ['--margin-top',
  99. str(webkit_header.margin_top).replace(',', '.')])
  100. if webkit_header.margin_bottom:
  101. command.extend(
  102. ['--margin-bottom',
  103. str(webkit_header.margin_bottom).replace(',', '.')])
  104. if webkit_header.margin_left:
  105. command.extend(
  106. ['--margin-left',
  107. str(webkit_header.margin_left).replace(',', '.')])
  108. if webkit_header.margin_right:
  109. command.extend(
  110. ['--margin-right',
  111. str(webkit_header.margin_right).replace(',', '.')])
  112. if webkit_header.orientation:
  113. command.extend(
  114. ['--orientation',
  115. str(webkit_header.orientation).replace(',', '.')])
  116. if webkit_header.format:
  117. command.extend(
  118. ['--page-size',
  119. str(webkit_header.format).replace(',', '.')])
  120. if parser_instance.localcontext.get('additional_args', False):
  121. for arg in parser_instance.localcontext['additional_args']:
  122. command.extend(arg)
  123. count = 0
  124. for html in html_list:
  125. with tempfile.NamedTemporaryFile(suffix="%d.body.html" % count,
  126. delete=False) as html_file:
  127. count += 1
  128. html_file.write(self._sanitize_html(html))
  129. file_to_del.append(html_file.name)
  130. command.append(html_file.name)
  131. command.append(out_filename)
  132. stderr_fd, stderr_path = tempfile.mkstemp(text=True)
  133. file_to_del.append(stderr_path)
  134. try:
  135. status = subprocess.call(command, stderr=stderr_fd)
  136. os.close(stderr_fd) # ensure flush before reading
  137. stderr_fd = None # avoid closing again in finally block
  138. fobj = open(stderr_path, 'r')
  139. error_message = fobj.read()
  140. fobj.close()
  141. if not error_message:
  142. error_message = _('No diagnosis message was provided')
  143. else:
  144. error_message = _(
  145. 'The following diagnosis message was provided:\n') + \
  146. error_message
  147. if status:
  148. raise except_orm(_('Webkit error'),
  149. _("The command 'wkhtmltopdf' failed with \
  150. error code = %s. Message: %s") %
  151. (status, error_message))
  152. with open(out_filename, 'rb') as pdf_file:
  153. pdf = pdf_file.read()
  154. os.close(fd)
  155. finally:
  156. if stderr_fd is not None:
  157. os.close(stderr_fd)
  158. for f_to_del in file_to_del:
  159. try:
  160. os.unlink(f_to_del)
  161. except (OSError, IOError), exc:
  162. _logger.error('cannot remove file %s: %s', f_to_del, exc)
  163. return pdf
  164. # override needed to keep the attachments' storing procedure
  165. # pylint: disable=old-api7-method-defined
  166. def create_single_pdf(self, cursor, uid, ids, data, report_xml,
  167. context=None):
  168. """generate the PDF"""
  169. if context is None:
  170. context = {}
  171. htmls = []
  172. if report_xml.report_type != 'webkit':
  173. return super(HeaderFooterTextWebKitParser, self
  174. ).create_single_pdf(cursor, uid, ids, data,
  175. report_xml, context=context)
  176. parser_instance = self.parser(cursor,
  177. uid,
  178. self.name2,
  179. context=context)
  180. self.pool = RegistryManager.get(cursor.dbname)
  181. objs = self.getObjects(cursor, uid, ids, context)
  182. parser_instance.set_context(objs, data, ids, report_xml.report_type)
  183. template = False
  184. if report_xml.report_file:
  185. path = get_module_resource(
  186. *report_xml.report_file.split('/'))
  187. if os.path.exists(path):
  188. template = file(path).read()
  189. if not template and report_xml.report_webkit_data:
  190. template = report_xml.report_webkit_data
  191. if not template:
  192. raise except_orm(
  193. _('Error!'), _('Webkit Report template not found !'))
  194. header = report_xml.webkit_header.html
  195. if not header and report_xml.header:
  196. raise except_orm(
  197. _('No header defined for this Webkit report!'),
  198. _('Please set a header in company settings.')
  199. )
  200. css = report_xml.webkit_header.css
  201. if not css:
  202. css = ''
  203. translate_call = partial(self.translate_call, parser_instance)
  204. # default_filters=['unicode', 'entity'] can be used to set global
  205. # filter
  206. body_mako_tpl = mako_template(template)
  207. helper = WebKitHelper(cursor, uid, report_xml.id, context)
  208. if report_xml.precise_mode:
  209. for obj in objs:
  210. parser_instance.localcontext['objects'] = [obj]
  211. try:
  212. html = body_mako_tpl.render(helper=helper,
  213. css=css,
  214. _=translate_call,
  215. **parser_instance.localcontext)
  216. htmls.append(html)
  217. except Exception:
  218. msg = exceptions.text_error_template().render()
  219. _logger.error(msg)
  220. raise except_orm(_('Webkit render'), msg)
  221. else:
  222. try:
  223. html = body_mako_tpl.render(helper=helper,
  224. css=css,
  225. _=translate_call,
  226. **parser_instance.localcontext)
  227. htmls.append(html)
  228. except Exception:
  229. msg = exceptions.text_error_template().render()
  230. _logger.error(msg)
  231. raise except_orm(_('Webkit render'), msg)
  232. # NO html footer and header because we write them as text with
  233. # wkhtmltopdf
  234. head = foot = False
  235. if report_xml.webkit_debug:
  236. try:
  237. deb = body_mako_tpl.render(helper=helper,
  238. css=css,
  239. _debug=tools.ustr("\n".join(htmls)),
  240. _=translate_call,
  241. **parser_instance.localcontext)
  242. except Exception:
  243. msg = exceptions.text_error_template().render()
  244. _logger.error(msg)
  245. raise except_orm(_('Webkit render'), msg)
  246. return (deb, 'html')
  247. bin = self.get_lib(cursor, uid)
  248. pdf = self.generate_pdf(bin, report_xml, head, foot, htmls,
  249. parser_instance=parser_instance)
  250. return (pdf, 'pdf')