Use unoconv and all Opendocument format in report

issue2031
review72001
This commit is contained in:
Cédric Krier
2011-08-06 18:25:24 +02:00
parent d5efbc62ad
commit d26164af74
9 changed files with 148 additions and 68 deletions
+2
View File
@@ -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
+1 -1
View File
@@ -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
+3
View File
@@ -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:
<field name="model">account.invoice</field>
<field name="report">my_module/invoice.odt</field>
<field name="style">module_name/header_A4.odt</field>
<field name="template_extension">odt</field>
</record>
Passing custom data to a report
+3
View File
@@ -87,3 +87,6 @@
# Enable cron
# cron = True
# unoconv connection
#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
+1 -1
View File
@@ -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'],
+1
View File
@@ -49,6 +49,7 @@ class ConfigManager(object):
'init': {},
'update': {},
'cron': True,
'unoconv': 'pipe,name=trytond;urp;StarOffice.ComponentContext',
}
self.configfile = None
+80 -5
View File
@@ -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 ''
+3 -1
View File
@@ -92,9 +92,11 @@ this repository contains the full copyright notices and license terms. -->
<field name="icon"/>
<newline/>
<label name="report"/>
<field name="report" colspan="3"/>
<field name="report"/>
<label name="report_content"/>
<field name="report_content"/>
<label name="template_extension"/>
<field name="template_extension"/>
<label name="extension"/>
<field name="extension"/>
<label name="style"/>
+54 -60
View File
@@ -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):