Use unoconv and all Opendocument format in report
issue2031 review72001
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -87,3 +87,6 @@
|
||||
|
||||
# Enable cron
|
||||
# cron = True
|
||||
|
||||
# unoconv connection
|
||||
#unoconv = pipe,name=trytond;urp;StarOffice.ComponentContext
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -49,6 +49,7 @@ class ConfigManager(object):
|
||||
'init': {},
|
||||
'update': {},
|
||||
'cron': True,
|
||||
'unoconv': 'pipe,name=trytond;urp;StarOffice.ComponentContext',
|
||||
}
|
||||
self.configfile = None
|
||||
|
||||
|
||||
+80
-5
@@ -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 ''
|
||||
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user