1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 from lxml
import etree
33 from interface
import report_rml
44 DT_FORMAT
= '%Y-%m-%d'
45 DHM_FORMAT
= '%Y-%m-%d %H:%M:%S'
46 HM_FORMAT
= '%H:%M:%S'
75 class _format(object):
76 def set_value(self
, cr
, uid
, name
, object, field
, lang_obj
):
80 self
.lang_obj
= lang_obj
82 class _float_format(float, _format
):
83 def __init__(self
,value
):
84 super(_float_format
, self
).__init
__()
89 if hasattr(self
,'_field') and hasattr(self
._field
, 'digits') and self
._field
.digits
:
90 digits
= self
._field
.digits
[1]
91 if hasattr(self
, 'lang_obj'):
92 return self
.lang_obj
.format('%.' + str(digits
) + 'f', self
.name
, True)
95 class _int_format(int, _format
):
96 def __init__(self
,value
):
97 super(_int_format
, self
).__init
__()
98 self
.val
= value
and str(value
) or str(0)
101 if hasattr(self
,'lang_obj'):
102 return self
.lang_obj
.format('%.d', self
.name
, True)
105 class _date_format(str, _format
):
106 def __init__(self
,value
):
107 super(_date_format
, self
).__init
__()
108 self
.val
= value
and str(value
) or ''
112 if hasattr(self
,'name') and (self
.name
):
113 date
= mx
.DateTime
.strptime(self
.name
,DT_FORMAT
)
114 return date
.strftime(self
.lang_obj
.date_format
)
117 class _dttime_format(str, _format
):
118 def __init__(self
,value
):
119 super(_dttime_format
, self
).__init
__()
120 self
.val
= value
and str(value
) or ''
124 if hasattr(self
,'name') and self
.name
:
125 datetime
= mx
.DateTime
.strptime(self
.name
,DHM_FORMAT
)
126 return datetime
.strftime(self
.lang_obj
.date_format
+ " " + self
.lang_obj
.time_format
)
131 'float': _float_format
,
132 'date': _date_format
,
133 'integer': _int_format
,
134 'datetime' : _dttime_format
138 # Context: {'node': node.dom}
140 class browse_record_list(list):
141 def __init__(self
, lst
, context
):
142 super(browse_record_list
, self
).__init
__(lst
)
143 self
.context
= context
145 def __getattr__(self
, name
):
146 res
= browse_record_list([getattr(x
,name
) for x
in self
], self
.context
)
150 return "browse_record_list("+str(len(self
))+")"
152 class rml_parse(object):
153 def __init__(self
, cr
, uid
, name
, parents
=rml_parents
, tag
=rml_tag
, context
=None):
158 self
.pool
= pooler
.get_pool(cr
.dbname
)
159 user
= self
.pool
.get('res.users').browse(cr
, uid
, uid
)
160 self
.localcontext
= {
162 'company': user
.company_id
,
163 'repeatIn': self
.repeatIn
,
164 'setLang': self
.setLang
,
165 'setTag': self
.setTag
,
166 'removeParentNode': self
.removeParentNode
,
167 'format': self
.format
,
168 'formatLang': self
.formatLang
,
169 'logo' : user
.company_id
.logo
,
170 'lang' : user
.company_id
.partner_id
.lang
,
171 'translate' : self
._translate
,
172 'setHtmlImage' : self
.set_html_image
174 self
.localcontext
.update(context
)
175 self
.rml_header
= user
.company_id
.rml_header
176 self
.rml_header2
= user
.company_id
.rml_header2
177 self
.logo
= user
.company_id
.logo
180 self
.parents
= parents
182 self
._lang
_cache
= {}
184 self
.default_lang
= {}
185 self
.lang_dict_called
= False
186 self
._transl
_regex
= re
.compile('(\[\[.+?\]\])')
188 def setTag(self
, oldtag
, newtag
, attrs
=None):
191 def format(self
, text
, oldtag
=None):
194 def removeParentNode(self
, tag
=None):
195 raise Exception('Skip')
197 def set_html_image(self
,id,model
=None,field
=None,context
=None):
201 model
= 'ir.attachment'
204 res
= self
.pool
.get(model
).read(self
.cr
,self
.uid
,id)
207 elif model
=='ir.attachment' :
214 def setLang(self
, lang
):
215 if not lang
or self
.default_lang
.has_key(lang
):
218 self
.localcontext
['lang'] = lang
219 self
.lang_dict_called
= False
220 for obj
in self
.objects
:
221 obj
._context
['lang'] = lang
222 for table
in obj
._cache
:
223 for id in obj
._cache
[table
]:
224 self
._lang
_cache
.setdefault(obj
._context
['lang'], {}).setdefault(table
,
225 {}).update(obj
._cache
[table
][id])
226 if lang
in self
._lang
_cache \
227 and table
in self
._lang
_cache
[lang
] \
228 and id in self
._lang
_cache
[lang
][table
]:
229 obj
._cache
[table
][id] = self
._lang
_cache
[lang
][table
][id]
231 obj
._cache
[table
][id] = {'id': id}
233 def _get_lang_dict(self
):
234 pool_lang
= self
.pool
.get('res.lang')
235 lang
= self
.localcontext
.get('lang', 'en_US') or 'en_US'
236 lang_ids
= pool_lang
.search(self
.cr
,self
.uid
,[('code','=',lang
)])[0]
237 lang_obj
= pool_lang
.browse(self
.cr
,self
.uid
,lang_ids
)
238 self
.lang_dict
.update({'lang_obj':lang_obj
,'date_format':lang_obj
.date_format
,'time_format':lang_obj
.time_format
})
239 self
.default_lang
[lang
] = self
.lang_dict
.copy()
242 def formatLang(self
, value
, digits
=2, date
=False,date_time
=False, grouping
=True, monetary
=False, currency
=None):
243 if isinstance(value
, (str, unicode)) and not value
:
245 if not self
.lang_dict_called
:
246 self
._get
_lang
_dict
()
247 self
.lang_dict_called
= True
249 if date
or date_time
:
252 date_format
= self
.lang_dict
['date_format']
253 parse_format
= DT_FORMAT
255 date_format
= date_format
+ " " + self
.lang_dict
['time_format']
256 parse_format
= DHM_FORMAT
258 # filtering time.strftime('%Y-%m-%d')
259 if type(value
) == type(''):
260 parse_format
= DHM_FORMAT
264 if not isinstance(value
, time
.struct_time
):
266 date
= mx
.DateTime
.strptime(str(value
),parse_format
)
267 except:# sometimes it takes converted values into value, so we dont need conversion.
270 date
= mx
.DateTime
.DateTime(*(value
.timetuple()[:6]))
271 return date
.strftime(date_format
)
272 return self
.lang_dict
['lang_obj'].format('%.' + str(digits
) + 'f', value
, grouping
=grouping
, monetary
=monetary
)
274 def repeatIn(self
, lst
, name
,nodes_parent
=False):
277 ret_lst
.append({name
:id})
280 def _translate(self
,text
):
281 lang
= self
.localcontext
['lang']
282 if lang
and text
and not text
.isspace():
283 transl_obj
= self
.pool
.get('ir.translation')
284 piece_list
= self
._transl
_regex
.split(text
)
285 for pn
in range(len(piece_list
)):
286 if not self
._transl
_regex
.match(piece_list
[pn
]):
287 source_string
= piece_list
[pn
].replace('\n', ' ').strip()
288 if len(source_string
):
289 translated_string
= transl_obj
._get
_source
(self
.cr
, self
.uid
, self
.name
, 'rml', lang
, source_string
)
290 if translated_string
:
291 piece_list
[pn
] = piece_list
[pn
].replace(source_string
, translated_string
)
292 text
= ''.join(piece_list
)
295 def _add_header(self
, rml_dom
, header
=1):
297 rml_head
= self
.rml_header2
299 rml_head
= self
.rml_header
300 if self
.logo
and (rml_head
.find('company.logo')<0 or rml_head
.find('<image')<0) and rml_head
.find('<!--image')<0:
301 rml_head
= rml_head
.replace('<pageGraphics>','''<pageGraphics> <image x="10" y="26cm" height="70" width="90" >[[company.logo]] </image> ''')
302 if not self
.logo
and rml_head
.find('company.logo')>=0:
303 rml_head
= rml_head
.replace('<image','<!--image')
304 rml_head
= rml_head
.replace('</image>','</image-->')
305 head_dom
= etree
.XML(rml_head
)
306 for tag
in head_dom
.getchildren():
307 found
= rml_dom
.find('.//'+tag
.tag
)
308 if found
is not None:
309 if tag
.get('position'):
312 found
.getparent().replace(found
,tag
)
315 def set_context(self
, objects
, data
, ids
, report_type
= None):
316 self
.localcontext
['data'] = data
317 self
.localcontext
['objects'] = objects
320 self
.objects
= objects
322 if report_type
=='odt' :
323 self
.localcontext
.update({'name_space' :common
.odt_namespace
})
325 self
.localcontext
.update({'name_space' :common
.sxw_namespace
})
327 class report_sxw(report_rml
, preprocess
.report
):
328 def __init__(self
, name
, table
, rml
=False, parser
=rml_parse
, header
=True, store
=False):
329 report_rml
.__init
__(self
, name
, table
, rml
, '')
335 def getObjects(self
, cr
, uid
, ids
, context
):
336 table_obj
= pooler
.get_pool(cr
.dbname
).get(self
.table
)
337 return table_obj
.browse(cr
, uid
, ids
, list_class
=browse_record_list
, context
=context
, fields_process
=_fields_process
)
339 def create(self
, cr
, uid
, ids
, data
, context
=None):
340 pool
= pooler
.get_pool(cr
.dbname
)
341 ir_obj
= pool
.get('ir.actions.report.xml')
342 report_xml_ids
= ir_obj
.search(cr
, uid
,
343 [('report_name', '=', self
.name
[7:])], context
=context
)
345 report_xml
= ir_obj
.browse(cr
, uid
, report_xml_ids
[0], context
=context
)
348 rml
= tools
.file_open(self
.tmpl
, subdir
=None).read()
349 report_type
= data
.get('report_type', 'pdf')
351 def __init__(self
, *args
, **argv
):
352 for key
,arg
in argv
.items():
353 setattr(self
, key
, arg
)
354 report_xml
= a(title
=title
, report_type
=report_type
, report_rml_content
=rml
, name
=title
, attachment
=False, header
=self
.header
)
355 report_type
= report_xml
.report_type
356 if report_type
in ['sxw','odt']:
357 fnct
= self
.create_source_odt
358 elif report_type
in ['pdf','raw','txt','html']:
359 fnct
= self
.create_source_pdf
360 elif report_type
=='html2html':
361 fnct
= self
.create_source_html2html
363 raise Exception('Unknown Report Type: '+report_type
)
364 return fnct(cr
, uid
, ids
, data
, report_xml
, context
)
366 def create_source_odt(self
, cr
, uid
, ids
, data
, report_xml
, context
=None):
367 return self
.create_single_odt(cr
, uid
, ids
, data
, report_xml
, context
or {})
369 def create_source_html2html(self
, cr
, uid
, ids
, data
, report_xml
, context
=None):
370 return self
.create_single_html2html(cr
, uid
, ids
, data
, report_xml
, context
or {})
372 def create_source_pdf(self
, cr
, uid
, ids
, data
, report_xml
, context
=None):
375 pool
= pooler
.get_pool(cr
.dbname
)
376 attach
= report_xml
.attachment
378 objs
= self
.getObjects(cr
, uid
, ids
, context
)
381 aname
= eval(attach
, {'object':obj
, 'time':time
})
383 if report_xml
.attachment_use
and aname
and context
.get('attachment_use', True):
384 aids
= pool
.get('ir.attachment').search(cr
, uid
, [('datas_fname','=',aname
+'.pdf'),('res_model','=',self
.table
),('res_id','=',obj
.id)])
386 brow_rec
= pool
.get('ir.attachment').browse(cr
, uid
, aids
[0])
387 if not brow_rec
.datas
:
389 d
= base64
.decodestring(brow_rec
.datas
)
390 results
.append((d
,'pdf'))
392 result
= self
.create_single_pdf(cr
, uid
, [obj
.id], data
, report_xml
, context
)
395 name
= aname
+'.'+result
[1]
396 pool
.get('ir.attachment').create(cr
, uid
, {
398 'datas': base64
.encodestring(result
[0]),
400 'res_model': self
.table
,
406 import traceback
, sys
407 tb_s
= reduce(lambda x
, y
: x
+y
, traceback
.format_exception(sys
.exc_type
, sys
.exc_value
, sys
.exc_traceback
))
408 netsvc
.Logger().notifyChannel('report', netsvc
.LOG_ERROR
,str(e
))
409 results
.append(result
)
411 if results
[0][1]=='pdf':
412 from pyPdf
import PdfFileWriter
, PdfFileReader
413 output
= PdfFileWriter()
415 reader
= PdfFileReader(cStringIO
.StringIO(r
[0]))
416 for page
in range(reader
.getNumPages()):
417 output
.addPage(reader
.getPage(page
))
418 s
= cStringIO
.StringIO()
420 return s
.getvalue(), results
[0][1]
421 return self
.create_single_pdf(cr
, uid
, ids
, data
, report_xml
, context
)
423 def create_single_pdf(self
, cr
, uid
, ids
, data
, report_xml
, context
={}):
425 context
= context
.copy()
426 title
= report_xml
.name
427 rml
= report_xml
.report_rml_content
428 rml_parser
= self
.parser(cr
, uid
, self
.name2
, context
)
429 objs
= self
.getObjects(cr
, uid
, ids
, context
)
430 rml_parser
.set_context(objs
, data
, ids
, report_xml
.report_type
)
431 processed_rml
= self
.preprocess_rml(etree
.XML(rml
),report_xml
.report_type
)
432 if report_xml
.header
:
433 rml_parser
._add
_header
(processed_rml
)
435 logo
= base64
.decodestring(rml_parser
.logo
)
436 create_doc
= self
.generators
[report_xml
.report_type
]
437 pdf
= create_doc(etree
.tostring(processed_rml
),rml_parser
.localcontext
,logo
,title
.encode('utf8'))
438 return (pdf
, report_xml
.report_type
)
440 def create_single_odt(self
, cr
, uid
, ids
, data
, report_xml
, context
={}):
441 context
= context
.copy()
442 report_type
= report_xml
.report_type
443 context
['parents'] = sxw_parents
444 sxw_io
= StringIO
.StringIO(report_xml
.report_sxw_content
)
445 sxw_z
= zipfile
.ZipFile(sxw_io
, mode
='r')
446 rml
= sxw_z
.read('content.xml')
447 meta
= sxw_z
.read('meta.xml')
450 rml_parser
= self
.parser(cr
, uid
, self
.name2
, context
)
451 rml_parser
.parents
= sxw_parents
452 rml_parser
.tag
= sxw_tag
453 objs
= self
.getObjects(cr
, uid
, ids
, context
)
454 rml_parser
.set_context(objs
, data
, ids
,report_xml
.report_type
)
456 rml_dom_meta
= node
= etree
.XML(meta
)
457 elements
= node
.findall(rml_parser
.localcontext
['name_space']["meta"]+"user-defined")
459 if pe
.get(rml_parser
.localcontext
['name_space']["meta"]+"name"):
460 if pe
.get(rml_parser
.localcontext
['name_space']["meta"]+"name") == "Info 3":
461 pe
.getchildren()[0].text
=data
['id']
462 if pe
.get(rml_parser
.localcontext
['name_space']["meta"]+"name") == "Info 4":
463 pe
.getchildren()[0].text
=data
['model']
464 meta
= etree
.tostring(rml_dom_meta
)
466 rml_dom
= etree
.XML(rml
)
467 body
= rml_dom
.getchildren()[-1]
469 key1
= rml_parser
.localcontext
['name_space']["text"]+"p"
470 key2
= rml_parser
.localcontext
['name_space']["text"]+"drop-down"
471 for n
in rml_dom
.iterdescendants():
474 if report_type
== 'odt':
479 if de
.text
or de
.tail
:
480 pe
.text
= de
.text
or de
.tail
481 for cnd
in de
.getchildren():
482 if cnd
.text
or cnd
.tail
:
484 pe
.text
+= cnd
.text
or cnd
.tail
486 pe
.text
= cnd
.text
or cnd
.tail
493 if de
.text
or de
.tail
:
494 pe
.text
= de
.text
or de
.tail
496 text
= cnd
.get("{http://openoffice.org/2000/text}value",False)
498 if pe
.text
and text
.startswith('[['):
500 elif text
.startswith('[['):
505 rml_dom
= self
.preprocess_rml(rml_dom
,report_type
)
506 create_doc
= self
.generators
[report_type
]
507 odt
= etree
.tostring(create_doc(rml_dom
, rml_parser
.localcontext
))
508 sxw_z
= zipfile
.ZipFile(sxw_io
, mode
='a')
509 sxw_z
.writestr('content.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
511 sxw_z
.writestr('meta.xml', "<?xml version='1.0' encoding='UTF-8'?>" + \
514 if report_xml
.header
:
515 #Add corporate header/footer
516 rml
= tools
.file_open(os
.path
.join('base', 'report', 'corporate_%s_header.xml' % report_type
)).read()
517 rml_parser
= self
.parser(cr
, uid
, self
.name2
, context
)
518 rml_parser
.parents
= sxw_parents
519 rml_parser
.tag
= sxw_tag
520 objs
= self
.getObjects(cr
, uid
, ids
, context
)
521 rml_parser
.set_context(objs
, data
, ids
, report_xml
.report_type
)
522 rml_dom
= self
.preprocess_rml(etree
.XML(rml
),report_type
)
523 create_doc
= self
.generators
[report_type
]
524 odt
= create_doc(rml_dom
,rml_parser
.localcontext
)
525 if report_xml
.header
:
526 rml_parser
._add
_header
(odt
)
527 odt
= etree
.tostring(odt
)
528 sxw_z
.writestr('styles.xml',"<?xml version='1.0' encoding='UTF-8'?>" + \
531 final_op
= sxw_io
.getvalue()
533 return (final_op
, report_type
)
535 def create_single_html2html(self
, cr
, uid
, ids
, data
, report_xml
, context
={}):
536 context
= context
.copy()
538 context
['parents'] = html_parents
540 html
= report_xml
.report_rml_content
541 html_parser
= self
.parser(cr
, uid
, self
.name2
, context
)
542 html_parser
.parents
= html_parents
543 html_parser
.tag
= sxw_tag
544 objs
= self
.getObjects(cr
, uid
, ids
, context
)
545 html_parser
.set_context(objs
, data
, ids
, report_type
)
547 html_dom
= etree
.HTML(html
)
548 html_dom
= self
.preprocess_rml(html_dom
,'html2html')
550 create_doc
= self
.generators
['html2html']
551 html
= etree
.tostring(create_doc(html_dom
, html_parser
.localcontext
))
553 return (html
.replace('&','&').replace('<', '<').replace('>', '>').replace('</br>',''), report_type
)