289 lines
10 KiB
Python
289 lines
10 KiB
Python
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
#this repository contains the full copyright notices and license terms.
|
|
from trytond.pool import Pool
|
|
import copy
|
|
import xml
|
|
from xml import dom
|
|
from xml.dom import minidom
|
|
import sys
|
|
import base64
|
|
try:
|
|
import cStringIO as StringIO
|
|
except ImportError:
|
|
import StringIO
|
|
import zipfile
|
|
import time
|
|
import os
|
|
import datetime
|
|
import md5
|
|
from base64 import decodestring
|
|
|
|
import warnings
|
|
warnings.simplefilter("ignore")
|
|
import relatorio.reporting
|
|
warnings.resetwarnings()
|
|
|
|
import tempfile
|
|
from genshi.filters import Translator
|
|
import traceback
|
|
from trytond.config import CONFIG
|
|
from trytond.backend import DatabaseIntegrityError
|
|
import inspect
|
|
import mx.DateTime
|
|
import logging
|
|
|
|
PARENTS = {
|
|
'table-row': 1,
|
|
'list-item': 1,
|
|
'body': 0,
|
|
'section': 0,
|
|
}
|
|
|
|
|
|
class ReportFactory:
|
|
|
|
def __call__(self, objects, **kwargs):
|
|
data = {}
|
|
data['objects'] = objects
|
|
data.update(kwargs)
|
|
return data
|
|
|
|
|
|
class TranslateFactory:
|
|
|
|
def __init__(self, cursor, report_name, language, translation):
|
|
self.cursor = cursor
|
|
self.report_name = report_name
|
|
self.language = language
|
|
self.translation = translation
|
|
|
|
def __call__(self, text):
|
|
res = self.translation._get_source(self.cursor,
|
|
self.report_name, 'odt', self.language, text)
|
|
if res:
|
|
return res
|
|
return text
|
|
|
|
def set_language(self, language):
|
|
self.language = language
|
|
|
|
|
|
class Report(object):
|
|
_name = ""
|
|
_rpc_allowed = [
|
|
'execute',
|
|
]
|
|
|
|
def __new__(cls):
|
|
Pool.register(cls, type='report')
|
|
|
|
def init(self, cursor, module_name):
|
|
pass
|
|
|
|
def execute(self, cursor, user, ids, datas, context=None):
|
|
if context is None:
|
|
context = {}
|
|
action_report_obj = self.pool.get('ir.action.report')
|
|
action_report_ids = action_report_obj.search(cursor, user, [
|
|
('report_name', '=', self._name)
|
|
], context=context)
|
|
if not action_report_ids:
|
|
raise Exception('Error', 'Report (%s) not find!' % self._name)
|
|
action_report = action_report_obj.browse(cursor, user,
|
|
action_report_ids[0], context=context)
|
|
objects = None
|
|
if action_report.model:
|
|
objects = self._get_objects(cursor, user, ids, action_report.model,
|
|
datas, context)
|
|
type, data = self.parse(cursor, user, action_report,
|
|
objects, datas, context)
|
|
return (type, base64.encodestring(data), action_report.direct_print)
|
|
|
|
def _get_objects(self, cursor, user, ids, model, datas, context):
|
|
model_obj = self.pool.get(model)
|
|
return model_obj.browse(cursor, user, ids, context=context)
|
|
|
|
def parse(self, cursor, user, report, objects, datas, context):
|
|
localcontext = {}
|
|
localcontext['datas'] = datas
|
|
localcontext['user'] = self.pool.get('res.user').\
|
|
browse(cursor, user, user)
|
|
localcontext['formatLang'] = self.format_lang
|
|
localcontext['decodestring'] = decodestring
|
|
localcontext['StringIO'] = StringIO.StringIO
|
|
localcontext['time'] = time
|
|
localcontext['datetime'] = datetime
|
|
localcontext.update(context)
|
|
|
|
translate = TranslateFactory(cursor, self._name,
|
|
context.get('language', 'en_US'),
|
|
self.pool.get('ir.translation'))
|
|
localcontext['setLang'] = lambda language: translate.set_language(language)
|
|
|
|
if not report.report_content:
|
|
raise Exception('Error', 'Missing report file!')
|
|
|
|
fd, path = tempfile.mkstemp(suffix='.odt', prefix='trytond')
|
|
outzip = zipfile.ZipFile(path, mode='w')
|
|
|
|
content_io = StringIO.StringIO()
|
|
content_io.write(base64.decodestring(report.report_content))
|
|
content_z = zipfile.ZipFile(content_io, mode='r')
|
|
|
|
style_info = None
|
|
style_xml = None
|
|
for f in content_z.infolist():
|
|
if f.filename == 'styles.xml' and report.style_content:
|
|
style_info = f
|
|
style_xml = content_z.read(f.filename)
|
|
continue
|
|
outzip.writestr(f, content_z.read(f.filename))
|
|
|
|
if report.style_content:
|
|
pictures = []
|
|
dom_style = xml.dom.minidom.parseString(style_xml)
|
|
node_style = dom_style.documentElement
|
|
|
|
#cStringIO difference:
|
|
#calling StringIO() with a string parameter creates a read-only object
|
|
style2_io = StringIO.StringIO()
|
|
style2_io.write(base64.decodestring(report.style_content))
|
|
style2_z = zipfile.ZipFile(style2_io, mode='r')
|
|
style2_xml = style2_z.read('styles.xml')
|
|
for file in style2_z.namelist():
|
|
if file.startswith('Pictures'):
|
|
picture = style2_z.read(file)
|
|
pictures.append((file, picture))
|
|
style2_z.close()
|
|
style2_io.close()
|
|
dom_style2 = xml.dom.minidom.parseString(style2_xml)
|
|
node_style2 = dom_style2.documentElement
|
|
style_header_node2 = self.find(node_style2, 'master-styles')
|
|
style_header_node = self.find(node_style, 'master-styles')
|
|
style_header_node.parentNode.replaceChild(style_header_node2,
|
|
style_header_node)
|
|
style_header_node2 = self.find(node_style2, 'automatic-styles')
|
|
style_header_node = self.find(node_style, 'automatic-styles')
|
|
style_header_node.parentNode.replaceChild(style_header_node2,
|
|
style_header_node)
|
|
|
|
outzip.writestr(style_info,
|
|
'<?xml version="1.0" encoding="UTF-8"?>' + \
|
|
dom_style.documentElement.toxml('utf-8'))
|
|
|
|
for file, picture in pictures:
|
|
outzip.writestr(file, picture)
|
|
|
|
content_z.close()
|
|
content_io.close()
|
|
outzip.close()
|
|
|
|
translator = Translator(translate)
|
|
|
|
rel_report = relatorio.reporting.Report(path, 'application/vnd.oasis.opendocument.text',
|
|
ReportFactory(), relatorio.reporting.MIMETemplateLoader())
|
|
rel_report.filters.insert(0, translator)
|
|
#Test compatibility with old relatorio version <= 0.3.0
|
|
if len(inspect.getargspec(rel_report.__call__)[0]) == 2:
|
|
data = rel_report(objects, **localcontext).render().getvalue()
|
|
else:
|
|
localcontext['objects'] = objects
|
|
data = rel_report(**localcontext).render()
|
|
if hasattr(data, 'getvalue'):
|
|
data = data.getvalue()
|
|
os.remove(path)
|
|
output_format = report.output_format.format
|
|
if output_format == 'pdf':
|
|
data = self.convert_pdf(data)
|
|
return (output_format, 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
|
|
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()
|
|
finally:
|
|
fh_odt.close()
|
|
os.remove(odt_name)
|
|
if not res_data:
|
|
Exception('Error', 'Error converting to PDF')
|
|
return res_data
|
|
|
|
def find(self, tnode, tag):
|
|
for node in tnode.childNodes:
|
|
if node.nodeType == node.ELEMENT_NODE \
|
|
and node.localName == tag:
|
|
return node
|
|
res = self.find(node, tag)
|
|
if res is not None:
|
|
return res
|
|
return None
|
|
|
|
def format_lang(self, value, lang, digits=2, grouping=True, monetary=False,
|
|
date=False, currency=None):
|
|
lang_obj = self.pool.get('ir.lang')
|
|
|
|
if date:
|
|
if lang:
|
|
locale_format = lang.date
|
|
else:
|
|
locale_format = lang_obj.default_date(None, None)
|
|
if not isinstance(value, time.struct_time):
|
|
# assume string, parse it
|
|
if len(str(value)) == 10:
|
|
# length of date like 2001-01-01 is ten
|
|
# assume format '%Y-%m-%d'
|
|
string_pattern = '%Y-%m-%d'
|
|
else:
|
|
# assume format '%Y-%m-%d %H:%M:%S'
|
|
value = str(value)[:19]
|
|
locale_format = locale_format + ' %H:%M:%S'
|
|
string_pattern = '%Y-%m-%d %H:%M:%S'
|
|
date = mx.DateTime.strptime(str(value), string_pattern)
|
|
else:
|
|
date = mx.DateTime.DateTime(*(value.timetuple()[:6]))
|
|
return date.strftime(locale_format)
|
|
if currency:
|
|
return lang_obj.currency(lang, value, currency, grouping=grouping)
|
|
return lang_obj.format(lang, '%.' + str(digits) + 'f', value,
|
|
grouping=grouping, monetary=monetary)
|