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.

206 lines
8.6 KiB

  1. # Copyright 2016 ACSONE SA/NV
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).).
  3. import base64
  4. from base64 import b64decode
  5. import mock
  6. import os
  7. import pkg_resources
  8. import shutil
  9. import tempfile
  10. from contextlib import contextmanager
  11. from odoo import tools
  12. from odoo.tests.common import TransactionCase
  13. from odoo.exceptions import ValidationError
  14. from odoo.addons.base.tests.test_mimetypes import PNG
  15. from ..models.py3o_report import TemplateNotFound
  16. from ..models._py3o_parser_context import format_multiline_value
  17. from base64 import b64encode
  18. import logging
  19. logger = logging.getLogger(__name__)
  20. try:
  21. from genshi.core import Markup
  22. except ImportError:
  23. logger.debug('Cannot import genshi.core')
  24. @contextmanager
  25. def temporary_copy(path):
  26. filname, ext = os.path.splitext(path)
  27. tmp_filename = tempfile.mktemp(suffix='.' + ext)
  28. try:
  29. shutil.copy2(path, tmp_filename)
  30. yield tmp_filename
  31. finally:
  32. os.unlink(tmp_filename)
  33. class TestReportPy3o(TransactionCase):
  34. def setUp(self):
  35. super(TestReportPy3o, self).setUp()
  36. self.env.user.image = PNG
  37. self.report = self.env.ref("report_py3o.res_users_report_py3o")
  38. self.py3o_report = self.env['py3o.report'].create({
  39. 'ir_actions_report_id': self.report.id})
  40. def test_required_py3_filetype(self):
  41. self.assertEqual(self.report.report_type, "py3o")
  42. with self.assertRaises(ValidationError) as e:
  43. self.report.py3o_filetype = False
  44. self.assertEqual(
  45. e.exception.name,
  46. "Field 'Output Format' is required for Py3O report")
  47. def _render_patched(self, result_text='test result', call_count=1):
  48. py3o_report = self.env['py3o.report']
  49. py3o_report_obj = py3o_report.create({
  50. "ir_actions_report_id": self.report.id
  51. })
  52. with mock.patch.object(
  53. py3o_report.__class__, '_create_single_report') as patched_pdf:
  54. result = tempfile.mktemp('.txt')
  55. with open(result, 'w') as fp:
  56. fp.write(result_text)
  57. patched_pdf.side_effect = lambda record, data:\
  58. py3o_report_obj._postprocess_report(
  59. record, result
  60. ) or result
  61. # test the call the the create method inside our custom parser
  62. self.report.render_report(self.env.user.ids,
  63. self.report.report_name,
  64. {})
  65. self.assertEqual(call_count, patched_pdf.call_count)
  66. # generated files no more exists
  67. self.assertFalse(os.path.exists(result))
  68. def test_reports(self):
  69. res = self.report.render_report(
  70. self.env.user.ids, self.report.report_name, {})
  71. self.assertTrue(res)
  72. self.report.py3o_filetype = 'pdf'
  73. res = self.report.render_report(
  74. self.env.user.ids, self.report.report_name, {})
  75. self.assertTrue(res)
  76. def test_report_load_from_attachment(self):
  77. self.report.write({"attachment_use": True,
  78. "attachment": "'my_saved_report'"})
  79. attachments = self.env['ir.attachment'].search([])
  80. self._render_patched()
  81. new_attachments = self.env['ir.attachment'].search([])
  82. created_attachement = new_attachments - attachments
  83. self.assertEqual(1, len(created_attachement))
  84. content = b64decode(created_attachement.datas)
  85. self.assertEqual(b"test result", content)
  86. # put a new content into tha attachement and check that the next
  87. # time we ask the report we received the saved attachment not a newly
  88. # generated document
  89. created_attachement.datas = base64.encodestring(b"new content")
  90. res = self.report.render_report(
  91. self.env.user.ids, self.report.report_name, {})
  92. self.assertEqual((b'new content', self.report.py3o_filetype), res)
  93. def test_report_post_process(self):
  94. """
  95. By default the post_process method is in charge to save the
  96. generated report into an ir.attachment if requested.
  97. """
  98. self.report.attachment = "object.name + '.txt'"
  99. ir_attachment = self.env['ir.attachment']
  100. attachements = ir_attachment.search([(1, '=', 1)])
  101. self._render_patched()
  102. attachements = ir_attachment.search([(1, '=', 1)]) - attachements
  103. self.assertEqual(1, len(attachements.ids))
  104. self.assertEqual(self.env.user.name + '.txt', attachements.name)
  105. self.assertEqual(self.env.user._name, attachements.res_model)
  106. self.assertEqual(self.env.user.id, attachements.res_id)
  107. self.assertEqual(b'test result', b64decode(attachements.datas))
  108. @tools.misc.mute_logger('odoo.addons.report_py3o.models.py3o_report')
  109. def test_report_template_configs(self):
  110. # the demo template is specified with a relative path in in the module
  111. # path
  112. tmpl_name = self.report.py3o_template_fallback
  113. flbk_filename = pkg_resources.resource_filename(
  114. "odoo.addons.%s" % self.report.module,
  115. tmpl_name)
  116. self.assertTrue(os.path.exists(flbk_filename))
  117. res = self.report.render_report(
  118. self.env.user.ids, self.report.report_name, {})
  119. self.assertTrue(res)
  120. # The generation fails if the template is not found
  121. self.report.module = False
  122. with self.assertRaises(TemplateNotFound), self.env.cr.savepoint():
  123. self.report.render_report(
  124. self.env.user.ids, self.report.report_name, {})
  125. # the template can also be provided as an abspath if it's root path
  126. # is trusted
  127. self.report.py3o_template_fallback = flbk_filename
  128. with self.assertRaises(TemplateNotFound):
  129. self.report.render_report(
  130. self.env.user.ids, self.report.report_name, {})
  131. with temporary_copy(flbk_filename) as tmp_filename:
  132. self.report.py3o_template_fallback = tmp_filename
  133. tools.config.misc['report_py3o'] = {
  134. 'root_tmpl_path': os.path.dirname(tmp_filename)}
  135. res = self.report.render_report(
  136. self.env.user.ids, self.report.report_name, {})
  137. self.assertTrue(res)
  138. # the tempalte can also be provided as a binary field
  139. self.report.py3o_template_fallback = False
  140. with open(flbk_filename, 'rb') as tmpl_file:
  141. tmpl_data = b64encode(tmpl_file.read())
  142. py3o_template = self.env['py3o.template'].create({
  143. 'name': 'test_template',
  144. 'py3o_template_data': tmpl_data,
  145. 'filetype': 'odt'})
  146. self.report.py3o_template_id = py3o_template
  147. self.report.py3o_template_fallback = flbk_filename
  148. res = self.report.render_report(
  149. self.env.user.ids, self.report.report_name, {})
  150. self.assertTrue(res)
  151. @tools.misc.mute_logger('odoo.addons.report_py3o.models.py3o_report')
  152. def test_report_template_fallback_validity(self):
  153. tmpl_name = self.report.py3o_template_fallback
  154. flbk_filename = pkg_resources.resource_filename(
  155. "odoo.addons.%s" % self.report.module,
  156. tmpl_name)
  157. # an exising file in a native format is a valid template if it's
  158. self.assertTrue(self.py3o_report._get_template_from_path(
  159. tmpl_name))
  160. self.report.module = None
  161. # a directory is not a valid template..
  162. self.assertFalse(self.py3o_report._get_template_from_path('/etc/'))
  163. self.assertFalse(self.py3o_report._get_template_from_path('.'))
  164. # an vaild template outside the root_tmpl_path is not a valid template
  165. # path
  166. # located in trusted directory
  167. self.report.py3o_template_fallback = flbk_filename
  168. self.assertFalse(self.py3o_report._get_template_from_path(
  169. flbk_filename))
  170. with temporary_copy(flbk_filename) as tmp_filename:
  171. self.assertTrue(self.py3o_report._get_template_from_path(
  172. tmp_filename))
  173. # check security
  174. self.assertFalse(self.py3o_report._get_template_from_path(
  175. 'rm -rf . & %s' % flbk_filename))
  176. # a file in a non native LibreOffice format is not a valid template
  177. with tempfile.NamedTemporaryFile(suffix='.toto')as f:
  178. self.assertFalse(self.py3o_report._get_template_from_path(
  179. f.name))
  180. # non exising files are not valid template
  181. self.assertFalse(self.py3o_report._get_template_from_path(
  182. '/etc/test.odt'))
  183. def test_escape_html_characters_format_multiline_value(self):
  184. self.assertEqual(Markup('&lt;&gt;<text:line-break/>&amp;test;'),
  185. format_multiline_value('<>\n&test;'))