diff --git a/CHANGELOG b/CHANGELOG
index dfaba16016..7e33dc5ca0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+* Use unoconv for report format conversion
+* Allow to use any Opendocument as report template
* Drop NetRPC and activate JSON-RPC by default
* Remove ir.action.wizard_size
* Rename expand and fill attributes into yexpand and yfill
diff --git a/doc/topics/install.rst b/doc/topics/install.rst
index ed496ac66a..dfa3832437 100644
--- a/doc/topics/install.rst
+++ b/doc/topics/install.rst
@@ -17,7 +17,7 @@ Prerequisites
* Optional: pywebdav 0.9.3 or later (http://code.google.com/p/pywebdav/)
* Optional: pydot (http://code.google.com/p/pydot/)
* Optional: pytz (http://pytz.sourceforge.net/)
- * Optional: openoffice-python (http://pypi.python.org/pypi/openoffice-python/)
+ * Optional: unoconv http://dag.wieers.com/home-made/unoconv/)
* Optional: sphinx (http://sphinx.pocoo.org/)
Install Tryton
diff --git a/doc/topics/reports/index.rst b/doc/topics/reports/index.rst
index 96a70b11ad..7adeab0f2a 100644
--- a/doc/topics/reports/index.rst
+++ b/doc/topics/reports/index.rst
@@ -74,6 +74,8 @@ When defining an `ir.action.report` the following attributes are available:
example, my_module/my_style.odt. If no style is to be used this field
will be blank.
+ * ``template_extension``: The template format.
+
Report Usage
============
@@ -211,6 +213,7 @@ Then you must activate the new invoice report that exists in your new module:
account.invoice
my_module/invoice.odt
module_name/header_A4.odt
+ odt
Passing custom data to a report
diff --git a/etc/trytond.conf b/etc/trytond.conf
index f1d439b4ab..85dc4cf004 100644
--- a/etc/trytond.conf
+++ b/etc/trytond.conf
@@ -87,3 +87,6 @@
# Enable cron
# cron = True
+
+# unoconv connection
+#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
diff --git a/setup.py b/setup.py
index e21a377c6b..004c7b1dc8 100644
--- a/setup.py
+++ b/setup.py
@@ -63,7 +63,7 @@ setup(name=PACKAGE,
'PostgreSQL': ['psycopg2 >= 2.0'],
'MySQL': ['MySQL-python'],
'WebDAV': ['PyWebDAV >= 0.9.3'],
- 'PDF': ['openoffice-python'],
+ 'unoconv': ['unoconv'],
'SSL': ['pyOpenSSL'],
'graphviz': ['pydot'],
'timezone': ['pytz'],
diff --git a/trytond/config.py b/trytond/config.py
index 131e2c94de..04068f0a0c 100644
--- a/trytond/config.py
+++ b/trytond/config.py
@@ -49,6 +49,7 @@ class ConfigManager(object):
'init': {},
'update': {},
'cron': True,
+ 'unoconv': 'pipe,name=trytond;urp;StarOffice.ComponentContext',
}
self.configfile = None
diff --git a/trytond/ir/action.py b/trytond/ir/action.py
index 8be92988f6..9198b1be24 100644
--- a/trytond/ir/action.py
+++ b/trytond/ir/action.py
@@ -226,10 +226,76 @@ class ActionReport(ModelSQL, ModelView):
style_content = fields.Function(fields.Binary('Style'),
'get_style_content')
direct_print = fields.Boolean('Direct Print')
- extension = fields.Selection(
- [('odt', 'ODT Document'),
- ('pdf', 'PDF Document'),],
- string='Extension', required=True)
+ template_extension = fields.Selection([
+ ('odt', 'OpenDocument Text'),
+ ('odp', 'OpenDocument Presentation'),
+ ('ods', 'OpenDocument Spreadsheet'),
+ ('odg', 'OpenDocument Graphics'),
+ ], string='Template Extension', required=True)
+ extension = fields.Selection([
+ ('', ''),
+ ('bib', 'BibTex'),
+ ('bmp', 'Windows Bitmap'),
+ ('csv', 'Text CSV'),
+ ('dbf', 'dBase'),
+ ('dif', 'Data Interchange Format'),
+ ('doc', 'Microsoft Word 97/2000/XP'),
+ ('doc6', 'Microsoft Word 6.0'),
+ ('doc95', 'Microsoft Word 95'),
+ ('docbook', 'DocBook'),
+ ('emf', 'Enhanced Metafile'),
+ ('eps', 'Encapsulated PostScript'),
+ ('gif', 'Graphics Interchange Format'),
+ ('html', 'HTML Document'),
+ ('jpg', 'Joint Photographic Experts Group'),
+ ('met', 'OS/2 Metafile'),
+ ('ooxml', 'Microsoft Office Open XML'),
+ ('pbm', 'Portable Bitmap'),
+ ('pct', 'Mac Pict'),
+ ('pdb', 'AportisDoc (Palm)'),
+ ('pdf', 'Portable Document Format'),
+ ('pgm', 'Portable Graymap'),
+ ('png', 'Portable Network Graphic'),
+ ('ppm', 'Portable Pixelmap'),
+ ('ppt', 'Microsoft PowerPoint 97/2000/XP'),
+ ('psw', 'Pocket Word'),
+ ('pwp', 'PlaceWare'),
+ ('pxl', 'Pocket Excel'),
+ ('ras', 'Sun Raster Image'),
+ ('rtf', 'Rich Text Format'),
+ ('latex', 'LaTeX 2e'),
+ ('sda', 'StarDraw 5.0 (OpenOffice.org Impress)'),
+ ('sdc', 'StarCalc 5.0'),
+ ('sdc4', 'StarCalc 4.0'),
+ ('sdc3', 'StarCalc 3.0'),
+ ('sdd', 'StarImpress 5.0'),
+ ('sdd3', 'StarDraw 3.0 (OpenOffice.org Impress)'),
+ ('sdd4', 'StarImpress 4.0'),
+ ('sdw', 'StarWriter 5.0'),
+ ('sdw4', 'StarWriter 4.0'),
+ ('sdw3', 'StarWriter 3.0'),
+ ('slk', 'SYLK'),
+ ('svg', 'Scalable Vector Graphics'),
+ ('svm', 'StarView Metafile'),
+ ('swf', 'Macromedia Flash (SWF)'),
+ ('sxc', 'OpenOffice.org 1.0 Spreadsheet'),
+ ('sxi', 'OpenOffice.org 1.0 Presentation'),
+ ('sxd', 'OpenOffice.org 1.0 Drawing'),
+ ('sxd3', 'StarDraw 3.0'),
+ ('sxd5', 'StarDraw 5.0'),
+ ('sxw', 'Open Office.org 1.0 Text Document'),
+ ('text', 'Text Encoded'),
+ ('tiff', 'Tagged Image File Format'),
+ ('txt', 'Plain Text'),
+ ('wmf', 'Windows Metafile'),
+ ('xhtml', 'XHTML Document'),
+ ('xls', 'Microsoft Excel 97/2000/XP'),
+ ('xls5', 'Microsoft Excel 5.0'),
+ ('xls95', 'Microsoft Excel 95'),
+ ('xpm', 'X PixMap'),
+ ],
+ string='Extension', help='Leave empty for the same as template, '
+ 'see unoconv documentation for compatible format')
module = fields.Char('Module', readonly=True, select=1)
email = fields.Char('Email')
pyson_email = fields.Function(fields.Char('PySON Email'), 'get_pyson')
@@ -269,6 +335,12 @@ class ActionReport(ModelSQL, ModelView):
TableHandler.dropTable(cursor, 'ir.action.report.outputformat',
'ir_action_report_outputformat')
+ # Migrate from 2.0 remove required on extension
+ table.not_null_action('extension', action='remove')
+ cursor.execute('UPDATE "' + self._table + '" '
+ 'SET extension = %s '
+ 'WHERE extension = %s', ('', 'odt'))
+
def default_type(self):
return 'ir.action.report'
@@ -278,9 +350,12 @@ class ActionReport(ModelSQL, ModelView):
def default_direct_print(self):
return False
- def default_extension(self):
+ def default_template_extension(self):
return 'odt'
+ def default_extension(self):
+ return ''
+
def default_module(self):
return Transaction().context.get('module') or ''
diff --git a/trytond/ir/action.xml b/trytond/ir/action.xml
index 08a28fbad6..9a5318655b 100644
--- a/trytond/ir/action.xml
+++ b/trytond/ir/action.xml
@@ -92,9 +92,11 @@ this repository contains the full copyright notices and license terms. -->
-
+
+
+
diff --git a/trytond/report/report.py b/trytond/report/report.py
index d25cb3da98..1dfe2258d0 100644
--- a/trytond/report/report.py
+++ b/trytond/report/report.py
@@ -1,8 +1,5 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
-import copy
-import xml
-import sys
import base64
try:
import cStringIO as StringIO
@@ -13,11 +10,10 @@ import time
import os
import datetime
from base64 import decodestring
-import traceback
import inspect
-import logging
import tempfile
import warnings
+import subprocess
warnings.simplefilter("ignore")
import relatorio.reporting
warnings.resetwarnings()
@@ -28,11 +24,36 @@ except ImportError:
from genshi.filters import Translator
import lxml.etree
from trytond.config import CONFIG
-from trytond.backend import DatabaseIntegrityError
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.url import URLMixin
+MIMETYPES = {
+ 'odt': 'application/vnd.oasis.opendocument.text',
+ 'odp': 'application/vnd.oasis.opendocument.presentation',
+ 'ods': 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odg': 'application/vnd.oasis.opendocument.graphics',
+ }
+FORMAT2EXT = {
+ 'doc6': 'doc',
+ 'doc95': 'doc',
+ 'docbook': 'xml',
+ 'ooxml': 'xml',
+ 'latex': 'ltx',
+ 'sdc4': 'sdc',
+ 'sdc3': 'sdc',
+ 'sdd3': 'sdd',
+ 'sdd4': 'sdd',
+ 'sdw4': 'sdw',
+ 'sdw3': 'sdw',
+ 'sxd3': 'sxd',
+ 'sxd5': 'sxd',
+ 'text': 'txt',
+ 'xhtml': 'html',
+ 'xls5': 'xls',
+ 'xls95': 'xls',
+ }
+
class ReportFactory:
@@ -150,7 +171,9 @@ class Report(URLMixin):
if not report.report_content:
raise Exception('Error', 'Missing report file!')
- fd, path = tempfile.mkstemp(suffix='.odt', prefix='trytond')
+ fd, path = tempfile.mkstemp(
+ suffix=(os.extsep + report.template_extension),
+ prefix='trytond_')
outzip = zipfile.ZipFile(path, mode='w')
content_io = StringIO.StringIO()
@@ -220,7 +243,8 @@ class Report(URLMixin):
# Since Genshi >= 0.6, Translator requires a function type
translator = Translator(lambda text: translate(text))
- rel_report = relatorio.reporting.Report(path, 'application/vnd.oasis.opendocument.text',
+ mimetype = MIMETYPES[report.template_extension]
+ rel_report = relatorio.reporting.Report(path, mimetype,
ReportFactory(), relatorio.reporting.MIMETemplateLoader())
rel_report.filters.insert(0, translator)
#convert unicode key into str
@@ -236,61 +260,31 @@ class Report(URLMixin):
data = data.getvalue()
os.close(fd)
os.remove(path)
- output_format = report.extension
- if output_format == 'pdf':
- data = self.convert_pdf(data)
- return (output_format, data)
+ output_format = report.extension or report.template_extension
+ if output_format not in MIMETYPES:
+ data = self.unoconv(data, report.template_extension, output_format)
+ oext = FORMAT2EXT.get(output_format, output_format)
+ return (oext, data)
- def convert_pdf(self, data):
- """
- Convert report to PDF using OpenOffice.org.
- This requires OpenOffice.org, pyuno and openoffice-python to
- be installed.
- """
- import tempfile
+ def unoconv(self, data, input_format, output_format):
+ '''
+ Call unoconv to convert the OpenDocument
+ '''
+ fd, path = tempfile.mkstemp(suffix=(os.extsep + input_format),
+ prefix='trytond_')
+ oext = FORMAT2EXT.get(output_format, output_format)
+ with os.fdopen(fd, 'wb+') as fp:
+ fp.write(data)
+ cmd = ['unoconv', '--connection=%s' % CONFIG['unoconv'],
+ '-f', oext, '--stdout', path]
try:
- import unohelper # installs import-hook
- import openoffice.interact
- import openoffice.officehelper as officehelper
- from openoffice.streams import OutputStream
- from com.sun.star.beans import PropertyValue
- except ImportError, exception:
- raise Exception('ImportError', str(exception))
- try:
- # connect to OOo
- desktop = openoffice.interact.Desktop()
- except officehelper.BootstrapException:
- raise Exception('Error', "Can't connect to (bootstrap) OpenOffice.org")
-
- res_data = None
- # Create temporary file (with name) and write data there.
- # We can not use NamedTemporaryFile here, since this would be
- # deleted as soon as we close it to allow OOo reading.
- #TODO use an input stream here
- fd_odt, odt_name = tempfile.mkstemp()
- fh_odt = os.fdopen(fd_odt, 'wb+')
- try:
- fh_odt.write(data)
- del data # save memory
- fh_odt.close()
- doc = desktop.openFile(odt_name, hidden=False)
- # Export as PDF
- buffer = StringIO.StringIO()
- out_props = (
- PropertyValue("FilterName", 0, "writer_pdf_Export", 0),
- PropertyValue("Overwrite", 0, True, 0),
- PropertyValue("OutputStream", 0, OutputStream(buffer), 0),
- )
- doc.storeToURL("private:stream", out_props)
- res_data = buffer.getvalue()
- del buffer
- doc.dispose()
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ stdoutdata, stderrdata = proc.communicate()
+ if proc.wait() != 0:
+ raise Exception(stderrdata)
+ return stdoutdata
finally:
- fh_odt.close()
- os.remove(odt_name)
- if not res_data:
- Exception('Error', 'Error converting to PDF')
- return res_data
+ os.remove(path)
def format_lang(self, value, lang, digits=2, grouping=True, monetary=False,
date=False, currency=None, symbol=True):