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.

278 lines
11 KiB

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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. import os
  31. import subprocess
  32. import tempfile
  33. import logging
  34. from functools import partial
  35. from mako import exceptions
  36. from openerp.osv.osv import except_osv
  37. from openerp.tools.translate import _
  38. from openerp import addons
  39. from openerp import pooler
  40. from openerp import tools
  41. from openerp.addons.report_webkit import webkit_report
  42. from openerp.addons.report_webkit.report_helper import WebKitHelper
  43. _logger = logging.getLogger('financial.reports.webkit')
  44. # Class used only as a workaround to bug:
  45. # http://code.google.com/p/wkhtmltopdf/issues/detail?id=656
  46. # html headers and footers do not work on big files (hundreds of pages) so we
  47. # replace them by text headers and footers passed as arguments to wkhtmltopdf
  48. # this class has to be removed once the bug is fixed
  49. # in your report class, to print headers and footers as text, you have to add
  50. # them in the localcontext with a key 'additional_args'
  51. # for instance:
  52. # header_report_name = _('PARTNER LEDGER')
  53. # footer_date_time = self.formatLang(str(datetime.today()),
  54. # date_time=True)
  55. # self.localcontext.update({
  56. # 'additional_args': [
  57. # ('--header-font-name', 'Helvetica'),
  58. # ('--footer-font-name', 'Helvetica'),
  59. # ('--header-font-size', '10'),
  60. # ('--footer-font-size', '7'),
  61. # ('--header-left', header_report_name),
  62. # ('--footer-left', footer_date_time),
  63. # ('--footer-right', ' '.join((_('Page'), '[page]', _('of'),
  64. # '[topage]'))),
  65. # ('--footer-line',),
  66. # ],
  67. # })
  68. # redefine mako_template as this is overriden by jinja since saas-1
  69. # from openerp.addons.report_webkit.webkit_report import mako_template
  70. from mako.template import Template
  71. from mako.lookup import TemplateLookup
  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_osv(_('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. def create_single_pdf(self, cursor, uid, ids, data, report_xml,
  166. context=None):
  167. """generate the PDF"""
  168. if context is None:
  169. context = {}
  170. htmls = []
  171. if report_xml.report_type != 'webkit':
  172. return super(HeaderFooterTextWebKitParser, self
  173. ).create_single_pdf(cursor, uid, ids, data,
  174. report_xml, context=context)
  175. parser_instance = self.parser(cursor,
  176. uid,
  177. self.name2,
  178. context=context)
  179. self.pool = pooler.get_pool(cursor.dbname)
  180. objs = self.getObjects(cursor, uid, ids, context)
  181. parser_instance.set_context(objs, data, ids, report_xml.report_type)
  182. template = False
  183. if report_xml.report_file:
  184. path = addons.get_module_resource(
  185. *report_xml.report_file.split(os.path.sep))
  186. if os.path.exists(path):
  187. template = file(path).read()
  188. if not template and report_xml.report_webkit_data:
  189. template = report_xml.report_webkit_data
  190. if not template:
  191. raise except_osv(
  192. _('Error!'), _('Webkit Report template not found !'))
  193. header = report_xml.webkit_header.html
  194. if not header and report_xml.header:
  195. raise except_osv(
  196. _('No header defined for this Webkit report!'),
  197. _('Please set a header in company settings.')
  198. )
  199. css = report_xml.webkit_header.css
  200. if not css:
  201. css = ''
  202. translate_call = partial(self.translate_call, parser_instance)
  203. # default_filters=['unicode', 'entity'] can be used to set global
  204. # filter
  205. body_mako_tpl = mako_template(template)
  206. helper = WebKitHelper(cursor, uid, report_xml.id, context)
  207. if report_xml.precise_mode:
  208. for obj in objs:
  209. parser_instance.localcontext['objects'] = [obj]
  210. try:
  211. html = body_mako_tpl.render(helper=helper,
  212. css=css,
  213. _=translate_call,
  214. **parser_instance.localcontext)
  215. htmls.append(html)
  216. except Exception:
  217. msg = exceptions.text_error_template().render()
  218. _logger.error(msg)
  219. raise except_osv(_('Webkit render'), msg)
  220. else:
  221. try:
  222. html = body_mako_tpl.render(helper=helper,
  223. css=css,
  224. _=translate_call,
  225. **parser_instance.localcontext)
  226. htmls.append(html)
  227. except Exception:
  228. msg = exceptions.text_error_template().render()
  229. _logger.error(msg)
  230. raise except_osv(_('Webkit render'), msg)
  231. # NO html footer and header because we write them as text with
  232. # wkhtmltopdf
  233. head = foot = False
  234. if report_xml.webkit_debug:
  235. try:
  236. deb = body_mako_tpl.render(helper=helper,
  237. css=css,
  238. _debug=tools.ustr("\n".join(htmls)),
  239. _=translate_call,
  240. **parser_instance.localcontext)
  241. except Exception:
  242. msg = exceptions.text_error_template().render()
  243. _logger.error(msg)
  244. raise except_osv(_('Webkit render'), msg)
  245. return (deb, 'html')
  246. binary = self.get_lib(cursor, uid)
  247. pdf = self.generate_pdf(binary, report_xml, head, foot, htmls,
  248. parser_instance=parser_instance)
  249. return (pdf, 'pdf')