Use relatorio for reporting
This commit is contained in:
1 parent
a051287eef
commit
5480bd311d
1 file changed
+73
-232
+73
-232
@@ -21,6 +21,11 @@ import datetime
|
||||
import md5
|
||||
from _strptime import LocaleTime
|
||||
from base64 import decodestring
|
||||
import relatorio
|
||||
import tempfile
|
||||
from genshi.filters import Translator
|
||||
import traceback
|
||||
from trytond.config import CONFIG
|
||||
|
||||
MODULE_LIST = []
|
||||
MODULE_CLASS_LIST = {}
|
||||
@@ -241,6 +246,34 @@ PARENTS = {
|
||||
}
|
||||
|
||||
|
||||
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 = ""
|
||||
|
||||
@@ -304,34 +337,33 @@ class Report(object):
|
||||
def parse(self, cursor, user, report, objects, datas, context):
|
||||
localcontext = {}
|
||||
localcontext['datas'] = datas
|
||||
localcontext['objects'] = objects
|
||||
localcontext['user'] = self.pool.get('res.user').\
|
||||
browse(cursor, user, user)
|
||||
localcontext['_language_cache'] = {}
|
||||
localcontext['formatLang'] = self.format_lang
|
||||
localcontext['decodestring'] = decodestring
|
||||
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 ExceptOSV('Error', 'Missing report file!')
|
||||
#cStringIO difference:
|
||||
#calling StringIO() with a string parameter creates a read-only object
|
||||
content_io = StringIO.StringIO()
|
||||
content_io.write(report.report_content)
|
||||
content_z = zipfile.ZipFile(content_io, mode='r')
|
||||
localcontext['content_z'] = content_z
|
||||
content_xml = content_z.read('content.xml')
|
||||
dom = xml.dom.minidom.parseString(content_xml)
|
||||
node = dom.documentElement
|
||||
self._parse_node(cursor, user, node, localcontext, context)
|
||||
raise Exception('Error', 'Missing report file!')
|
||||
|
||||
style_z = zipfile.ZipFile(content_io, mode='r')
|
||||
style_xml = content_z.read('styles.xml')
|
||||
style_z.close()
|
||||
dom_style = xml.dom.minidom.parseString(style_xml)
|
||||
node_style = dom_style.documentElement
|
||||
self._parse_node(cursor, user, node_style, localcontext, context)
|
||||
content_z.close()
|
||||
fd, path = tempfile.mkstemp(suffix='.odt', prefix='trytond')
|
||||
os.write(fd, report.report_content)
|
||||
os.close(fd)
|
||||
|
||||
pictures = []
|
||||
if report.style_content:
|
||||
pictures = []
|
||||
content_z = zipfile.ZipFile(path, mode='r')
|
||||
style_xml = content_z.read('styles.xml')
|
||||
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()
|
||||
@@ -346,7 +378,6 @@ class Report(object):
|
||||
style2_io.close()
|
||||
dom_style2 = xml.dom.minidom.parseString(style2_xml)
|
||||
node_style2 = dom_style2.documentElement
|
||||
self._parse_node(cursor, user, node_style2, localcontext, context)
|
||||
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,
|
||||
@@ -356,27 +387,28 @@ class Report(object):
|
||||
style_header_node.parentNode.replaceChild(style_header_node2,
|
||||
style_header_node)
|
||||
|
||||
content_z = zipfile.ZipFile(content_io, mode='a')
|
||||
content_z.writestr('content.xml',
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' + \
|
||||
dom.documentElement.toxml('utf-8'))
|
||||
content_z.writestr('styles.xml',
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' + \
|
||||
dom_style.documentElement.toxml('utf-8'))
|
||||
content_z = zipfile.ZipFile(path, mode='a')
|
||||
|
||||
if localcontext.get('pictures'):
|
||||
pictures.extend(localcontext['pictures'])
|
||||
for file, picture in pictures:
|
||||
content_z.writestr(file, picture)
|
||||
content_z.close()
|
||||
data = content_io.getvalue()
|
||||
content_io.close()
|
||||
content_z.writestr('styles.xml',
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' + \
|
||||
dom_style.documentElement.toxml('utf-8'))
|
||||
|
||||
for file, picture in pictures:
|
||||
content_z.writestr(file, picture)
|
||||
content_z.close()
|
||||
|
||||
translator = Translator(translate)
|
||||
|
||||
rel_report = relatorio.Report(path, 'application/vnd.oasis.opendocument.text',
|
||||
ReportFactory(), relatorio.MIMETemplateLoader())
|
||||
rel_report.filters.insert(0, translator)
|
||||
data = rel_report(objects, **localcontext).render().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.
|
||||
@@ -428,174 +460,6 @@ class Report(object):
|
||||
ExceptOSV('Error', 'Error converting to PDF')
|
||||
return res_data
|
||||
|
||||
def _parse_node(self, cursor, user, node, localcontext, context,
|
||||
node_context = None):
|
||||
if node_context is None:
|
||||
node_context = {}
|
||||
while True:
|
||||
if node.hasChildNodes():
|
||||
node = node.firstChild
|
||||
elif node.nextSibling:
|
||||
node = node.nextSibling
|
||||
else:
|
||||
while node and not node.nextSibling:
|
||||
node = node.parentNode
|
||||
if not node:
|
||||
break
|
||||
node = node.nextSibling
|
||||
if node in node_context:
|
||||
localcontext.update(node_context[node])
|
||||
if node.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
|
||||
res = self._parse_text(cursor, user, node, localcontext, context,
|
||||
node_context)
|
||||
if isinstance(res, dom.Node):
|
||||
node = res
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
if node.nodeName == "draw:frame":
|
||||
self._parse_draw_frame(cursor, user, node, localcontext, context,)
|
||||
|
||||
def _parse_draw_frame(self, cursor, user, node, localcontext, context):
|
||||
"""When a picture name in the template document contain
|
||||
"replaceWith(content, extension)", this piture is replaced
|
||||
with the content, a file name is generated automatically with
|
||||
the given extension
|
||||
"""
|
||||
if "replaceWith" not in node.attributes["draw:name"].nodeValue:
|
||||
return
|
||||
localcontext.setdefault('pictures', [])
|
||||
ctx = localcontext.copy()
|
||||
ctx.update(context)
|
||||
ctx["replaceWith"] = lambda x,y: (x,y)
|
||||
try:
|
||||
res = eval(node.attributes["draw:name"].nodeValue, ctx)
|
||||
except:
|
||||
Logger().notify_channel('report', LOG_ERROR,
|
||||
'Error on eval "%s"' % node.nodeValue)
|
||||
raise
|
||||
|
||||
if isinstance(res, tuple):
|
||||
data = decodestring(res[0])
|
||||
filename = 'Pictures/%s.%s'% (md5.new(data).hexdigest(), res[1])
|
||||
localcontext['pictures'].append((filename,data))
|
||||
for child_node in node.childNodes:
|
||||
if child_node.nodeName == "draw:image":
|
||||
child_node.attributes["xlink:href"].nodeValue = filename
|
||||
continue
|
||||
|
||||
def _parse_text(self, cursor, user, node, localcontext, context,
|
||||
node_context):
|
||||
if node.parentNode.tagName == 'text:text-input':
|
||||
ctx = localcontext.copy()
|
||||
ctx.update(context)
|
||||
ctx['RepeatIn'] = lambda lst, name, parents=False: \
|
||||
self.repeat_in(lst, name, parents=parents,
|
||||
tnode=node, node_context=node_context)
|
||||
ctx['setTag'] = lambda oldtag, newtag, attrs=None: \
|
||||
self.set_tag(oldtag, newtag, attrs=attrs, tnode=node)
|
||||
ctx['removeParentNode'] = lambda tag='p': \
|
||||
self.remove_parent_node(tag, tnode=node)
|
||||
ctx['setLang'] = lambda lang: \
|
||||
self.set_lang(lang, localcontext)
|
||||
ctx['formatLang'] = lambda value, digits=2, date=False: \
|
||||
self.format_lang(value, digits=digits, date=date,
|
||||
localcontext=localcontext)
|
||||
ctx['time'] = time
|
||||
ctx['datetime'] = datetime
|
||||
try:
|
||||
res = eval(node.nodeValue, ctx)
|
||||
except:
|
||||
Logger().notify_channel('report', LOG_ERROR,
|
||||
'Error on eval "%s"' % node.nodeValue)
|
||||
raise
|
||||
if isinstance(res, bool):
|
||||
res = ''
|
||||
if hasattr(res, '__str__'):
|
||||
res = res.__str__()
|
||||
if isinstance(res, basestring):
|
||||
if '\n' in res:
|
||||
parent2 = node.parentNode.parentNode
|
||||
parent = node.parentNode
|
||||
first = True
|
||||
newnode = None
|
||||
for val in res.decode('utf-8').split('\n'):
|
||||
if first:
|
||||
newnode = node
|
||||
first = False
|
||||
else:
|
||||
newnode = node.cloneNode(1)
|
||||
newnode.nodeValue = val
|
||||
parent2.insertBefore(newnode, parent)
|
||||
newnode = node.parentNode.cloneNode(1)
|
||||
newnode.nodeType = newnode.ELEMENT_NODE
|
||||
newnode.tagName = 'text:line-break'
|
||||
newnode.firstChild.nodeValue = ''
|
||||
if newnode.getAttribute('text:style-name'):
|
||||
newnode.removeAttribute('text:style-name')
|
||||
parent2.insertBefore(newnode, parent)
|
||||
parent2.removeChild(parent)
|
||||
if newnode:
|
||||
parent2.removeChild(newnode)
|
||||
else:
|
||||
node.nodeValue = res.decode('utf-8')
|
||||
node.parentNode.parentNode.replaceChild(node, node.parentNode)
|
||||
return res
|
||||
if 'language' in localcontext:
|
||||
lang = localcontext['language']
|
||||
text = node.nodeValue
|
||||
if lang and text and not text.isspace():
|
||||
translation_obj = self.pool.get('ir.translation')
|
||||
new_text = translation_obj._get_source(cursor,
|
||||
self._name, 'odt', lang, text)
|
||||
if new_text:
|
||||
node.nodeValue = new_text.decode('utf-8')
|
||||
return None
|
||||
|
||||
def repeat_in(self, lst, name, parents=False, tnode=None, node_context=None):
|
||||
node = self.find_parent(tnode, parents or PARENTS)
|
||||
|
||||
pnode = node.parentNode
|
||||
nextnode = node.nextSibling
|
||||
pnode.removeChild(node)
|
||||
tnode.parentNode.parentNode.removeChild(tnode.parentNode)
|
||||
|
||||
if not lst:
|
||||
return pnode
|
||||
for i in range(len(lst)):
|
||||
newnode = node.cloneNode(1)
|
||||
if nextnode:
|
||||
pnode.insertBefore(newnode, nextnode)
|
||||
else:
|
||||
pnode.appendChild(newnode)
|
||||
node_context[newnode] = {name: lst[i]}
|
||||
return pnode
|
||||
|
||||
def set_tag(self, oldtag, newtag, attrs=None, tnode=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
node = self.find_parent(tnode, [oldtag])
|
||||
if node:
|
||||
node.tagName = newtag
|
||||
for key, val in attrs.items():
|
||||
node.setAttribute(key, val)
|
||||
return None
|
||||
|
||||
def remove_parent_node(self, tag='p', tnode=None):
|
||||
node = self.find_parent(tnode, [tag])
|
||||
if node:
|
||||
pnode = node.parentNode
|
||||
pnode.removeChild(node)
|
||||
return pnode
|
||||
|
||||
def find_parent(self, node, parents):
|
||||
while True:
|
||||
if not node.parentNode:
|
||||
return None
|
||||
node = node.parentNode
|
||||
if node.nodeType == node.ELEMENT_NODE \
|
||||
and node.localName in parents:
|
||||
break
|
||||
return node
|
||||
|
||||
def find(self, tnode, tag):
|
||||
for node in tnode.childNodes:
|
||||
if node.nodeType == node.ELEMENT_NODE \
|
||||
@@ -606,43 +470,20 @@ class Report(object):
|
||||
return res
|
||||
return None
|
||||
|
||||
def set_lang(self, lang, localcontext):
|
||||
localcontext['language'] = lang
|
||||
_language_cache = localcontext['_language_cache']
|
||||
for obj in localcontext['objects']:
|
||||
obj._context['language'] = lang
|
||||
for table in obj._cache:
|
||||
for obj_id in obj._cache[table]:
|
||||
_language_cache.setdefault(
|
||||
obj._context['language'], {}).setdefault(
|
||||
table, {}).update(
|
||||
obj._cache[table][obj_id])
|
||||
if lang in _language_cache \
|
||||
and table in _language_cache[lang] \
|
||||
and obj_id in _language_cache[lang][table]:
|
||||
obj._cache[table][obj_id] = \
|
||||
_language_cache[lang][table][obj_id]
|
||||
else:
|
||||
obj._cache[table][obj_id] = {'id': obj_id}
|
||||
return ''
|
||||
|
||||
def format_lang(self, value, digits=2, date=False, localcontext=None):
|
||||
if localcontext is None:
|
||||
localcontext = {}
|
||||
def format_lang(self, value, digits=2, date=False, language='en_US'):
|
||||
encoding = locale.getdefaultlocale()[1]
|
||||
if encoding == 'utf':
|
||||
encoding = 'UTF-8'
|
||||
if encoding == 'cp1252':
|
||||
encoding = '1252'
|
||||
lang = localcontext.get('language', False) or 'en_US'
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
lang = _LOCALE2WIN32.get(lang, lang)
|
||||
locale.setlocale(locale.LC_ALL, lang + '.' + encoding)
|
||||
language = _LOCALE2WIN32.get(language, language)
|
||||
locale.setlocale(locale.LC_ALL, language + '.' + encoding)
|
||||
except Exception:
|
||||
Logger().notify_channel('web-service', LOG_ERROR,
|
||||
'Report %s: unable to set locale "%s"' % \
|
||||
(self._name, lang + '.' + encoding))
|
||||
(self._name, language + '.' + encoding))
|
||||
if date:
|
||||
if isinstance(value, time.struct_time):
|
||||
locale_format = LocaleTime().LC_date.replace('%y', '%Y')
|
||||
|
||||
Reference in new issue
Block a user