1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
21 from lxml import etree
28 from datetime import datetime
32 from interface import report_rml
43 DT_FORMAT = '%Y-%m-%d'
44 DHM_FORMAT = '%Y-%m-%d %H:%M:%S'
45 HM_FORMAT = '%H:%M:%S'
74 class _format(object):
75 def set_value(self, cr, uid, name, object, field, lang_obj):
79 self.lang_obj = lang_obj
81 class _float_format(float, _format):
82 def __init__(self,value):
83 super(_float_format, self).__init__()
88 if hasattr(self,'_field') and getattr(self._field, 'digits', None):
89 digits = self._field.digits[1]
90 if hasattr(self, 'lang_obj'):
91 return self.lang_obj.format('%.' + str(digits) + 'f', self.name, True)
94 class _int_format(int, _format):
95 def __init__(self,value):
96 super(_int_format, self).__init__()
97 self.val = value and str(value) or str(0)
100 if hasattr(self,'lang_obj'):
101 return self.lang_obj.format('%.d', self.name, True)
104 class _date_format(str, _format):
105 def __init__(self,value):
106 super(_date_format, self).__init__()
107 self.val = value and str(value) or ''
111 if getattr(self,'name', None):
112 date = datetime.strptime(self.name, DT_FORMAT)
113 return date.strftime(self.lang_obj.date_format)
116 class _dttime_format(str, _format):
117 def __init__(self,value):
118 super(_dttime_format, self).__init__()
119 self.val = value and str(value) or ''
122 if self.val and getattr(self,'name', None):
123 return datetime.strptime(self.name, DHM_FORMAT)\
124 .strftime("%s %s"%(self.lang_obj.date_format,
125 self.lang_obj.time_format))
130 'float': _float_format,
131 'date': _date_format,
132 'integer': _int_format,
133 'datetime' : _dttime_format
137 # Context: {'node': node.dom}
139 class browse_record_list(list):
140 def __init__(self, lst, context):
141 super(browse_record_list, self).__init__(lst)
142 self.context = context
144 def __getattr__(self, name):
145 res = browse_record_list([getattr(x,name) for x in self], self.context)
149 return "browse_record_list("+str(len(self))+")"
151 class rml_parse(object):
152 def __init__(self, cr, uid, name, parents=rml_parents, tag=rml_tag, context=None):
157 self.pool = pooler.get_pool(cr.dbname)
158 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
159 self.localcontext = {
161 'company': user.company_id,
162 'repeatIn': self.repeatIn,
163 'setLang': self.setLang,
164 'setTag': self.setTag,
165 'removeParentNode': self.removeParentNode,
166 'format': self.format,
167 'formatLang': self.formatLang,
168 'logo' : user.company_id.logo,
169 'lang' : user.company_id.partner_id.lang,
170 'translate' : self._translate,
171 '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):
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
261 # if (not date_time):
264 if not isinstance(value, time.struct_time):
266 date = datetime.datetime(str(value), parse_format)
267 except:# sometimes it takes converted values into value, so we dont need conversion.
270 date = 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)
307 found = rml_dom.find('.//'+tag.tag)
308 if found is not None and len(found):
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
362 elif report_type=='mako2html':
363 fnct = self.create_source_mako2html
365 raise 'Unknown Report Type'
366 fnct_ret = fnct(cr, uid, ids, data, report_xml, context)
371 def create_source_odt(self, cr, uid, ids, data, report_xml, context=None):
372 return self.create_single_odt(cr, uid, ids, data, report_xml, context or {})
374 def create_source_html2html(self, cr, uid, ids, data, report_xml, context=None):
375 return self.create_single_html2html(cr, uid, ids, data, report_xml, context or {})
377 def create_source_mako2html(self, cr, uid, ids, data, report_xml, context=None):
378 return self.create_single_mako2html(cr, uid, ids, data, report_xml, context or {})
380 def create_source_pdf(self, cr, uid, ids, data, report_xml, context=None):
383 pool = pooler.get_pool(cr.dbname)
384 attach = report_xml.attachment
386 objs = self.getObjects(cr, uid, ids, context)
389 aname = eval(attach, {'object':obj, 'time':time})
391 if report_xml.attachment_use and aname and context.get('attachment_use', True):
392 aids = pool.get('ir.attachment').search(cr, uid, [('datas_fname','=',aname+'.pdf'),('res_model','=',self.table),('res_id','=',obj.id)])
394 brow_rec = pool.get('ir.attachment').browse(cr, uid, aids[0])
395 if not brow_rec.datas:
397 d = base64.decodestring(brow_rec.datas)
398 results.append((d,'pdf'))
400 result = self.create_single_pdf(cr, uid, [obj.id], data, report_xml, context)
405 name = aname+'.'+result[1]
406 pool.get('ir.attachment').create(cr, uid, {
408 'datas': base64.encodestring(result[0]),
410 'res_model': self.table,
416 import traceback, sys
417 tb_s = reduce(lambda x, y: x+y, traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
418 netsvc.Logger().notifyChannel('report', netsvc.LOG_ERROR,str(e))
419 results.append(result)
421 if results[0][1]=='pdf':
422 from pyPdf import PdfFileWriter, PdfFileReader
423 output = PdfFileWriter()
425 reader = PdfFileReader(cStringIO.StringIO(r[0]))
426 for page in range(reader.getNumPages()):
427 output.addPage(reader.getPage(page))
428 s = cStringIO.StringIO()
430 return s.getvalue(), results[0][1]
431 return self.create_single_pdf(cr, uid, ids, data, report_xml, context)
433 def create_single_pdf(self, cr, uid, ids, data, report_xml, context=None):
437 context = context.copy()
438 title = report_xml.name
439 rml = report_xml.report_rml_content
440 # if no rml file is found
443 rml_parser = self.parser(cr, uid, self.name2, context=context)
444 objs = self.getObjects(cr, uid, ids, context)
445 rml_parser.set_context(objs, data, ids, report_xml.report_type)
446 processed_rml = self.preprocess_rml(etree.XML(rml),report_xml.report_type)
447 if report_xml.header:
448 rml_parser._add_header(processed_rml)
450 logo = base64.decodestring(rml_parser.logo)
451 create_doc = self.generators[report_xml.report_type]
452 pdf = create_doc(etree.tostring(processed_rml),rml_parser.localcontext,logo,title.encode('utf8'))
453 return (pdf, report_xml.report_type)
455 def create_single_odt(self, cr, uid, ids, data, report_xml, context=None):
458 context = context.copy()
459 report_type = report_xml.report_type
460 context['parents'] = sxw_parents
461 sxw_io = StringIO.StringIO(report_xml.report_sxw_content)
462 sxw_z = zipfile.ZipFile(sxw_io, mode='r')
463 rml = sxw_z.read('content.xml')
464 meta = sxw_z.read('meta.xml')
467 rml_parser = self.parser(cr, uid, self.name2, context=context)
468 rml_parser.parents = sxw_parents
469 rml_parser.tag = sxw_tag
470 objs = self.getObjects(cr, uid, ids, context)
471 rml_parser.set_context(objs, data, ids,report_xml.report_type)
473 rml_dom_meta = node = etree.XML(meta)
474 elements = node.findall(rml_parser.localcontext['name_space']["meta"]+"user-defined")
476 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name"):
477 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 3":
478 pe[0].text=data['id']
479 if pe.get(rml_parser.localcontext['name_space']["meta"]+"name") == "Info 4":
480 pe[0].text=data['model']
481 meta = etree.tostring(rml_dom_meta, encoding='utf-8',
482 xml_declaration=True)
484 rml_dom = etree.XML(rml)
487 key1 = rml_parser.localcontext['name_space']["text"]+"p"
488 key2 = rml_parser.localcontext['name_space']["text"]+"drop-down"
489 for n in rml_dom.iterdescendants():
492 if report_type == 'odt':
497 if de.text or de.tail:
498 pe.text = de.text or de.tail
500 if cnd.text or cnd.tail:
502 pe.text += cnd.text or cnd.tail
504 pe.text = cnd.text or cnd.tail
511 if de.text or de.tail:
512 pe.text = de.text or de.tail
514 text = cnd.get("{http://openoffice.org/2000/text}value",False)
516 if pe.text and text.startswith('[['):
518 elif text.startswith('[['):
523 rml_dom = self.preprocess_rml(rml_dom,report_type)
524 create_doc = self.generators[report_type]
525 odt = etree.tostring(create_doc(rml_dom, rml_parser.localcontext),
526 encoding='utf-8', xml_declaration=True)
527 sxw_z = zipfile.ZipFile(sxw_io, mode='a')
528 sxw_z.writestr('content.xml', odt)
529 sxw_z.writestr('meta.xml', meta)
531 if report_xml.header:
532 #Add corporate header/footer
533 rml = tools.file_open(os.path.join('base', 'report', 'corporate_%s_header.xml' % report_type)).read()
534 rml_parser = self.parser(cr, uid, self.name2, context=context)
535 rml_parser.parents = sxw_parents
536 rml_parser.tag = sxw_tag
537 objs = self.getObjects(cr, uid, ids, context)
538 rml_parser.set_context(objs, data, ids, report_xml.report_type)
539 rml_dom = self.preprocess_rml(etree.XML(rml),report_type)
540 create_doc = self.generators[report_type]
541 odt = create_doc(rml_dom,rml_parser.localcontext)
542 if report_xml.header:
543 rml_parser._add_header(odt)
544 odt = etree.tostring(odt, encoding='utf-8',
545 xml_declaration=True)
546 sxw_z.writestr('styles.xml', odt)
548 final_op = sxw_io.getvalue()
550 return (final_op, report_type)
552 def create_single_html2html(self, cr, uid, ids, data, report_xml, context=None):
555 context = context.copy()
557 context['parents'] = html_parents
559 html = report_xml.report_rml_content
560 html_parser = self.parser(cr, uid, self.name2, context=context)
561 html_parser.parents = html_parents
562 html_parser.tag = sxw_tag
563 objs = self.getObjects(cr, uid, ids, context)
564 html_parser.set_context(objs, data, ids, report_type)
566 html_dom = etree.HTML(html)
567 html_dom = self.preprocess_rml(html_dom,'html2html')
569 create_doc = self.generators['html2html']
570 html = etree.tostring(create_doc(html_dom, html_parser.localcontext))
572 return (html.replace('&','&').replace('<', '<').replace('>', '>').replace('</br>',''), report_type)
574 def create_single_mako2html(self, cr, uid, ids, data, report_xml, context=None):
575 mako_html = report_xml.report_rml_content
576 html_parser = self.parser(cr, uid, self.name2, context)
577 objs = self.getObjects(cr, uid, ids, context)
578 html_parser.set_context(objs, data, ids, 'html')
579 create_doc = self.generators['makohtml2html']
580 html = create_doc(mako_html,html_parser.localcontext)