merged
[odoo/odoo.git] / addons / base_report_creator / base_report_creator.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 string
24 import time
25 import tools
26 from osv import fields,osv,orm
27 from tools.translate import _
28
29 #class ir_model_fields(osv.osv):
30 #   _inherit = 'ir.model.fields'
31 #   def _get_models(self, cr, uid, model_name, level=1):
32 #       if not level:
33 #           return []
34 #       result = [model_name]
35 #       print model_name
36 #       for field,data in self.pool.get(model_name).fields_get(cr, uid).items():
37 #           if data.get('relation', False):
38 #               result += self._get_models(cr, uid, data['relation'], level-1)
39 #       return result
40 #   def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None):
41 #       if context and ('model_id' in context):
42 #           model_name = self.pool.get("ir.model").browse(cr, uid, context['model_id'], context).model
43 #           models = self._get_models(cr, uid, model_name, context.get('model_level',2))
44 #           models = map(lambda x: self.pool.get('ir.model').search(cr, uid, [('model','=',x)])[0], models)
45 #           args.append(('model_id','in',models))
46 #           print args
47 #       return super(ir_model_fields, self).search(cr, uid, args, offset, limit, order, context)
48 #ir_model_fields()
49
50
51 class report_creator(osv.osv):
52     _name = "base_report_creator.report"
53     _description = "Report"
54     model_set_id = False
55     #
56     # Should request only used fields
57     #
58     def fields_get(self, cr, user, fields=None, context=None):
59         if (not context) or 'report_id' not in context:
60             return super(report_creator, self).fields_get(cr, user, fields, context)
61         report = self.browse(cr, user, context['report_id'])
62         models = {}
63         for model in report.model_ids:
64             models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
65         fields = {}
66         i = 0
67         for f in report.field_ids:
68             fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
69             i+=1
70         return fields
71
72     #
73     # Should Call self.fields_get !
74     #
75     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
76         if (not context) or 'report_id' not in context:
77             return super(report_creator, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
78         report = self.browse(cr, user, context['report_id'])
79         models = {}
80         for model in report.model_ids:
81             models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
82         fields = {}
83         i = 0
84         for f in report.field_ids:
85             fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
86             i+=1
87         arch = '<?xml version="1.0" encoding="utf-8"?>\n'
88         if view_type=='graph':
89             arch +='<graph string="%s" type="%s" orientation="%s">' % (report.name, report.view_graph_type,report.view_graph_orientation)
90             for val in ('x','y'):
91                 i = 0
92                 for f in report.field_ids:
93                     if f.graph_mode==val:
94                         arch += '<field name="%s" select="1"/>' % ('field'+str(i),)
95                     i+=1
96         elif view_type=='calendar':
97             required_types = ['date_start','date_delay','color']
98             set_dict = {'view_type':view_type,'string':report.name}
99             temp_list = []
100             i=0
101             for f in report.field_ids:
102                 if f.calendar_mode and f.calendar_mode in required_types:
103                     set_dict[f.calendar_mode] = 'field'+str(i)
104                     i+=1
105                     del required_types[required_types.index(f.calendar_mode)]
106                     
107                 else:
108                     temp_list.append('''<field name="%(name)s" select="1"/>''' % {'name':'field'+str(i)})
109                     i+=1
110             arch += '''<%(view_type)s string="%(string)s" date_start="%(date_start)s" ''' %set_dict
111             if set_dict.get('date_delay',False):
112                 arch +=''' date_delay="%(date_delay)s"  '''%set_dict
113             
114             if set_dict.get('date_stop',False):
115                 arch +=''' date_stop="%(date_stop)s" '''%set_dict      
116             
117             if set_dict.get('color',False):
118                 arch +=''' color="%(color)s"'''%set_dict
119             arch += '''>'''
120             arch += ''.join(temp_list)
121         else:
122             arch += '<%s string="%s">\n' % (view_type, report.name)
123             i = 0
124             for f in report.field_ids:
125                 arch += '<field name="%s" select="1"/>' % ('field'+str(i),)
126                 i+=1
127         arch += '</%s>' % (view_type,)
128         result = {
129             'arch': arch,
130             'fields': fields
131         }
132         result['toolbar'] = {
133             'print': [],
134             'action': [],
135             'relate': []
136         }
137         return result
138
139     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
140         if (not context) or 'report_id' not in context:
141             return super(report_creator, self).read(cr, user, ids, fields, context, load)
142         ctx = context or {}
143         wp = ''
144         if self.model_set_id:
145             wp = [self._id_get(cr, user, context['report_id'], context)+(' in (%s)' % (','.join(map(lambda x: "'"+str(x)+"'",ids))))]
146         report = self._sql_query_get(cr, user, [context['report_id']], 'sql_query', None, ctx, where_plus = wp)
147         sql_query = report[context['report_id']]
148         cr.execute(sql_query)
149         res = cr.dictfetchall()
150         fields_get = self.fields_get(cr,user,None,context)
151         for r in res:
152             for k in r:
153                 r[k] = r[k] or False
154                 field_dict = fields_get.get(k)
155                 field_type = field_dict and field_dict.get('type',False) or False 
156                 if field_type and field_type == 'many2one':
157                     if r[k]==False:
158                         continue
159                     related_name = self.pool.get(field_dict.get('relation')).name_get(cr,user,[r[k]],context)[0]
160                     r[k] = related_name 
161         return res
162
163     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
164         if (not context) or 'report_id' not in context:
165             return super(report_creator, self).search(cr, user, args, offset, limit, order, context, count)
166         report = self.browse(cr, user, context['report_id'])
167         i = 0
168         fields = {}
169         for f in report.field_ids:
170             fields['field'+str(i)] = (f.field_id.model, f.field_id.name)
171             i+=1
172         newargs = []
173         newargs2 = []
174         for a in args:
175             res =  self.pool.get(fields[a[0]][0])._where_calc(cr, user, [[fields[a[0]][1],a[1],a[2]]], active_test=False, context=context)
176             newargs+=res[0]
177             newargs2+=res[1]
178         ctx = context or {}
179         ctx['getid'] = True
180         report = self._sql_query_get(cr, user, [context['report_id']], 'sql_query', None, ctx, where_plus=newargs, limit=limit, offset=offset)
181         query = report[context['report_id']]
182         cr.execute(query, newargs2)
183         result = cr.fetchall()
184         return map(lambda x: x[0], result)
185
186     def _path_get(self,cr, uid, models, filter_ids=[]):
187 #       ret_str = """   sale_order_line
188 #   left join sale_order on (sale_order_line.order_id=sale_order.id)
189 #   left join res_partner on (res_partner.id=sale_order.partner_id)"""
190 #       where_list = []
191 #       for filter_id in filter_ids:
192 #           where_list.append(filter_id.expression)
193 #       if where_list:
194 #           ret_str+="\nwhere\n\t"+" and\n\t".join(where_list)
195         self.model_set_id = False
196         model_dict = {}
197         from_list = []
198         where_list = []
199         filter_list = []
200         for model in models:            
201             model_dict[model.model] = self.pool.get(model.model)._table
202         
203         model_list = model_dict.keys()
204         reference_model_dict = {}
205         for model in model_dict:
206             from_list.append(model_dict.get(model))
207             rest_list = model_dict.keys()
208             rest_list.remove(model)
209             model_pool = self.pool.get(model)
210             fields_get = model_pool.fields_get(cr,uid)
211             fields_filter = dict(filter(lambda x:x[1].get('relation',False) 
212                                         and x[1].get('relation') in rest_list 
213                                         and x[1].get('type')=='many2one' 
214                                         and not (isinstance(model_pool._columns[x[0]],fields.function) or isinstance(model_pool._columns[x[0]],fields.related)), fields_get.items()))
215             if fields_filter:
216                 model in model_list and model_list.remove(model)
217             model_count = reference_model_dict.get(model,False)
218             if model_count:
219                 reference_model_dict[model] = model_count +1
220             else:
221                 reference_model_dict[model] = 1
222             for k,v in fields_filter.items():
223                 v.get('relation') in model_list and model_list.remove(v.get('relation'))
224                 relation_count = reference_model_dict.get(v.get('relation'),False)
225                 if relation_count:
226                     reference_model_dict[v.get('relation')] = relation_count+1
227                 else:
228                     reference_model_dict[v.get('relation')]=1
229                    
230                 str_where = model_dict.get(model)+"."+ k + "=" + model_dict.get(v.get('relation'))+'.id'
231                 where_list.append(str_where)
232         if reference_model_dict:
233             self.model_set_id = model_dict.get(reference_model_dict.keys()[reference_model_dict.values().index(min(reference_model_dict.values()))])
234         if model_list and not len(model_dict.keys()) == 1:
235             raise osv.except_osv(_('No Related Models!!'),_('These is/are model(s) (%s) in selection which is/are not related to any other model') % ','.join(model_list))
236         
237         if filter_ids and where_list<>[]:
238             filter_list.append(' and ')
239             filter_list.append(' ')
240         
241         for filter_id in filter_ids:
242             filter_list.append(filter_id.expression)
243             filter_list.append(' ')
244             filter_list.append(filter_id.condition)
245         
246         if len(from_list) == 1 and filter_ids:
247             from_list.append(' ')
248             ret_str = "\n where \n".join(from_list)
249         else:
250             ret_str = ",\n".join(from_list)
251         
252             
253         if where_list:
254             ret_str+="\n where \n"+" and\n".join(where_list)
255             ret_str = ret_str.strip()
256         if filter_list:
257             ret_str +="\n".join(filter_list)
258             if ret_str.endswith('and'):
259                 ret_str = ret_str[0:len(ret_str)-3]
260             if ret_str.endswith('or'):
261                 ret_str = ret_str[0:len(ret_str)-2]
262             ret_str = ret_str.strip()    
263         return ret_str
264
265     def _id_get(self, cr, uid, id, context):
266 #       return 'min(sale_order_line.id)'
267         return self.model_set_id and 'min('+self.model_set_id+'.id)'
268
269     def _sql_query_get(self, cr, uid, ids, prop, unknow_none, context, where_plus=[], limit=None, offset=None):
270         result = {}
271         for obj in self.browse(cr, uid, ids):
272             fields = []
273             groupby = []
274             i = 0
275             for f in obj.field_ids:
276                 t = self.pool.get(f.field_id.model_id.model)._table
277                 if f.group_method == 'group':
278                     fields.append('\t'+t+'.'+f.field_id.name+' as field'+str(i))
279                     groupby.append(t+'.'+f.field_id.name)
280                 else:
281                     fields.append('\t'+f.group_method+'('+t+'.'+f.field_id.name+')'+' as field'+str(i))
282                 i+=1
283             models = self._path_get(cr, uid, obj.model_ids, obj.filter_ids)
284             check = self._id_get(cr, uid, ids[0], context)
285             if check<>False:
286                 fields.insert(0,(check+' as id'))
287             
288             if models:
289                 result[obj.id] = """select
290     %s
291     from
292     %s
293                 """ % (',\n'.join(fields), models)
294                 if groupby:
295                     result[obj.id] += "group by\n\t"+', '.join(groupby)
296                 if where_plus:
297                     result[obj.id] += "\nhaving \n\t"+"\n\t and ".join(where_plus)
298                 if limit:
299                     result[obj.id] += " limit "+str(limit)
300                 if offset:
301                     result[obj.id] += " offset "+str(offset)
302             else:
303                 result[obj.id] = False
304         return result
305     
306     _columns = {
307         'name': fields.char('Report Name',size=64, required=True),
308         'type': fields.selection([('list','Rows And Columns Report'),], 'Report Type',required=True),#('sum','Summation Report')
309         'active': fields.boolean('Active'),
310         'view_type1': fields.selection([('form','Form'),('tree','Tree'),('graph','Graph'),('calendar','Calendar')], 'First View', required=True),
311         'view_type2': fields.selection([('','/'),('form','Form'),('tree','Tree'),('graph','Graph'),('calendar','Calendar')], 'Second View'),
312         'view_type3': fields.selection([('','/'),('form','Form'),('tree','Tree'),('graph','Graph'),('calendar','Calendar')], 'Third View'),
313         'view_graph_type': fields.selection([('pie','Pie Chart'),('bar','Bar Chart')], 'Graph Type', required=True),
314         'view_graph_orientation': fields.selection([('horz','Horizontal'),('vert','Vertical')], 'Graph Orientation', required=True),
315         'model_ids': fields.many2many('ir.model', 'base_report_creator_report_model_rel', 'report_id','model_id', 'Reported Objects'),
316         'field_ids': fields.one2many('base_report_creator.report.fields', 'report_id', 'Fields to Display'),
317         'filter_ids': fields.one2many('base_report_creator.report.filter', 'report_id', 'Filters'),
318         'state': fields.selection([('draft','Draft'),('valid','Valid')], 'Status', required=True),
319         'sql_query': fields.function(_sql_query_get, method=True, type="text", string='SQL Query', store=True),
320         'group_ids': fields.many2many('res.groups', 'base_report_creator_group_rel','report_id','group_id','Authorized Groups'),
321     }
322     _defaults = {
323         'type': lambda *args: 'list',
324         'state': lambda *args: 'draft',
325         'active': lambda *args: True,
326         'view_type1': lambda *args: 'tree',
327         'view_type2': lambda *args: 'graph',
328         'view_graph_type': lambda *args: 'bar',
329         'view_graph_orientation': lambda *args: 'horz',
330     }
331     def _function_field(self, cr, uid, ids):
332         this_objs = self.browse(cr, uid, ids)
333         for obj in this_objs:
334             for fld in obj.field_ids:
335                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
336                 if (isinstance(model_column,fields.function) or isinstance(model_column,fields.related)) and not model_column.store:
337                     return False 
338         return True
339     
340     def _aggregation_error(self, cr, uid, ids):
341         aggregate_columns = ('integer','float')
342         apply_functions = ('sum','min','max','avg','count')
343         this_objs = self.browse(cr, uid, ids)
344         for obj in this_objs:
345             for fld in obj.field_ids:
346                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]                
347                 if model_column._type not in aggregate_columns and fld.group_method in apply_functions:
348                     return False 
349         return True
350     
351     def _calander_view_error(self, cr, uid, ids):
352 #       required_types = ['date_start','date_delay','color'] 
353         required_types = []
354         this_objs = self.browse(cr, uid, ids)
355         for obj in this_objs:
356             if obj.view_type1=='calendar' or obj.view_type2=='calendar' or obj.view_type3=='calendar': 
357                 for fld in obj.field_ids:
358                     model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
359                     if fld.calendar_mode in ('date_start','date_end') and model_column._type not in ('date','datetime'):
360                         return False
361                     elif fld.calendar_mode=='date_delay' and model_column._type not in ('int','float'):
362                         return False
363                     else:
364                         required_types.append(fld.calendar_mode)
365                 if 'date_start' not in required_types:
366                     return False     
367         return True
368     
369     _constraints = [
370         (_function_field, 'You can not display field which are not stored in Database.', ['field_ids']),
371         (_aggregation_error, 'You can apply aggregate function to the non calculated field.', ['field_ids']),
372         (_calander_view_error, "You must have to give calendar view's color,start date and delay.", ['field_ids']),
373     ]
374 report_creator()
375
376 class report_creator_field(osv.osv):
377     _name = "base_report_creator.report.fields"
378     _description = "Display Fields"
379     _rec_name = 'field_id'
380     _order = "sequence,id"
381     _columns = {
382         'sequence': fields.integer('Sequence'),
383         'field_id': fields.many2one('ir.model.fields', 'Field'),
384         'report_id': fields.many2one('base_report_creator.report','Report', on_delete='cascade'),
385         'group_method': fields.selection([('group','Grouped'),('sum','Sum'),('min','Minimum'),('count','Count'),('max','Maximum'),('avg','Average')], 'Grouping Method', required=True),
386         'graph_mode': fields.selection([('','/'),('x','X Axis'),('y','Y Axis')], 'Graph Mode'),
387         'calendar_mode': fields.selection([('','/'),('date_start','Starting Date'),('date_end','Ending Date'),('date_delay','Delay'),('date_stop','End Date'),('color','Unique Colors')], 'Calendar Mode'),
388     }
389     _defaults = {
390         'group_method': lambda *args: 'group',
391         'graph_mode': lambda *args: '',
392     }
393 report_creator_field()
394
395 class report_creator_filter(osv.osv):
396     _name = "base_report_creator.report.filter"
397     _description = "Report Filters"
398     _columns = {
399         'name': fields.char('Filter Name',size=64, required=True),
400         'expression': fields.text('Value', required=True,help='Provide an expression for the field based on which you want to filter the records.\n e.g. res_partner.id=3'),
401         'report_id': fields.many2one('base_report_creator.report','Report', on_delete='cascade'),
402         'condition' : fields.selection([('and','AND'),('or','OR')], 'Condition')
403     }
404     _defaults = {
405         'condition': lambda *args: 'and',
406     }
407 report_creator_filter()
408 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
409