Files
tryton/trytond/report/report.py
T

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)