diff --git a/.travis.yml b/.travis.yml index 75e8bc4c..030df2db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,13 @@ sudo: false cache: pip addons: + sources: + - pov-wkhtmltopdf apt: packages: - expect-dev # provides unbuffer utility - python-lxml # because pip installation is slow + - wkhtmltopdf # report tests language: python @@ -26,6 +29,10 @@ env: virtualenv: system_site_packages: true +before_install: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + install: - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} diff --git a/report_qweb_pdf_watermark/README.rst b/report_qweb_pdf_watermark/README.rst new file mode 100644 index 00000000..35c63a05 --- /dev/null +++ b/report_qweb_pdf_watermark/README.rst @@ -0,0 +1,62 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============= +Pdf watermark +============= + +This module was written to add watermarks (backgrounds) to PDF reports. + +This is necessary because of the way wkhtmltopdf handles headers and footers, in the current versions if quite impossible to have a background for the complete page. + +Usage +===== + +To use this module, you need to: + +#. go to your report +#. select a PDF or image to use as watermark. Note that resolutions and size must match, otherwise you'll have funny results +#. advanced users (members of group technical settings) can also fill in an expression that returns the data (base64 encoded) to be used as watermark + +.. 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 + +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. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Holger Brunn + +Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list `_ or the `appropriate specialized mailinglist `_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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 https://odoo-community.org. diff --git a/report_qweb_pdf_watermark/__init__.py b/report_qweb_pdf_watermark/__init__.py new file mode 100644 index 00000000..7eda98a2 --- /dev/null +++ b/report_qweb_pdf_watermark/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/report_qweb_pdf_watermark/__openerp__.py b/report_qweb_pdf_watermark/__openerp__.py new file mode 100644 index 00000000..74ccecf9 --- /dev/null +++ b/report_qweb_pdf_watermark/__openerp__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Pdf watermark", + "version": "9.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Reporting", + "summary": "Add watermarks to your QWEB PDF reports", + "depends": [ + 'report', + ], + "data": [ + "demo/report.xml", + "views/ir_actions_report_xml.xml", + ], +} diff --git a/report_qweb_pdf_watermark/demo/report.xml b/report_qweb_pdf_watermark/demo/report.xml new file mode 100644 index 00000000..c25210ba --- /dev/null +++ b/report_qweb_pdf_watermark/demo/report.xml @@ -0,0 +1,30 @@ + + + + + + docs[:1].company_id.logo + + + + diff --git a/report_qweb_pdf_watermark/models/__init__.py b/report_qweb_pdf_watermark/models/__init__.py new file mode 100644 index 00000000..e9e37ae5 --- /dev/null +++ b/report_qweb_pdf_watermark/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import ir_actions_report_xml +from . import report diff --git a/report_qweb_pdf_watermark/models/ir_actions_report_xml.py b/report_qweb_pdf_watermark/models/ir_actions_report_xml.py new file mode 100644 index 00000000..d703c028 --- /dev/null +++ b/report_qweb_pdf_watermark/models/ir_actions_report_xml.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models + + +class IrActionsReportXml(models.Model): + _inherit = 'ir.actions.report.xml' + + pdf_watermark = fields.Binary('Watermark') + pdf_watermark_expression = fields.Char( + 'Watermark expression', help='An expression yielding the base64 ' + 'encoded data to be used as watermark. \n' + 'You have access to variables `env` and `docs`') diff --git a/report_qweb_pdf_watermark/models/report.py b/report_qweb_pdf_watermark/models/report.py new file mode 100644 index 00000000..acb9aff6 --- /dev/null +++ b/report_qweb_pdf_watermark/models/report.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from base64 import b64decode +from logging import getLogger +from pyPdf import PdfFileWriter, PdfFileReader +from pyPdf.utils import PdfReadError +from PIL import Image +from StringIO import StringIO +from openerp import api, models, tools +logger = getLogger(__name__) + + +class Report(models.Model): + _inherit = 'report' + + @api.v7 + def get_pdf( + self, cr, uid, ids, report_name, html=None, data=None, context=None + ): + # pylint: disable=R8110 + # this override cannot be done in v8 api + result = super(Report, self).get_pdf( + cr, uid, ids, report_name, html=html, data=data, + context=context + ) + report = self._get_report_from_name(cr, uid, report_name) + watermark = None + if report.pdf_watermark: + watermark = b64decode(report.pdf_watermark) + else: + env = api.Environment(cr, uid, context) + watermark = tools.safe_eval( + report.pdf_watermark_expression or 'None', + dict(env=env, docs=env[report.model].browse(ids)), + ) + if watermark: + watermark = b64decode(watermark) + + if not watermark: + return result + + pdf = PdfFileWriter() + pdf_watermark = None + try: + pdf_watermark = PdfFileReader(StringIO(watermark)) + except PdfReadError: + # let's see if we can convert this with pillow + try: + image = Image.open(StringIO(watermark)) + pdf_buffer = StringIO() + if image.mode != 'RGB': + image = image.convert('RGB') + resolution = image.info.get( + 'dpi', report.paperformat_id.dpi or 90 + ) + if isinstance(resolution, tuple): + resolution = resolution[0] + image.save(pdf_buffer, 'pdf', resolution=resolution) + pdf_watermark = PdfFileReader(pdf_buffer) + except: + logger.exception('Failed to load watermark') + + if not pdf_watermark: + logger.error( + 'No usable watermark found, got %s...', watermark[:100] + ) + return result + + if pdf_watermark.numPages < 1: + logger.error('Your watermark pdf does not contain any pages') + return result + if pdf_watermark.numPages > 1: + logger.debug('Your watermark pdf contains more than one page, ' + 'all but the first one will be ignored') + + for page in PdfFileReader(StringIO(result)).pages: + watermark_page = pdf.addBlankPage( + page.mediaBox.getWidth(), page.mediaBox.getHeight() + ) + watermark_page.mergePage(pdf_watermark.getPage(0)) + watermark_page.mergePage(page) + + pdf_content = StringIO() + pdf.write(pdf_content) + + return pdf_content.getvalue() diff --git a/report_qweb_pdf_watermark/static/description/icon.png b/report_qweb_pdf_watermark/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/report_qweb_pdf_watermark/static/description/icon.png differ diff --git a/report_qweb_pdf_watermark/tests/__init__.py b/report_qweb_pdf_watermark/tests/__init__.py new file mode 100644 index 00000000..e4637a64 --- /dev/null +++ b/report_qweb_pdf_watermark/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_report_qweb_pdf_watermark diff --git a/report_qweb_pdf_watermark/tests/test_report_qweb_pdf_watermark.py b/report_qweb_pdf_watermark/tests/test_report_qweb_pdf_watermark.py new file mode 100644 index 00000000..7525a95a --- /dev/null +++ b/report_qweb_pdf_watermark/tests/test_report_qweb_pdf_watermark.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# © 2016 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.tests.common import TransactionCase + + +class TestReportQwebPdfWatermark(TransactionCase): + def test_report_qweb_pdf_watermark(self): + # with our image, we have three + self._test_report_images(3) + + self.env.ref('report_qweb_pdf_watermark.demo_report').write({ + 'pdf_watermark_expression': False, + }) + # without, we have two + self._test_report_images(2) + + self.env.ref('report_qweb_pdf_watermark.demo_report').write({ + 'pdf_watermark': self.env.user.company_id.logo, + }) + # and now we should have three again + self._test_report_images(3) + + def _test_report_images(self, number): + pdf = self.registry['report'].get_pdf( + self.cr, + self.uid, + self.env['res.users'].search([]).ids, + 'report_qweb_pdf_watermark.demo_report_view', + context={} + ) + self.assertEqual(pdf.count('/Subtype /Image'), number) diff --git a/report_qweb_pdf_watermark/views/ir_actions_report_xml.xml b/report_qweb_pdf_watermark/views/ir_actions_report_xml.xml new file mode 100644 index 00000000..a9b59502 --- /dev/null +++ b/report_qweb_pdf_watermark/views/ir_actions_report_xml.xml @@ -0,0 +1,15 @@ + + + + + ir.actions.report.xml + + + + + + + + + +