0f770a8760a616f7364a5e3ec60db9c57d742569
[odoo/odoo.git] / bin / report / custom.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
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.
12 #
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.
17 #
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/>.
20 #
21 ##############################################################################
22
23 import os
24 import time
25 import netsvc
26
27 import tools
28 import print_xml
29 import render
30 from interface import report_int
31 import common
32 from osv.osv import except_osv
33 from osv.orm import browse_null
34 from osv.orm import browse_record_list
35 import pooler
36 from xml.dom import minidom
37 from pychart import *
38 import misc
39 import cStringIO
40 from lxml import etree
41
42 class external_pdf(render.render):
43     def __init__(self, pdf):
44         render.render.__init__(self)
45         self.pdf = pdf
46         self.output_type='pdf'
47     def _render(self):
48         return self.pdf
49
50 theme.use_color = 1
51
52
53 #TODO: devrait heriter de report_rml a la place de report_int 
54 # -> pourrait overrider que create_xml a la place de tout create
55 # heuu, ca marche pas ds tous les cas car graphs sont generes en pdf directment
56 # par pychart, et on passe donc pas par du rml
57 class report_custom(report_int):
58     def __init__(self, name):
59         report_int.__init__(self, name)
60     #
61     # PRE:
62     #    fields = [['address','city'],['name'], ['zip']]
63     #    conditions = [[('zip','==','3'),(,)],(,),(,)] #same structure as fields
64     #    row_canvas = ['Rue', None, None]
65     # POST:
66     #    [ ['ville','name','zip'] ]
67     #
68     def _row_get(self, cr, uid, objs, fields, conditions, row_canvas=None, group_by=None):
69         result = []
70         tmp = []
71         for obj in objs:
72             tobreak = False
73             for cond in conditions:
74                 if cond and cond[0]:
75                     c = cond[0]
76                     temp = c[0](eval('obj.'+c[1]))
77                     if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
78                         tobreak = True
79             if tobreak:
80                 break
81             levels = {}
82             row = []
83             for i in range(len(fields)):
84                 if not fields[i]:
85                     row.append(row_canvas and row_canvas[i])
86                     if row_canvas[i]:
87                         row_canvas[i]=False
88                 elif len(fields[i])==1:
89                     if not isinstance(obj, browse_null):
90                         row.append(str(eval('obj.'+fields[i][0])))
91                     else:
92                         row.append(None)
93                 else:
94                     row.append(None)
95                     levels[fields[i][0]]=True
96             if not levels:
97                 result.append(row)
98             else:
99                 # Process group_by data first
100                 key = []
101                 if group_by != None and fields[group_by] != None:
102                     if fields[group_by][0] in levels.keys():
103                         key.append(fields[group_by][0])
104                     for l in levels.keys():
105                         if l != fields[group_by][0]:
106                             key.append(l)
107                 else:
108                     key = levels.keys()
109                 for l in key:
110                     objs = eval('obj.'+l)
111                     if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
112                         objs = [objs]
113                     field_new = []
114                     cond_new = []
115                     for f in range(len(fields)):
116                         if (fields[f] and fields[f][0])==l:
117                             field_new.append(fields[f][1:])
118                             cond_new.append(conditions[f][1:])
119                         else:
120                             field_new.append(None)
121                             cond_new.append(None)
122                     if len(objs):
123                         result += self._row_get(cr, uid, objs, field_new, cond_new, row, group_by)
124                     else:
125                         result.append(row)
126         return result 
127
128
129     def create(self, cr, uid, ids, datas, context=None):
130         if not context:
131             context={}
132         self.pool = pooler.get_pool(cr.dbname)
133         report = self.pool.get('ir.report.custom').browse(cr, uid, [datas['report_id']])[0]
134         datas['model'] = report.model_id.model
135         if report.menu_id:
136             ids = self.pool.get(report.model_id.model).search(cr, uid, [])
137             datas['ids'] = ids
138
139         service = netsvc.LocalService("object_proxy")
140         report_id = datas['report_id']
141         report = service.execute(cr.dbname, uid, 'ir.report.custom', 'read', [report_id], context=context)[0]
142         fields = service.execute(cr.dbname, uid, 'ir.report.custom.fields', 'read', report['fields_child0'], context=context)
143
144         fields.sort(lambda x,y : x['sequence'] - y['sequence'])
145
146         if report['field_parent']:
147             parent_field = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [report['field_parent'][0]],['model'])
148         model_name = service.execute(cr.dbname, uid, 'ir.model', 'read', [report['model_id'][0]], ['model'],context=context)[0]['model']
149
150         fct = {}
151         fct['id'] = lambda x : x
152         fct['gety'] = lambda x: x.split('-')[0]
153         fct['in'] = lambda x: x.split(',')
154         new_fields = []
155         new_cond = []
156         for f in fields:
157             row = []
158             cond = []
159             for i in range(4):
160                 field_child = f['field_child'+str(i)]
161                 if field_child:
162                     row.append(
163                         service.execute(cr.dbname, uid, 
164                                         'ir.model.fields', 'read', [field_child[0]],
165                                         ['name'], context=context)[0]['name']
166                     )
167                     if f['fc'+str(i)+'_operande']:
168                         fct_name = 'id'
169                         cond_op =  f['fc'+str(i)+'_op']
170                         if len(f['fc'+str(i)+'_op'].split(',')) == 2:
171                             cond_op =  f['fc'+str(i)+'_op'].split(',')[1]
172                             fct_name = f['fc'+str(i)+'_op'].split(',')[0]
173                         cond.append((fct[fct_name], f['fc'+str(i)+'_operande'][1], cond_op, f['fc'+str(i)+'_condition']))
174                     else:
175                         cond.append(None)
176             new_fields.append(row)
177             new_cond.append(cond)
178         objs = self.pool.get(model_name).browse(cr, uid, ids)
179
180         # Group by
181         groupby = None
182         idx = 0
183         for f in fields:
184             if f['groupby']:
185                 groupby = idx
186             idx += 1
187
188
189         results = []
190         if report['field_parent']:
191             level = []
192             def build_tree(obj, level, depth):
193                 res = self._row_get(cr, uid,[obj], new_fields, new_cond)
194                 level.append(depth)
195                 new_obj = eval('obj.'+report['field_parent'][1])
196                 if not isinstance(new_obj, list) :
197                     new_obj = [new_obj]
198                 for o in  new_obj:
199                     if not isinstance(o, browse_null):
200                         res += build_tree(o, level, depth+1)
201                 return res
202
203             for obj in objs:
204                 results += build_tree(obj, level, 0)
205         else:
206             results = self._row_get(cr, uid,objs, new_fields, new_cond, group_by=groupby)
207
208         fct = {
209             'calc_sum': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0),
210             'calc_avg': lambda l: reduce(lambda x,y: float(x)+float(y), filter(None, l), 0) / (len(filter(None, l)) or 1.0),
211             'calc_max': lambda l: reduce(lambda x,y: max(x,y), [(i or 0.0) for i in l], 0),
212             'calc_min': lambda l: reduce(lambda x,y: min(x,y), [(i or 0.0) for i in l], 0),
213             'calc_count': lambda l: len(filter(None, l)),
214             'False': lambda l: '\r\n'.join(filter(None, l)),
215             'groupby': lambda l: reduce(lambda x,y: x or y, l)
216         }
217         new_res = []
218
219         prev = None
220         if groupby != None:
221             res_dic = {}
222             for line in results:
223                 if not line[groupby] and prev in res_dic:
224                     res_dic[prev].append(line)
225                 else:
226                     prev = line[groupby]
227                     if res_dic.has_key(line[groupby]):
228                         res_dic[line[groupby]].append(line)
229                     else:
230                         res_dic[line[groupby]] = []
231                         res_dic[line[groupby]].append(line)
232             #we use the keys in results since they are ordered, whereas in res_dic.heys() they aren't
233             for key in filter(None, [x[groupby] for x in results]):
234                 row = []
235                 for col in range(len(fields)):
236                     if col == groupby:
237                         row.append(fct['groupby'](map(lambda x: x[col], res_dic[key])))
238                     else:
239                         row.append(fct[str(fields[col]['operation'])](map(lambda x: x[col], res_dic[key])))
240                 new_res.append(row)
241             results = new_res
242         
243         if report['type']=='table':
244             if report['field_parent']:
245                 res = self._create_tree(uid, ids, report, fields, level, results, context)
246             else:
247                 sort_idx = 0
248                 for idx in range(len(fields)):
249                     if fields[idx]['name'] == report['sortby']:
250                         sort_idx = idx
251                         break
252                 try :
253                     results.sort(lambda x,y : cmp(float(x[sort_idx]),float(y[sort_idx])))
254                 except :
255                     results.sort(lambda x,y : cmp(x[sort_idx],y[sort_idx]))
256                 if report['limitt']:
257                     results = results[:int(report['limitt'])]
258                 res = self._create_table(uid, ids, report, fields, None, results, context)
259         elif report['type'] in ('pie','bar', 'line'):
260             results2 = []
261             prev = False
262             for r in results:
263                 row = []
264                 for j in range(len(r)):
265                     if j == 0 and not r[j]:
266                         row.append(prev)
267                     elif j == 0 and r[j]:
268                         prev = r[j]
269                         row.append(r[j])
270                     else:
271                         try:
272                             row.append(float(r[j]))
273                         except:
274                             row.append(r[j])
275                 results2.append(row)
276             if report['type']=='pie':
277                 res = self._create_pie(cr,uid, ids, report, fields, results2, context)
278             elif report['type']=='bar':
279                 res = self._create_bars(cr,uid, ids, report, fields, results2, context)
280             elif report['type']=='line':
281                 res = self._create_lines(cr,uid, ids, report, fields, results2, context)
282         return (self.obj.get(), 'pdf')
283
284     def _create_tree(self, uid, ids, report, fields, level, results, context):
285         pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
286         if report['print_orientation']=='landscape':
287             pageSize=[pageSize[1],pageSize[0]]
288
289         impl = minidom.getDOMImplementation()
290         new_doc = impl.createDocument(None, "report", None)
291         
292         # build header
293         config = new_doc.createElement("config")
294
295         def _append_node(name, text):
296             n = new_doc.createElement(name)
297             t = new_doc.createTextNode(text)
298             n.appendChild(t)
299             config.appendChild(n)
300
301         _append_node('date', time.strftime('%d/%m/%Y'))
302         _append_node('PageFormat', '%s' % report['print_format'])
303         _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
304         _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
305         _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
306
307         length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
308         count = 0
309         for f in fields:
310             if not f['width']: count+=1
311         for f in fields:
312             if not f['width']:
313                 f['width']=round((float(length)/count)-0.5)
314
315         _append_node('tableSize', '%s' %  ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
316         _append_node('report-header', '%s' % (report['title'],))
317         _append_node('report-footer', '%s' % (report['footer'],))
318
319         new_doc.childNodes[0].appendChild(config)
320         header = new_doc.createElement("header")
321         
322         for f in fields:
323             field = new_doc.createElement("field")
324             field_txt = new_doc.createTextNode('%s' % (f['name'],))
325             field.appendChild(field_txt)
326             header.appendChild(field)
327         
328         new_doc.childNodes[0].appendChild(header)
329
330         lines = new_doc.createElement("lines")
331         level.reverse()
332         for line in results:
333             shift = level.pop()
334             node_line = new_doc.createElement("row")
335             prefix = '+'
336             for f in range(len(fields)):
337                 col = new_doc.createElement("col")
338                 if f == 0:
339                     col.setAttribute('para','yes')
340                     col.setAttribute('tree','yes')
341                     col.setAttribute('space',str(3*shift)+'mm')
342                 if line[f] != None:
343                     txt = new_doc.createTextNode(prefix+str(line[f]) or '')
344                 else:
345                     txt = new_doc.createTextNode('/')
346                 col.appendChild(txt)
347                 node_line.appendChild(col)
348                 prefix = ''
349             lines.appendChild(node_line)
350             
351         new_doc.childNodes[0].appendChild(lines)
352
353         transform = etree.XSLT(
354             etree.parse(os.path.join(tools.config['root_path'],
355                                      'addons/base/report/custom_new.xsl')))
356         rml = etree.tostring(
357             transform(etree.fromstring(new_doc.toxml())))
358
359         self.obj = render.rml(rml)
360         self.obj.render()
361         return True
362
363
364     def _create_lines(self, cr, uid, ids, report, fields, results, context):
365         service = netsvc.LocalService("object_proxy")
366         pdf_string = cStringIO.StringIO()
367         can = canvas.init(fname=pdf_string, format='pdf')
368         
369         can.show(80,380,'/16/H'+report['title'])
370         
371         ar = area.T(size=(350,350),
372         #x_coord = category_coord.T(['2005-09-01','2005-10-22'],0),
373         x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
374         y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
375         
376         process_date = {}
377         process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
378         process_date['M'] = lambda x : x.split('-')[1]
379         process_date['Y'] = lambda x : x.split('-')[0]
380
381         order_date = {}
382         order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
383         order_date['M'] = lambda x : x
384         order_date['Y'] = lambda x : x
385
386         abscissa = []
387         tmp = {}
388         
389         idx = 0 
390         date_idx = None
391         fct = {}
392         for f in fields:
393             field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
394             if field_id:
395                 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
396                 if type[0]['ttype'] == 'date':
397                     date_idx = idx
398                     fct[idx] = process_date[report['frequency']] 
399                 else:
400                     fct[idx] = lambda x : x
401             else:
402                 fct[idx] = lambda x : x
403             idx+=1
404
405         # plots are usually displayed year by year
406         # so we do so if the first field is a date
407         data_by_year = {}
408         if date_idx != None:
409             for r in results:
410                 key = process_date['Y'](r[date_idx])
411                 if not data_by_year.has_key(key):
412                     data_by_year[key] = []
413                 for i in range(len(r)):
414                     r[i] = fct[i](r[i])
415                 data_by_year[key].append(r)
416         else:
417             data_by_year[''] = results
418
419         idx0 = 0
420         nb_bar = len(data_by_year)*(len(fields)-1)
421         colors = map(lambda x:line_style.T(color=x), misc.choice_colors(nb_bar))
422         abscissa = {}
423         for line in data_by_year.keys():
424             fields_bar = []
425             # sum data and save it in a list. An item for a fields
426             for d in data_by_year[line]:
427                 for idx in range(len(fields)-1):
428                     fields_bar.append({})
429                     if fields_bar[idx].has_key(d[0]):
430                         fields_bar[idx][d[0]] += d[idx+1]
431                     else:
432                         fields_bar[idx][d[0]] = d[idx+1]
433             for idx  in range(len(fields)-1):
434                 data = {}
435                 for k in fields_bar[idx].keys():
436                     if data.has_key(k):
437                         data[k] += fields_bar[idx][k]
438                     else:
439                         data[k] = fields_bar[idx][k]
440                 data_cum = []
441                 prev = 0.0
442                 keys = data.keys()
443                 keys.sort()
444                 # cumulate if necessary
445                 for k in keys:
446                     data_cum.append([k, float(data[k])+float(prev)])
447                     if fields[idx+1]['cumulate']:
448                         prev += data[k]
449                 idx0 = 0
450                 plot = line_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, line_style=colors[idx0*(len(fields)-1)+idx])
451                 ar.add_plot(plot)
452                 abscissa.update(fields_bar[idx])
453                 idx0 += 1
454         
455         abscissa = map(lambda x : [x, None], abscissa)
456         ar.x_coord = category_coord.T(abscissa,0)
457         ar.draw(can)
458
459         can.close()
460         self.obj = external_pdf(pdf_string.getvalue())
461         self.obj.render()
462         pdf_string.close()
463         return True
464
465
466
467     def _create_bars(self, cr, uid, ids, report, fields, results, context):
468         service = netsvc.LocalService("object_proxy")
469         pdf_string = cStringIO.StringIO()
470         can = canvas.init(fname=pdf_string, format='pdf')
471         
472         can.show(80,380,'/16/H'+report['title'])
473         
474         process_date = {}
475         process_date['D'] = lambda x : reduce(lambda xx,yy : xx+'-'+yy,x.split('-')[1:3])
476         process_date['M'] = lambda x : x.split('-')[1]
477         process_date['Y'] = lambda x : x.split('-')[0]
478
479         order_date = {}
480         order_date['D'] = lambda x : time.mktime((2005,int(x.split('-')[0]), int(x.split('-')[1]),0,0,0,0,0,0))
481         order_date['M'] = lambda x : x
482         order_date['Y'] = lambda x : x
483
484         ar = area.T(size=(350,350),
485             x_axis = axis.X(label = fields[0]['name'], format="/a-30{}%s"),
486             y_axis = axis.Y(label = ', '.join(map(lambda x : x['name'], fields[1:]))))
487
488         idx = 0 
489         date_idx = None
490         fct = {}
491         for f in fields:
492             field_id = (f['field_child3'] and f['field_child3'][0]) or (f['field_child2'] and f['field_child2'][0]) or (f['field_child1'] and f['field_child1'][0]) or (f['field_child0'] and f['field_child0'][0])
493             if field_id:
494                 type = service.execute(cr.dbname, uid, 'ir.model.fields', 'read', [field_id],['ttype'])
495                 if type[0]['ttype'] == 'date':
496                     date_idx = idx
497                     fct[idx] = process_date[report['frequency']] 
498                 else:
499                     fct[idx] = lambda x : x
500             else:
501                 fct[idx] = lambda x : x
502             idx+=1
503         
504         # plot are usually displayed year by year
505         # so we do so if the first field is a date
506         data_by_year = {}
507         if date_idx != None:
508             for r in results:
509                 key = process_date['Y'](r[date_idx])
510                 if not data_by_year.has_key(key):
511                     data_by_year[key] = []
512                 for i in range(len(r)):
513                     r[i] = fct[i](r[i])
514                 data_by_year[key].append(r)
515         else:
516             data_by_year[''] = results
517
518
519         nb_bar = len(data_by_year)*(len(fields)-1)
520         colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(nb_bar))
521         
522         abscissa = {}
523         for line in data_by_year.keys():
524             fields_bar = []
525             # sum data and save it in a list. An item for a fields
526             for d in data_by_year[line]:
527                 for idx in range(len(fields)-1):
528                     fields_bar.append({})
529                     if fields_bar[idx].has_key(d[0]):
530                         fields_bar[idx][d[0]] += d[idx+1]
531                     else:
532                         fields_bar[idx][d[0]] = d[idx+1]
533             for idx  in range(len(fields)-1):
534                 data = {}
535                 for k in fields_bar[idx].keys():
536                     if data.has_key(k):
537                         data[k] += fields_bar[idx][k]
538                     else:
539                         data[k] = fields_bar[idx][k]
540                 data_cum = []
541                 prev = 0.0
542                 keys = data.keys()
543                 keys.sort()
544                 # cumulate if necessary
545                 for k in keys:
546                     data_cum.append([k, float(data[k])+float(prev)])
547                     if fields[idx+1]['cumulate']:
548                         prev += data[k]
549                         
550                 idx0 = 0
551                 plot = bar_plot.T(label=fields[idx+1]['name']+' '+str(line), data = data_cum, cluster=(idx0*(len(fields)-1)+idx,nb_bar), fill_style=colors[idx0*(len(fields)-1)+idx])
552                 ar.add_plot(plot)
553                 abscissa.update(fields_bar[idx])
554             idx0 += 1
555         abscissa = map(lambda x : [x, None], abscissa)
556         abscissa.sort()
557         ar.x_coord = category_coord.T(abscissa,0)
558         ar.draw(can)
559
560         can.close()
561         self.obj = external_pdf(pdf_string.getvalue())
562         self.obj.render()
563         pdf_string.close()
564         return True
565
566     def _create_pie(self, cr, uid, ids, report, fields, results, context):
567         pdf_string = cStringIO.StringIO()
568         can = canvas.init(fname=pdf_string, format='pdf')
569         ar = area.T(size=(350,350), legend=legend.T(),
570                     x_grid_style = None, y_grid_style = None)
571         colors = map(lambda x:fill_style.Plain(bgcolor=x), misc.choice_colors(len(results)))
572
573         if reduce(lambda x,y : x+y, map(lambda x : x[1],results)) == 0.0:
574             raise except_osv(_('Error'), _("The sum of the data (2nd field) is null.\nWe can't draw a pie chart !"))
575
576         plot = pie_plot.T(data=results, arc_offsets=[0,10,0,10],
577                           shadow = (2, -2, fill_style.gray50),
578                           label_offset = 25,
579                           arrow_style = arrow.a3,
580                           fill_styles=colors)
581         ar.add_plot(plot)
582         ar.draw(can)
583         can.close()
584         self.obj = external_pdf(pdf_string.getvalue())
585         self.obj.render()
586         pdf_string.close()
587         return True
588
589     def _create_table(self, uid, ids, report, fields, tree, results, context):
590         pageSize=common.pageSize.get(report['print_format'], [210.0,297.0])
591         if report['print_orientation']=='landscape':
592             pageSize=[pageSize[1],pageSize[0]]
593
594         impl = minidom.getDOMImplementation()
595         new_doc = impl.createDocument(None, "report", None)
596         
597         # build header
598         config = new_doc.createElement("config")
599
600         def _append_node(name, text):
601             n = new_doc.createElement(name)
602             t = new_doc.createTextNode(text)
603             n.appendChild(t)
604             config.appendChild(n)
605
606         _append_node('date', time.strftime('%d/%m/%Y'))
607         _append_node('PageSize', '%.2fmm,%.2fmm' % tuple(pageSize))
608         _append_node('PageFormat', '%s' % report['print_format'])
609         _append_node('PageWidth', '%.2f' % (pageSize[0] * 2.8346,))
610         _append_node('PageHeight', '%.2f' %(pageSize[1] * 2.8346,))
611
612         length = pageSize[0]-30-reduce(lambda x,y:x+(y['width'] or 0), fields, 0)
613         count = 0
614         for f in fields:
615             if not f['width']: count+=1
616         for f in fields:
617             if not f['width']:
618                 f['width']=round((float(length)/count)-0.5)
619
620         _append_node('tableSize', '%s' %  ','.join(map(lambda x: '%.2fmm' % (x['width'],), fields)))
621         _append_node('report-header', '%s' % (report['title'],))
622         _append_node('report-footer', '%s' % (report['footer'],))
623
624         new_doc.childNodes[0].appendChild(config)
625         header = new_doc.createElement("header")
626         
627         for f in fields:
628             field = new_doc.createElement("field")
629             field_txt = new_doc.createTextNode('%s' % (f['name'],))
630             field.appendChild(field_txt)
631             header.appendChild(field)
632         
633         new_doc.childNodes[0].appendChild(header)
634
635         lines = new_doc.createElement("lines")
636         for line in results:
637             node_line = new_doc.createElement("row")
638             for f in range(len(fields)):
639                 col = new_doc.createElement("col")
640                 col.setAttribute('tree','no')
641                 if line[f] != None:
642                     txt = new_doc.createTextNode(str(line[f] or ''))
643                 else:
644                     txt = new_doc.createTextNode('/')
645                 col.appendChild(txt)
646                 node_line.appendChild(col)
647             lines.appendChild(node_line)    
648             
649         new_doc.childNodes[0].appendChild(lines)
650
651         transform = etree.XSLT(
652             etree.parse(os.path.join(tools.config['root_path'],
653                                      'addons/base/report/custom_new.xsl')))
654         rml = etree.tostring(
655             transform(etree.fromstring(new_doc.toxml())))
656
657         self.obj = render.rml(rml)
658         self.obj.render()
659         return True
660 report_custom('report.custom')
661
662
663 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
664