1
# -*- encoding: utf-8 -*-
3
##############################################################################
5
# Copyright (c) 2009-2010 KN dati, SIA. (http://kndati.lv) All Rights Reserved.
6
# General contacts <info@kndati.lv>
7
# Copyright (C) 2009 Domsense s.r.l.
9
# WARNING: This program as such is intended to be used by professional
10
# programmers who take the whole responsability of assessing all potential
11
# consequences resulting from its eventual inadequacies and bugs
12
# End users who are looking for a ready-to-use solution with commercial
13
# garantees and support are strongly adviced to contract a Free Software
16
# This program is Free Software; you can redistribute it and/or
17
# modify it under the terms of the GNU General Public License
18
# as published by the Free Software Foundation; either version 2
19
# of the License, or (at your option) any later version.
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
# GNU General Public License for more details.
26
# You should have received a copy of the GNU General Public License
27
# along with this program; if not, write to the Free Software
28
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30
##############################################################################
32
import os, sys, traceback
35
from report.report_sxw import *
38
from cStringIO import StringIO
40
from StringIO import StringIO
41
from xml.dom import minidom
44
from tools.translate import _
49
from addons import load_information_from_description_file
53
from aeroolib.plugins.opendocument import Template, OOSerializer
54
from genshi.template import NewTextTemplate
57
logger = netsvc.Logger()
59
from ExtraFunctions import ExtraFunctions
61
class Counter(object):
62
def __init__(self, name, start=0, interval=1):
65
self._interval = interval
68
curr_number = self._number
69
self._number += self._interval
76
return self._number-self._interval
78
class Aeroo_report(report_sxw):
80
def logger(self, message, level=netsvc.LOG_DEBUG):
81
netsvc.Logger().notifyChannel('report_aeroo', level, message)
83
def __init__(self, cr, name, table, rml=False, parser=False, header=True, store=False):
84
super(Aeroo_report, self).__init__(name, table, rml, parser, header, store)
85
self.logger("registering %s (%s)" % (name, table), netsvc.LOG_INFO)
86
self.oo_subreports = []
90
pool = pooler.get_pool(cr.dbname)
91
ir_obj = pool.get('ir.actions.report.xml')
92
report_xml_ids = ir_obj.search(cr, 1, [('report_name', '=', name[7:])])
94
report_xml = ir_obj.browse(cr, 1, report_xml_ids[0])
96
if report_xml.preload_mode == 'preload':
97
file_data = report_xml.report_sxw_content
99
self.logger("template is not defined in %s (%s) !" % (name, table), netsvc.LOG_WARNING)
102
template_io = StringIO()
103
template_io.write(base64.decodestring(file_data))
104
style_io=self.get_styles_file(cr, 1, report_xml)
106
self.serializer = OOSerializer(template_io, oo_styles=style_io)
108
##### Counter functions #####
109
def _def_inc(self, name, start=0, interval=1):
110
self.counters[name] = Counter(name, start, interval)
112
def _get_inc(self, name):
113
return self.counters[name].get_inc()
115
def _prev(self, name):
116
return self.counters[name].prev()
118
def _next(self, name):
119
return self.counters[name].next()
120
#############################
122
def _epl_asimage(self, data):
123
from PIL import Image
124
from math import ceil
127
img = Image.open(StringIO(base64.decodestring(data)))
128
if img.format!='BMP':
130
data = base64.decodestring(data)[62:]
131
line_len = int(ceil(img.size[0]/32.0)*4)
133
for n in range(img.size[1]):
134
curr_pos = n*line_len
135
temp_data = data[curr_pos:curr_pos+line_len][:int(img.size[0]/8)] + temp_data
139
new_data += chr(ord(d)^255)
140
self.epl_images.append(new_data)
143
def _epl2_gw(self, start_x, start_y, data):
146
size_x, size_y = self._epl_asimage(data)
147
return 'GW'+str(start_x)+','+str(start_y)+','+str(int(size_x/8))+','+str(size_y)+',<binary_data>'
149
def _include_document(self, aeroo_ooo=False):
150
def include_document(data, silent=False):
152
return _("Error! Include document not available!")
153
import binascii, urllib2
154
dummy_fd, temp_file_name = tempfile.mkstemp(suffix='.odt', prefix='aeroo-report-')
155
temp_file = open(temp_file_name, 'wb')
156
if os.path.isfile(data):
157
fd = file(data, 'rb')
162
url_file = urllib2.urlopen(data)
163
data = url_file.read()
164
except urllib2.HTTPError:
165
os.unlink(temp_file_name)
166
error = _('HTTP Error 404! Not file found:')+' %s' % data
167
except urllib2.URLError, e:
168
os.unlink(temp_file_name)
169
error = _('Error!')+' %s' % e
171
os.unlink(temp_file_name)
172
error = _('Error!')+' %s' % e
175
data = base64.decodestring(data)
176
except binascii.Error:
177
os.unlink(temp_file_name)
178
error = _('Error! Not file found:')+' %s' % data
185
temp_file.write(data)
188
self.oo_subreports.append(temp_file_name)
189
return "<insert_doc('%s')>" % temp_file_name
190
return include_document
192
def _subreport(self, cr, uid, output='odt', aeroo_ooo=False, context={}):
193
pool = pooler.get_pool(cr.dbname)
194
ir_obj = pool.get('ir.actions.report.xml')
195
#### for odt documents ####
196
def odt_subreport(name=None, obj=None):
198
return _("Error! Subreports not available!")
199
report_xml_ids = ir_obj.search(cr, uid, [('report_name', '=', name)], context=context)
201
report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
202
data = {'model': obj._table_name, 'id': obj.id, 'report_type': 'aeroo', 'in_format': 'oo-odt'}
203
report, output = netsvc.Service._services['report.%s' % name].create_aeroo_report(cr, uid, \
204
[obj.id], data, report_xml, context=context, output='odt') # change for OpenERP 6.0 - Service class usage
206
dummy_fd, temp_file_name = tempfile.mkstemp(suffix='.odt', prefix='aeroo-report-')
207
temp_file = open(temp_file_name, 'wb')
209
temp_file.write(report)
213
self.oo_subreports.append(temp_file_name)
215
return "<insert_doc('%s')>" % temp_file_name
217
#### for text documents ####
218
def raw_subreport(name=None, obj=None):
219
report_xml_ids = ir_obj.search(cr, uid, [('report_name', '=', name)], context=context)
221
report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
222
data = {'model': obj._table_name, 'id': obj.id, 'report_type': 'aeroo', 'in_format': 'genshi-raw'}
223
report, output = netsvc.Service._services['report.%s' % name].create_genshi_raw_report(cr, uid, \
224
[obj.id], data, report_xml, context=context, output=output) # change for OpenERP 6.0 - Service class usage
233
def set_xml_data_fields(self, objects, parser):
234
xml_data_fields = parser.localcontext.get('xml_data_fields', False)
236
for field in xml_data_fields:
238
if getattr(o, field):
239
xml_data = base64.decodestring(getattr(o, field))
240
xmldoc = minidom.parseString(xml_data)
241
setattr(o, field, xmldoc.firstChild)
244
def get_other_template(self, cr, uid, data, parser):
245
if hasattr(parser, 'get_template'):
246
pool = pooler.get_pool(cr.dbname)
247
record = pool.get(data['model']).browse(cr, uid, data['id'], {})
248
template = parser.get_template(cr, uid, record)
253
def get_styles_file(self, cr, uid, report_xml, context=None):
254
pool = pooler.get_pool(cr.dbname)
256
if report_xml.styles_mode!='default':
257
if report_xml.styles_mode=='global':
258
company_id = pool.get('res.users')._get_company(cr, uid, context=context)
259
style_content = pool.get('res.company').browse(cr, uid, company_id, context=context).stylesheet_id
260
style_content = style_content and style_content.report_styles or False
261
elif report_xml.styles_mode=='specified':
262
style_content = report_xml.stylesheet_id
263
style_content = style_content and style_content.report_styles or False
265
style_io = StringIO()
266
style_io.write(base64.decodestring(style_content))
269
def create_genshi_raw_report(self, cr, uid, ids, data, report_xml, context=None, output='raw'):
270
def preprocess(data):
271
self.epl_images.reverse()
272
while self.epl_images:
273
img = self.epl_images.pop()
274
data = data.replace('<binary_data>', img, 1)
275
return data.replace('\n', '\r\n')
279
context = context.copy()
280
objects = self.getObjects(cr, uid, ids, context)
281
oo_parser = self.parser(cr, uid, self.name2, context=context)
282
oo_parser.objects = objects
283
self.set_xml_data_fields(objects, oo_parser) # Get/Set XML
284
file_data = self.get_other_template(cr, uid, data, oo_parser) or report_xml.report_sxw_content # Get other Tamplate
285
################################################
289
oo_parser.localcontext['objects'] = objects
290
oo_parser.localcontext['data'] = data
291
oo_parser.localcontext['user_lang'] = context['lang']
293
oo_parser.localcontext['o'] = objects[0]
294
xfunc = ExtraFunctions(cr, uid, report_xml.id, oo_parser.localcontext)
295
oo_parser.localcontext.update(xfunc.functions)
296
oo_parser.localcontext['include_subreport'] = self._subreport(cr, uid, output='raw', aeroo_ooo=False, context=context)
297
oo_parser.localcontext['epl2_gw'] = self._epl2_gw
300
basic = NewTextTemplate(source=file_data)
302
data = preprocess(basic.generate(**oo_parser.localcontext).render().decode('utf8').encode(report_xml.charset))
303
#except Exception, e:
304
# self.logger(str(e), netsvc.LOG_ERROR)
305
# return False, output
307
if report_xml.content_fname:
308
output = report_xml.content_fname
311
def create_aeroo_report(self, cr, uid, ids, data, report_xml, context=None, output='odt'):
312
""" Returns an aeroo report generated with aeroolib
314
pool = pooler.get_pool(cr.dbname)
317
context = context.copy()
318
if self.name=='report.printscreen.list':
319
context['model'] = data['model']
322
objects = not context.get('no_objects', False) and self.getObjects(cr, uid, ids, context) or []
323
oo_parser = self.parser(cr, uid, self.name2, context=context)
325
oo_parser.objects = objects
326
self.set_xml_data_fields(objects, oo_parser) # Get/Set XML
328
style_io=self.get_styles_file(cr, uid, report_xml, context)
329
if report_xml.tml_source in ('file', 'database'):
330
file_data = base64.decodestring(report_xml.report_sxw_content)
332
file_data = self.get_other_template(cr, uid, data, oo_parser)
333
if not file_data and not report_xml.report_sxw_content:
336
# template_io = StringIO()
337
# template_io.write(file_data or report_xml.report_sxw_content)
338
# basic = Template(source=template_io, styles=style_io)
340
if report_xml.preload_mode == 'preload' and hasattr(self, 'serializer'):
341
serializer = copy.copy(self.serializer)
342
serializer.apply_style(style_io)
343
template_io = serializer.template
345
template_io = StringIO()
346
template_io.write(file_data or base64.decodestring(report_xml.report_sxw_content) )
347
serializer = OOSerializer(template_io, oo_styles=style_io)
348
basic = Template(source=template_io, serializer=serializer)
351
# return False, output
353
#basic = Template(source=template_io, serializer=serializer)
355
oo_parser.localcontext['objects'] = objects
356
oo_parser.localcontext['data'] = data
357
oo_parser.localcontext['user_lang'] = context['lang']
359
oo_parser.localcontext['o'] = objects[0]
360
xfunc = ExtraFunctions(cr, uid, report_xml.id, oo_parser.localcontext)
361
oo_parser.localcontext.update(xfunc.functions)
363
###### Detect report_aeroo_ooo module ######
365
cr.execute("SELECT id, state FROM ir_module_module WHERE name='report_aeroo_ooo'")
366
helper_module = cr.dictfetchone()
367
if helper_module['state'] in ('installed', 'to upgrade'):
369
############################################
371
oo_parser.localcontext['include_subreport'] = self._subreport(cr, uid, output='odt', aeroo_ooo=aeroo_ooo, context=context)
372
oo_parser.localcontext['include_document'] = self._include_document(aeroo_ooo)
374
####### Add counter functons to localcontext #######
375
oo_parser.localcontext.update({'def_inc':self._def_inc,
376
'get_inc':self._get_inc,
380
user_name = pool.get('res.users').browse(cr, uid, uid, {}).name
381
model_id = pool.get('ir.model').search(cr, uid, [('model','=',data['model'])])[0]
382
model_name = pool.get('ir.model').browse(cr, uid, model_id).name
384
#basic = Template(source=None, filepath=odt_path)
386
basic.Serializer.add_title(model_name)
387
basic.Serializer.add_creation_user(user_name)
388
version = load_information_from_description_file('report_aeroo')['version']
389
basic.Serializer.add_generator_info('Aeroo Lib/%s Aeroo Reports/%s' % (aeroolib.__version__, version))
390
basic.Serializer.add_custom_property('Aeroo Reports %s' % version, 'Generator')
391
basic.Serializer.add_custom_property('OpenERP %s' % release.version, 'Software')
392
basic.Serializer.add_custom_property('http://www.alistek.com/', 'URL')
393
basic.Serializer.add_creation_date(time.strftime('%Y-%m-%dT%H:%M:%S'))
396
data = basic.generate(**oo_parser.localcontext).render().getvalue()
398
tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
399
self.logger(tb_s, netsvc.LOG_ERROR)
400
for sub_report in self.oo_subreports:
401
os.unlink(sub_report)
402
raise Exception(_("Aeroo Reports: Error while generating the report."), e, str(e), _("For more reference inspect error logs."))
403
#return False, output
405
######### OpenOffice extras #########
406
DC = netsvc.Service._services.get('openoffice')
407
if (output!=report_xml.in_format[3:] or self.oo_subreports):
411
if self.oo_subreports:
412
DC.insertSubreports(self.oo_subreports)
413
self.oo_subreports = []
414
data = DC.saveByStream(report_xml.out_format.filter_name)
418
self.logger(str(e), netsvc.LOG_ERROR)
419
output=report_xml.in_format[3:]
420
self.oo_subreports = []
422
output=report_xml.in_format[3:]
423
elif output in ('pdf', 'doc', 'xls'):
424
output=report_xml.in_format[3:]
425
#####################################
427
if report_xml.content_fname:
428
output = report_xml.content_fname
431
# override needed to keep the attachments' storing procedure
432
def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
435
if report_xml.report_type == 'aeroo':
436
if report_xml.out_format.code.startswith('oo-'):
437
output = report_xml.out_format.code[3:]
438
return self.create_aeroo_report(cr, uid, ids, data, report_xml, context=context, output=output)
439
elif report_xml.out_format.code =='genshi-raw':
440
return self.create_genshi_raw_report(cr, uid, ids, data, report_xml, context=context, output='raw')
442
context = context.copy()
443
title = report_xml.name
444
rml = report_xml.report_rml_content
445
oo_parser = self.parser(cr, uid, self.name2, context=context)
446
objs = self.getObjects(cr, uid, ids, context)
447
oo_parser.set_context(objs, data, ids, report_xml.report_type)
448
processed_rml = self.preprocess_rml(etree.XML(rml),report_xml.report_type)
449
if report_xml.header:
450
oo_parser._add_header(processed_rml)
452
logo = base64.decodestring(oo_parser.logo)
453
create_doc = self.generators[report_xml.report_type]
454
pdf = create_doc(etree.tostring(processed_rml),oo_parser.localcontext,logo,title.encode('utf8'))
455
return (pdf, report_xml.report_type)
457
def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
460
pool = pooler.get_pool(cr.dbname)
462
attach = report_xml.attachment
464
objs = self.getObjects(cr, uid, ids, context)
466
aname = eval(attach, {'object':obj, 'time':time})
468
if report_xml.attachment_use and aname and context.get('attachment_use', True):
469
aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.odt'),('res_model','=',self.table),('res_id','=',obj.id)])
471
brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
472
if not brow_rec.datas:
474
d = base64.decodestring(brow_rec.datas)
475
results.append((d,'odt'))
477
result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
480
name = aname+'.'+result[1]
481
pool.get('ir.attachment').create(cr, uid, {
483
'datas': base64.encodestring(result[0]),
485
'res_model': self.table,
491
self.logger(str(e), netsvc.LOG_ERROR)
492
results.append(result)
494
return results and len(results)==1 and results[0] or self.create_single_pdf(cr, uid, ids, data, report_xml, context)
496
# override needed to intercept the call to the proper 'create' method
497
def create(self, cr, uid, ids, data, context=None):
498
pool = pooler.get_pool(cr.dbname)
499
ir_obj = pool.get('ir.actions.report.xml')
500
report_xml_ids = ir_obj.search(cr, uid,
501
[('report_name', '=', self.name[7:])], context=context)
503
report_xml = ir_obj.browse(cr, uid, report_xml_ids[0], context=context)
504
report_xml.report_rml = None
505
report_xml.report_rml_content = None
506
report_xml.report_sxw_content_data = None
507
report_rml.report_sxw_content = None
508
report_rml.report_sxw = None
511
rml = tools.file_open(self.tmpl, subdir=None).read()
512
report_type= data.get('report_type', 'pdf')
514
def __init__(self, *args, **argv):
515
for key,arg in argv.items():
516
setattr(self, key, arg)
517
report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
519
report_type = report_xml.report_type
520
if report_type in ['sxw','odt']:
521
fnct = self.create_source_odt
522
elif report_type in ['pdf','raw','txt','html']:
523
fnct = self.create_source_pdf
524
elif report_type=='html2html':
525
fnct = self.create_source_html2html
526
elif report_type=='mako2html':
527
fnct = self.create_source_mako2html
528
elif report_type=='aeroo':
529
if report_xml.out_format.code in ['oo-pdf']:
530
fnct = self.create_source_pdf
531
elif report_xml.out_format.code in ['oo-odt','oo-ods','oo-doc','oo-xls','genshi-raw']:
532
fnct = self.create_source_odt
534
return super(Aeroo_report, self).create(cr, uid, ids, data, context)
536
raise 'Unknown Report Type'
537
return fnct(cr, uid, ids, data, report_xml, context)
539
class ReportTypeException(Exception):
540
def __init__(self, value):
541
self.parameter = value
543
return repr(self.parameter)
545
#########################################################################
548
#from tools.config import config
550
#def load_from_file(path, dbname, key):
552
# expected_class = 'Parser'
555
# if path.find(config['addons_path'])==-1:
556
# filepath=config['addons_path']+os.path.sep+path
557
# filepath = os.path.normpath(filepath)
558
# if not os.path.lexists(filepath):
559
# filepath = os.path.normpath(config['root_path']+os.path.sep+path)
560
# sys.path.append(os.path.dirname(filepath))
561
# mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])
562
# mod_name = '%s_%s_%s' % (dbname,mod_name,key)
564
# if file_ext.lower() == '.py':
565
# py_mod = imp.load_source(mod_name, filepath)
567
# elif file_ext.lower() == '.pyc':
568
# py_mod = imp.load_compiled(mod_name, filepath)
570
# if expected_class in dir(py_mod):
571
# class_inst = py_mod.Parser
573
# except Exception, e:
576
#def load_from_source(source):
577
# expected_class = 'Parser'
578
# context = {'Parser':None}
580
# exec source in context
581
# return context['Parser']
582
# except Exception, e:
585
#def delete_report_service(name):
586
# name = 'report.%s' % name
587
# if netsvc.Service.exists( name ): # change for OpenERP 6.0 - Service class usage
588
# netsvc.Service.remove( name ) # change for OpenERP 6.0 - Service class usage
590
#def register_report(cr, name, model, tmpl_path, parser):
591
# name = 'report.%s' % name
592
# if netsvc.Service.exists( name ): # change for OpenERP 6.0 - Service class usage
593
# netsvc.Service.remove( name ) # change for OpenERP 6.0 - Service class usage
594
# Aeroo_report(cr, name, model, tmpl_path, parser=parser)
596
#old_register_all = report.interface.register_all
597
#def new_register_all(db):
598
# value = old_register_all(db)
602
########### Run OpenOffice service ###########
604
# from report_aeroo_ooo.report import OpenOffice_service
605
# except Exception, e:
606
# OpenOffice_service = False
608
# if OpenOffice_service:
609
# cr.execute("SELECT id, state FROM ir_module_module WHERE name='report_aeroo_ooo'")
610
# helper_module = cr.dictfetchone()
611
# helper_installed = helper_module['state']=='installed'
613
# if OpenOffice_service and helper_installed:
614
# cr.execute("SELECT host, port FROM oo_config")
615
# host, port = cr.fetchone()
617
# OpenOffice_service(cr, host, port)
618
# netsvc.Logger().notifyChannel('report_aeroo', netsvc.LOG_INFO, "OpenOffice connection successfully established")
619
# except DocumentConversionException, e:
620
# netsvc.Logger().notifyChannel('report_aeroo', netsvc.LOG_WARNING, e)
621
##############################################
623
# cr.execute("SELECT * FROM ir_act_report_xml WHERE report_type = 'aeroo' ORDER BY id") # change for OpenERP 6.0
624
# records = cr.dictfetchall()
625
# for record in records:
627
# if record['parser_state']=='loc' and record['parser_loc']:
628
# parser=load_from_file(record['parser_loc'], db.dbname, record['id']) or parser
629
# elif record['parser_state']=='def' and record['parser_def']:
630
# parser=load_from_source("from report import report_sxw\n"+record['parser_def']) or parser
631
# register_report(cr, record['report_name'], record['model'], record['report_rml'], parser)
635
#report.interface.register_all = new_register_all