[RFR] split off the fusion server to its own module
use libreoffice for conversions in the base version. Fixes #179pull/479/head
parent
4c1b8037b7
commit
c44c33dfb5
|
@ -0,0 +1,127 @@
|
|||
.. 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
|
||||
|
||||
==========================================
|
||||
Py3o Report Engine - Fusion server support
|
||||
==========================================
|
||||
|
||||
This addons was written to let a fusion server handle format conversion instead of local libreoffice.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Install several additional components and Python libs:
|
||||
|
||||
* `Py3o Fusion server <https://bitbucket.org/faide/py3o.fusion>`_,
|
||||
* `Py3o render server <https://bitbucket.org/faide/py3o.renderserver>`_,
|
||||
* a Java Runtime Environment (JRE), which can be OpenJDK,
|
||||
* Libreoffice started in the background in headless mode,
|
||||
* the Java driver for Libreoffice (Juno).
|
||||
|
||||
It is also possible to use the Python driver for Libreoffice (PyUNO), but it is recommended to use the Java driver because it is more stable.
|
||||
|
||||
The installation procedure below uses the Java driver. It has been successfully tested on Ubuntu 16.04 LTS ; if you use another OS, you may have to change a few details.
|
||||
|
||||
Installation of py3o.fusion:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install py3o.fusion
|
||||
pip install service-identity
|
||||
|
||||
Installation of py3o.renderserver:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install py3o.renderserver
|
||||
|
||||
Installation of Libreoffice and JRE on Debian/Ubuntu:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo apt-get install default-jre ure libreoffice-java-common libreoffice-writer
|
||||
|
||||
You may have to install additionnal fonts. For example, to have the special unicode symbols for phone/fax/email in the PDF reports generated by Py3o, you should install the following package:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo apt-get install fonts-symbola
|
||||
|
||||
At the end, with the dependencies, you should have the following py3o python libs:
|
||||
|
||||
.. code::
|
||||
|
||||
% pip freeze | grep py3o
|
||||
py3o.formats==0.3
|
||||
py3o.fusion==0.8.6
|
||||
py3o.renderclient==0.2
|
||||
py3o.renderers.juno==0.8
|
||||
py3o.renderserver==0.5.1
|
||||
py3o.template==0.9.11
|
||||
py3o.types==0.1.1
|
||||
|
||||
Start the Py3o Fusion server:
|
||||
|
||||
.. code::
|
||||
|
||||
start-py3o-fusion --debug -s localhost
|
||||
|
||||
Start the Py3o render server:
|
||||
|
||||
.. code::
|
||||
|
||||
start-py3o-renderserver --java=/usr/lib/jvm/default-java/jre/lib/amd64/server/libjvm.so --ure=/usr/share --office=/usr/lib/libreoffice --driver=juno --sofficeport=8997
|
||||
|
||||
On the output of the Py3o render server, the first line looks like:
|
||||
|
||||
.. code::
|
||||
|
||||
DEBUG:root:Starting JVM: /usr/lib/jvm/default-java/jre/lib/amd64/server/libjvm.so with options: -Djava.class.path=/usr/local/lib/python2.7/dist-packages/py3o/renderers/juno/py3oconverter.jar:/usr/share/java/juh.jar:/usr/share/java/jurt.jar:/usr/share/java/ridl.jar:/usr/share/java/unoloader.jar:/usr/share/java/java_uno.jar:/usr/lib/libreoffice/program/classes/unoil.jar -Xmx150M
|
||||
|
||||
After **-Djava.class.path**, there is a list of Java libs with *.jar* extension ; check that each JAR file is really present on your filesystem. If one of the jar files is present in another directory, create a symlink that points to the real location of the file. If all the jar files are present on another directory, adapt the *--ure=* argument on the command line of Py3o render server.
|
||||
|
||||
To check that the Py3o Fusion server is running fine, visit the URL http://<IP_address>:8765/form. On this web page, under the section *Target format*, make sure that you have a line *This server currently supports these formats: ods, odt, docx, doc, html, docbook, pdf, xls.*.
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* none yet
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/reporting-engine/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
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Florent Aide (`XCG Consulting <http://odoo.consulting/>`_)
|
||||
* Laurent Mignon <laurent.mignon@acsone.eu>,
|
||||
* Alexis de Lattre <alexis.delattre@akretion.com>,
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
* Omar Castiñeira <omar@comunitea.com>
|
||||
* Holger Brunn <hbrunn@therp.nl>
|
||||
|
||||
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ 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.
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from . import models
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
'name': 'Py3o Report Engine - Fusion server support',
|
||||
'summary': 'Let the fusion server handle format conversion.',
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Reporting',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'XCG Consulting,'
|
||||
'ACSONE SA/NV,'
|
||||
'Odoo Community Association (OCA)',
|
||||
'website': 'https://github.com/OCA/reporting-engine',
|
||||
'depends': ['report_py3o'],
|
||||
'external_dependencies': {
|
||||
'python': [
|
||||
'py3o.template',
|
||||
'py3o.formats',
|
||||
],
|
||||
},
|
||||
'demo': [
|
||||
"demo/report_py3o.xml",
|
||||
],
|
||||
'data': [
|
||||
"views/ir_report.xml",
|
||||
'security/ir.model.access.csv',
|
||||
'views/py3o_server.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="report_py3o.res_users_report_py3o" model="ir.actions.report.xml">
|
||||
<field name="py3o_is_local_fusion" eval="1"/>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from . import ir_actions_report_xml
|
||||
from . import py3o_report
|
||||
from . import py3o_server
|
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2013 XCG Consulting <http://odoo.consulting>
|
||||
# © 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import logging
|
||||
from openerp import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from py3o.formats import Formats
|
||||
except ImportError:
|
||||
logger.debug('Cannot import py3o.formats')
|
||||
|
||||
|
||||
class IrActionsReportXml(models.Model):
|
||||
_inherit = 'ir.actions.report.xml'
|
||||
|
||||
@api.multi
|
||||
@api.constrains("py3o_is_local_fusion", "py3o_server_id", "py3o_filetype")
|
||||
def _check_py3o_server_id(self):
|
||||
for report in self:
|
||||
if report.report_type != "py3o":
|
||||
continue
|
||||
is_native = Formats().get_format(report.py3o_filetype).native
|
||||
if ((not is_native or not report.py3o_is_local_fusion) and
|
||||
not report.py3o_server_id):
|
||||
raise ValidationError(_(
|
||||
"Can not use not native format in local fusion. "
|
||||
"Please specify a Fusion Server"))
|
||||
|
||||
py3o_is_local_fusion = fields.Boolean(
|
||||
"Local Fusion",
|
||||
help="Native formats will be processed without a server. "
|
||||
"You must use this mode if you call methods on your model into "
|
||||
"the template.",
|
||||
default=True)
|
||||
py3o_server_id = fields.Many2one(
|
||||
"py3o.server",
|
||||
"Fusion Server")
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2013 XCG Consulting <http://odoo.consulting>
|
||||
# © 2016 ACSONE SA/NV
|
||||
# © 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import tempfile
|
||||
from contextlib import closing
|
||||
from openerp import _, api, models
|
||||
from openerp.exceptions import UserError
|
||||
from StringIO import StringIO
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from py3o.template import Template
|
||||
from py3o.template.helpers import Py3oConvertor
|
||||
except ImportError:
|
||||
logger.debug('Cannot import py3o.template')
|
||||
|
||||
|
||||
class Py3oReport(models.TransientModel):
|
||||
_inherit = 'py3o.report'
|
||||
|
||||
@api.multi
|
||||
def _create_single_report(self, model_instance, data, save_in_attachment):
|
||||
""" This function to generate our py3o report
|
||||
"""
|
||||
self.ensure_one()
|
||||
report_xml = self.ir_actions_report_xml_id
|
||||
filetype = report_xml.py3o_filetype
|
||||
if report_xml.py3o_is_local_fusion:
|
||||
result_path = super(Py3oReport, self)._create_single_report(
|
||||
model_instance, data, save_in_attachment,
|
||||
)
|
||||
with closing(open(result_path, 'r')) as out_stream:
|
||||
tmpl_data = out_stream.read()
|
||||
datadict = {}
|
||||
else:
|
||||
result_fd, result_path = tempfile.mkstemp(
|
||||
suffix='.' + filetype, prefix='p3o.report.tmp.')
|
||||
tmpl_data = self.get_template(model_instance)
|
||||
|
||||
in_stream = StringIO(tmpl_data)
|
||||
with closing(os.fdopen(result_fd, 'w+')) as out_stream:
|
||||
template = Template(in_stream, out_stream, escape_false=True)
|
||||
localcontext = self._get_parser_context(model_instance, data)
|
||||
expressions = template.get_all_user_python_expression()
|
||||
py_expression = template.convert_py3o_to_python_ast(
|
||||
expressions)
|
||||
convertor = Py3oConvertor()
|
||||
data_struct = convertor(py_expression)
|
||||
datadict = data_struct.render(localcontext)
|
||||
|
||||
# Call py3o.server to render the template in the desired format
|
||||
files = {
|
||||
'tmpl_file': tmpl_data,
|
||||
}
|
||||
fields = {
|
||||
"targetformat": filetype,
|
||||
"datadict": json.dumps(datadict),
|
||||
"image_mapping": "{}",
|
||||
"escape_false": "on",
|
||||
}
|
||||
if report_xml.py3o_is_local_fusion:
|
||||
fields['skipfusion'] = '1'
|
||||
r = requests.post(
|
||||
report_xml.py3o_server_id.url, data=fields, files=files)
|
||||
if r.status_code != 200:
|
||||
# server says we have an issue... let's tell that to enduser
|
||||
raise UserError(
|
||||
_('Fusion server error %s') % r.text,
|
||||
)
|
||||
|
||||
chunk_size = 1024
|
||||
with open(result_path, 'w+') as fd:
|
||||
for chunk in r.iter_content(chunk_size):
|
||||
fd.write(chunk)
|
||||
if len(model_instance) == 1:
|
||||
self._postprocess_report(
|
||||
result_path, model_instance.id, save_in_attachment)
|
||||
return result_path
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 XCG Consulting (http://odoo.consulting)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Py3oServer(models.Model):
|
||||
_name = 'py3o.server'
|
||||
_rec_name = 'url'
|
||||
|
||||
url = fields.Char(
|
||||
"Py3o Fusion Server URL", required=True,
|
||||
help="If your Py3o Fusion server is on the same machine and runs "
|
||||
"on the default port, the URL is http://localhost:8765/form")
|
||||
is_active = fields.Boolean("Active", default=True)
|
|
@ -0,0 +1,3 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
access_py3o_server_admin,access_py3o_server_admin,model_py3o_server,base.group_no_one,1,1,1,1
|
||||
access_py3o_server_user,access_py3o_server_user,model_py3o_server,base.group_user,1,0,0,0
|
|
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
from . import test_report_py3o_fusion_server
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Therp BV <http://therp.nl>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import mock
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.report_py3o.tests import test_report_py3o
|
||||
|
||||
|
||||
@mock.patch(
|
||||
'requests.post', mock.Mock(
|
||||
return_value=mock.Mock(
|
||||
status_code=200,
|
||||
iter_content=mock.Mock(return_value=['test_result']),
|
||||
)
|
||||
)
|
||||
)
|
||||
class TestReportPy3oFusionServer(test_report_py3o.TestReportPy3o):
|
||||
def setUp(self):
|
||||
super(TestReportPy3oFusionServer, self).setUp()
|
||||
py3o_server = self.env['py3o.server'].create({"url": "http://dummy"})
|
||||
# check the call to the fusion server
|
||||
self.report.write({
|
||||
"py3o_server_id": py3o_server.id,
|
||||
"py3o_filetype": 'pdf',
|
||||
})
|
||||
|
||||
def test_no_local_fusion_without_fusion_server(self):
|
||||
self.assertTrue(self.report.py3o_is_local_fusion)
|
||||
with self.assertRaises(ValidationError) as e:
|
||||
self.report.write({"py3o_server_id": None})
|
||||
self.assertEqual(
|
||||
e.exception.name,
|
||||
"Can not use not native format in local fusion. "
|
||||
"Please specify a Fusion Server")
|
||||
|
||||
def test_reports_no_local_fusion(self):
|
||||
self.report.py3o_is_local_fusion = False
|
||||
self.test_reports()
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="view_ir_actions_report_base" model="ir.ui.view">
|
||||
<field name="model">ir.actions.report.xml</field>
|
||||
<field name="inherit_id" ref="report_py3o.py3o_report_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="py3o_multi_in_one" position="after">
|
||||
<field name="py3o_is_local_fusion"/>
|
||||
<field name="py3o_server_id" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="py3o_server_configuration_form_view" model="ir.ui.view">
|
||||
<field name="name">py3o.server.configuration.form.view</field>
|
||||
<field name="model">py3o.server</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Py3o Server Configuration">
|
||||
<group name="main">
|
||||
<field name="url" widget="url"/>
|
||||
<field name="is_active" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="py3o_server_configuration_tree_view" model="ir.ui.view">
|
||||
<field name="name">py3o.server.configuration.tree.view</field>
|
||||
<field name="model">py3o.server</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Py3o Servers Configuration">
|
||||
<field name="url" />
|
||||
<field name="is_active" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="py3o_server_configuration_action" model="ir.actions.act_window">
|
||||
<field name="name">Py3o Servers</field>
|
||||
<field name="res_model">py3o.server</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="py3o_server_configuration_menu"
|
||||
parent="report_py3o.py3o_config_menu"
|
||||
action="py3o_server_configuration_action" />
|
||||
|
||||
</odoo>
|
Loading…
Reference in New Issue