e3b7fdb1c4daced9c2834726b7adee1786e483cb
[odoo/odoo.git] / addons / base_report_creator / base_report_creator.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from osv import fields, osv
23 from tools.translate import _
24 from tools import ustr
25
26 class report_result(osv.osv):
27     """
28     Report Creator
29     """
30     _name = "base_report_creator_report.result"
31     _description = "Report"
32     #
33     # Should request only used fields
34     #
35
36     def fields_get(self, cr, user, fields=None, context=None):
37         """
38         Get Fields.
39         @param cr: the current row, from the database cursor,
40         @param user: the current user’s ID for security checks,
41         @param Fields: List of field of customer reports form.
42         @return: Dictionary of Fields
43         """
44         if context is None:
45             context = {}
46
47         data = context and context.get('report_id', False) or False
48         if (not context) or 'report_id' not in context:
49             return super(report_result, self).fields_get(cr, user, fields, context)
50         if data:
51             report = self.pool.get('base_report_creator.report').browse(cr, user, data,context=context)
52             models = {}
53         #Start Loop
54             for model in report.model_ids:
55                 models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
56             #End Loop
57             fields = {}
58             i = 0
59             for f in report.field_ids:
60                 if f.field_id.model:
61                     fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
62                     i += 1
63                 else:
64                     fields['column_count'] = {'readonly': True, 'type': 'integer', 'string': 'Count', 'size': 64, 'name': 'column_count'}
65             return fields
66
67     def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
68         """
69         Overrides orm field_view_get.
70         @param cr: the current row, from the database cursor,
71         @param user: the current user’s ID for security checks,
72         @return: Dictionary of Fields, arch and toolbar.
73         """
74         if context is None:
75             context = {}
76
77         data = context and context.get('report_id', False) or False
78         if (not context) or 'report_id' not in context:
79             return super(report_result, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
80         report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context)
81         models = {}
82         for model in report.model_ids:
83             models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
84         fields = {}
85         i = 0
86         for f in report.field_ids:
87             if f.field_id.model:
88                 fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
89                 i += 1
90             else:
91                 fields['column_count'] = {'readonly': True, 'type': 'integer', 'string': 'Count', 'size': 64, 'name': 'column_count'}
92
93         arch = '<?xml version="1.0"?>'
94         if view_type == 'graph':
95             orientation_eval = {'horz':'horizontal','vert' :'vertical'}
96             orientation = eval(report.view_graph_orientation,orientation_eval)
97             arch +='<graph string="%s" type="%s" orientation="%s">' % (report.name, report.view_graph_type, orientation)
98             i = 0
99             for val in ('x','y'):
100                 for f in report.field_ids:
101                     if f.graph_mode == val:
102                         if f.field_id.model:
103                             arch += '<field name="%s" select="1"/>' % ('field'+str(i),)
104                             i += 1
105                         else:
106                             arch += '<field name="%s" select="1"/>' % ('column_count',)
107
108         elif view_type == 'calendar':
109             required_types = ['date_start', 'date_delay', 'color']
110             set_dict = {'view_type':view_type, 'string':report.name}
111             temp_list = []
112             i = 0
113             for f in report.field_ids:
114                 if f.calendar_mode and f.calendar_mode in required_types:
115                     if f.field_id.model:
116                         field_cal = 'field'+str(i)
117                         i += 1
118                     else:
119                         field_cal = 'column_count'
120                     set_dict[f.calendar_mode] = field_cal
121                     del required_types[required_types.index(f.calendar_mode)]
122
123                 else:
124                     if f.field_id.model:
125                         temp_list.append('''<field name = "%(name)s" select = "1"/>''' % {'name': 'field' + str(i)})
126                         i += 1
127                     else:
128                         temp_list.append('''<field name="%(name)s" select="1"/>''' % {'name':'column_count'})
129
130             arch += '''<% (view_type)s string = "%(string)s" date_start = "%(date_start)s" ''' % set_dict
131             if set_dict.get('date_delay', False):
132                 arch += ''' date_delay = "%(date_delay)s"  ''' % set_dict
133
134             if set_dict.get('date_stop', False):
135                 arch += ''' date_stop="%(date_stop)s" '''%set_dict
136
137             if set_dict.get('color', False):
138                 arch += ''' color="%(color)s"'''%set_dict
139             arch += '''>'''
140             arch += ''.join(temp_list)
141         else:
142             arch += '<%s string="%s">' % (view_type, report.name)
143             i = 0
144             for f in report.field_ids:
145                 if f.field_id.model:
146                     arch += '<field name="%s"/>' % ('field' + str(i),)
147                     i += 1
148                 else:
149                     arch += '<field name="%s"/>' % ('column_count',)
150         arch += '</%s>' % (view_type,)
151         result = {
152             'arch': arch,
153             'fields': fields
154         }
155         result['toolbar'] = {
156             'print': [],
157             'action': [],
158             'relate': []
159         }
160         return result
161
162     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
163         """
164         overrides  orm Read method.Read List of fields for report creator.
165         @param cr: the current row, from the database cursor,
166         @param user: the current user’s ID for security checks,
167         @param ids: List of report creator's id.
168         @param fields: List of fields.
169         @return: List of Dictionary of form [{‘name_of_the_field’: value, ...}, ...]
170         """
171         if context is None:
172             context = {}
173         report_id = context.get('active_id', False)
174         report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context)
175         cr.execute(report.sql_query)
176         result = cr.dictfetchall()
177         return result
178
179     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
180         """
181         overrides  orm search method.
182         @param cr: the current row, from the database cursor,
183         @param user: the current user’s ID for security checks,
184         @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...].
185         @return: List of id
186         """
187         if context is None:
188             context = {}
189         context_id = context.get('report_id', False)
190
191         if (not context) or 'report_id' not in context:
192             return {}
193         if context_id:
194             report = self.pool.get('base_report_creator.report').browse(cr, user, context_id, context=context)
195             i = 0
196             fields = {}
197             for f in report.field_ids:
198                 if f.field_id.model:
199                     fields['field'+str(i)] = (f.field_id.model, f.field_id.name)
200                     i += 1
201                 else:
202                     fields['column_count'] = (False, 'Count')
203             newargs = []
204             for a in args:
205                 if fields[a[0]][0]:
206                     model = self.pool.get(fields[a[0]][0])
207                     if a[1] in ('like', 'ilike', 'not like', 'not ilike'):
208                         right = '%%%s%%' % (a[2],)
209                     else:
210                         right = a[2]
211                     newargs.append(str(model._table+"."+fields[a[0]][1] + " " +a[1] + " '" + right)+"'")
212             ctx = context or {}
213             ctx['getid'] = True
214             report_pool = self.pool.get('base_report_creator.report')
215             reports = report_pool._sql_query_get(cr, user, [context_id], 'sql_query', None, ctx, where_plus=newargs, limit=limit, offset=offset)
216             cr.execute(reports[context_id])
217             result = cr.fetchall()
218             return map(lambda x: x[0], result)
219
220 report_result()
221
222
223 class report_creator(osv.osv):
224     """
225     Report Creator
226     """
227     _name = "base_report_creator.report"
228     _description = "Report"
229     model_set_id = False
230     #
231     # Should request only used fields
232     #
233     def export_data(self, cr, uid, ids, fields_to_export, context=None):
234         if context is None:
235             context = {}
236         data_l = self.read(cr, uid, ids, ['sql_query'], context=context)
237         final_datas = []
238         #start Loop
239         for record in data_l:
240             datas = []
241             for key in fields_to_export:
242                 value = record.get(key,'')
243                 if isinstance(value, tuple):
244                     datas.append(ustr(value[1]))
245                 else:
246                     datas.append(ustr(value))
247             final_datas += [datas]
248             #End Loop
249         return {'datas': final_datas}
250
251
252     def _path_get(self, cr, uid, models, filter_ids=[]):
253         """
254         @param cr: the current row, from the database cursor,
255         @param uid: the current user’s ID for security checks,
256         @param models: List of object.
257         """
258         self.model_set_id = False
259         model_dict = {}
260         from_list = []
261         where_list = []
262         filter_list = []
263         for model in models:
264             model_dict[model.model] = self.pool.get(model.model)._table
265
266         model_list = model_dict.keys()
267         reference_model_dict = {}
268         for model in model_dict:
269             from_list.append(model_dict.get(model))
270             rest_list = model_dict.keys()
271             rest_list.remove(model)
272             model_pool = self.pool.get(model)
273             fields_get = model_pool.fields_get(cr, uid)
274             model_columns = {}
275
276             def _get_inherit_fields(obj):
277                 pool_model = self.pool.get(obj)
278                 #Adding the columns of the model itself
279                 model_columns.update(pool_model._columns)
280                 #Adding the columns of its _inherits
281                 for record in pool_model._inherits.keys():
282                     _get_inherit_fields(record)
283
284             _get_inherit_fields(model)
285
286             fields_filter = dict(filter(lambda x:x[1].get('relation', False)
287                                         and x[1].get('relation') in rest_list
288                                         and x[1].get('type') == 'many2one'
289                                         and not (isinstance(model_columns[x[0]], fields.function) or isinstance(model_columns[x[0]], fields.related)), fields_get.items()))
290             if fields_filter:
291                 model in model_list and model_list.remove(model)
292             model_count = reference_model_dict.get(model, False)
293             if model_count:
294                 reference_model_dict[model] = model_count +1
295             else:
296                 reference_model_dict[model] = 1
297             for k, v in fields_filter.items():
298                 v.get('relation') in model_list and model_list.remove(v.get('relation'))
299                 relation_count = reference_model_dict.get(v.get('relation'), False)
300                 if relation_count:
301                     reference_model_dict[v.get('relation')] = relation_count+1
302                 else:
303                     reference_model_dict[v.get('relation')]=1
304                 if k in self.pool.get(model)._columns:
305                     str_where = model_dict.get(model)+"."+ k + "=" + model_dict.get(v.get('relation'))+'.id'
306                     where_list.append(str_where)
307
308         if reference_model_dict:
309             self.model_set_id = model_dict.get(reference_model_dict.keys()[reference_model_dict.values().index(min(reference_model_dict.values()))])
310         if model_list and not len(model_dict.keys()) == 1:
311             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))
312
313         if filter_ids and where_list <> []:
314             filter_list.append(' and ')
315             filter_list.append(' ')
316
317         for filter_id in filter_ids:
318             filter_list.append(filter_id.expression)
319             filter_list.append(' ')
320             filter_list.append(filter_id.condition)
321
322         if len(from_list) == 1 and filter_ids:
323             from_list.append(' ')
324             ret_str = "\n where \n".join(from_list)
325         else:
326             ret_str = ",\n".join(from_list)
327
328         if where_list:
329             where_list = list(set(where_list))
330             ret_str += "\n where \n"+" and\n".join(where_list)
331             ret_str = ret_str.strip()
332         if filter_list:
333             ret_str += "\n".join(filter_list)
334             if ret_str.endswith('and'):
335                 ret_str = ret_str[0:len(ret_str)-3]
336             if ret_str.endswith('or'):
337                 ret_str = ret_str[0:len(ret_str)-2]
338             ret_str = ret_str.strip()
339
340         return ret_str % {'uid': uid}
341
342     def _id_get(self, cr, uid, id, context):
343         """
344         Get Model id
345         """
346         return self.model_set_id and 'min('+self.model_set_id+'.id)'
347
348     def _sql_query_get(self, cr, uid, ids, prop, unknow_none, context=None, where_plus=[], limit=None, offset=None):
349         """
350         Get sql query which return on sql_query field.
351         @return: Dictionary of sql query.
352         """
353         result = {}
354         for obj in self.browse(cr, uid, ids, context=context):
355             fields = []
356             groupby = []
357             i = 0
358             for f in obj.field_ids:
359                 # Allowing to use count(*)
360                 if not f.field_id.model and f.group_method == 'count':
361                     fields.insert(0, ('count(*) as column_count'))
362                     continue
363                 t = self.pool.get(f.field_id.model_id.model)._table
364                 if f.group_method == 'group':
365                     fields.append('\t'+t+'.'+f.field_id.name+' as field'+str(i))
366                     groupby.append(t+'.'+f.field_id.name)
367                 else:
368                     fields.append('\t'+f.group_method+'('+t+'.'+f.field_id.name+')'+' as field'+str(i))
369
370                 i += 1
371             models = self._path_get(cr, uid, obj.model_ids, obj.filter_ids)
372             check = self._id_get(cr, uid, ids[0], context)
373             if check<>False:
374                 fields.insert(0, (check + ' as id'))
375
376             if models:
377                 result[obj.id] = """select
378     %s
379     from
380     %s
381                 """ % (',\n'.join(fields), models)
382                 if groupby:
383                     result[obj.id] += "group by\n\t"+', '.join(groupby)
384                 if where_plus:
385                     result[obj.id] += "\nhaving \n\t"+"\n\t and ".join(where_plus)
386                 if limit:
387                     result[obj.id] += " limit "+str(limit)
388                 if offset:
389                     result[obj.id] += " offset "+str(offset)
390             else:
391                 result[obj.id] = False
392         return result
393
394     _columns = {
395         'name': fields.char('Report Name', size=64, required=True),
396         'type': fields.selection([('list', 'Rows And Columns Report'), ], 'Report Type', required=True), #('sum','Summation Report')
397         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the report without removing it."),
398         'view_type1': fields.selection([('form', 'Form'),
399                                         ('tree', 'Tree'),
400                                         ('graph', 'Graph'),
401                                         ('calendar', 'Calendar')], 'First View', required=True),
402         'view_type2': fields.selection([('', '/'),
403                                         ('form', 'Form'),
404                                         ('tree', 'Tree'),
405                                         ('graph', 'Graph'),
406                                         ('calendar', 'Calendar')], 'Second View'),
407         'view_type3': fields.selection([('', '/'),
408                                         ('form', 'Form'),
409                                         ('tree', 'Tree'),
410                                         ('graph', 'Graph'),
411                                         ('calendar', 'Calendar')], 'Third View'),
412         'view_graph_type': fields.selection([('pie', 'Pie Chart'),
413                                              ('bar', 'Bar Chart')], 'Graph Type', required=True),
414         'view_graph_orientation': fields.selection([('horz', 'Horizontal'),
415                                                     ('vert', 'Vertical')], 'Graph Orientation', required=True),
416         'model_ids': fields.many2many('ir.model', 'base_report_creator_report_model_rel', 'report_id', 'model_id', 'Reported Objects'),
417         'field_ids': fields.one2many('base_report_creator.report.fields', 'report_id', 'Fields to Display'),
418         'filter_ids': fields.one2many('base_report_creator.report.filter', 'report_id', 'Filters'),
419         'sql_query': fields.function(_sql_query_get, method=True, type="text", string='SQL Query', store=True),
420         'group_ids': fields.many2many('res.groups', 'base_report_creator_group_rel', 'report_id', 'group_id', 'Authorized Groups'),
421         'menu_id': fields.many2one('ir.ui.menu', "Menu", readonly=True),
422     }
423     _defaults = {
424         'type': lambda *args: 'list',
425         'active': lambda *args: True,
426         'view_type1': lambda *args: 'tree',
427         'view_type2': lambda *args: 'graph',
428         'view_graph_type': lambda *args: 'bar',
429         'view_graph_orientation': lambda *args: 'horz',
430     }
431
432     def open_report(self, cr, uid, ids, context=None):
433         """
434         This Function opens base creator report  view
435         @param self: The object pointer
436         @param cr: the current row, from the database cursor,
437         @param uid: the current user’s ID for security checks,
438         @param ids: List of report open's IDs
439         @param context: A standard dictionary for contextual values
440         @return : Dictionary value for base creator report form
441         """
442         if context is None:
443             context = {}
444
445         rep = self.browse(cr, uid, ids, context=context)
446         if not rep:
447             return False
448
449         rep = rep[0]
450         view_mode = rep.view_type1
451         if rep.view_type2:
452             view_mode += ',' + rep.view_type2
453         if rep.view_type3:
454             view_mode += ',' + rep.view_type3
455         value = {
456             'name': rep.name,
457             'view_type': 'form',
458             'view_mode': view_mode,
459             'res_model': 'base_report_creator_report.result',
460             'type': 'ir.actions.act_window',
461             'context': "{'report_id':%d}" % (rep.id,),
462             'nodestroy': True,
463         }
464
465         return value
466
467     def _function_field(self, cr, uid, ids, context=None):
468         """
469         constraints function which specify that
470                 not display field which are not stored in Database.
471         @param cr: the current row, from the database cursor,
472         @param uid: the current user’s ID for security checks,
473         @param ids: List of Report creator's id.
474         @return: True if display field which are  stored in database.
475                      or false if display field which are not store in dtabase.
476         """
477         this_objs = self.browse(cr, uid, ids, context=context)
478         for obj in this_objs:
479             for fld in obj.field_ids:
480                 # Allowing to use count(*)
481                 if not fld.field_id.model and fld.group_method == 'count':
482                     continue
483                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
484                 if (isinstance(model_column, fields.function) or isinstance(model_column, fields.related)) and not model_column.store:
485                     return False
486         return True
487
488     def _aggregation_error(self, cr, uid, ids, context=None):
489         """
490         constraints function which specify that
491                 aggregate function to the non calculated field..
492         @param cr: the current row, from the database cursor,
493         @param uid: the current user’s ID for security checks,
494         @param ids: List of Report creator's id.
495         @return: True if model colume type is in integer or float.
496                      or false model colume type is not in integer or float.
497         """
498         aggregate_columns = ('integer', 'float')
499         apply_functions = ('sum', 'min', 'max', 'avg', 'count')
500         this_objs = self.browse(cr, uid, ids, context=context)
501         for obj in this_objs:
502             for fld in obj.field_ids:
503                 # Allowing to use count(*)
504                 if not fld.field_id.model and fld.group_method == 'count':
505                     continue
506                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
507                 if model_column._type not in aggregate_columns and fld.group_method in apply_functions:
508                     return False
509         return True
510
511     def _calander_view_error(self, cr, uid, ids, context=None):
512         required_types = []
513         this_objs = self.browse(cr, uid, ids, context=context)
514         for obj in this_objs:
515             if obj.view_type1 == 'calendar' or obj.view_type2 == 'calendar' or obj.view_type3 == 'calendar':
516                 for fld in obj.field_ids:
517                     # Allowing to use count(*)
518                     if not fld.field_id.model and fld.group_method == 'count':
519                         continue
520                     model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
521                     if fld.calendar_mode in ('date_start','date_end') and model_column._type not in ('date','datetime'):
522                         return False
523                     elif fld.calendar_mode=='date_delay' and model_column._type not in ('int','float'):
524                         return False
525                     else:
526                         required_types.append(fld.calendar_mode)
527                 if 'date_start' not in required_types:
528                     return False
529         return True
530
531
532     _constraints = [
533         (_function_field, 'You can not display field which are not stored in Database.', ['field_ids']),
534         (_aggregation_error, 'You can apply aggregate function to the non calculated field.', ['field_ids']),
535         (_calander_view_error, "You must have to give calendar view's color,start date and delay.", ['field_ids']),
536     ]
537 report_creator()
538
539
540 class report_creator_field(osv.osv):
541     """
542     Report Creator Field
543     """
544     _name = "base_report_creator.report.fields"
545     _description = "Display Fields"
546     _rec_name = 'field_id'
547     _order = "sequence,id"
548     _columns = {
549         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of fields."),
550         'field_id': fields.many2one('ir.model.fields', 'Field'),
551         'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
552         'group_method': fields.selection([('group', 'Grouped'),
553                                           ('sum', 'Sum'),
554                                           ('min', 'Minimum'),
555                                           ('count', 'Count'),
556                                           ('max', 'Maximum'),
557                                           ('avg', 'Average')], 'Grouping Method', required=True),
558         'graph_mode': fields.selection([('', '/'),
559                                         ('x', 'X Axis'),
560                                         ('y', 'Y Axis')], 'Graph Mode'),
561         'calendar_mode': fields.selection([('', '/'),
562                                            ('date_start', 'Starting Date'),
563                                            ('date_end', 'Ending Date'), ('date_delay', 'Delay'), ('date_stop', 'End Date'), ('color', 'Unique Colors')], 'Calendar Mode'),
564     }
565     _defaults = {
566         'group_method': lambda *args: 'group',
567         'graph_mode': lambda *args: '',
568     }
569 report_creator_field()
570
571
572 class report_creator_filter(osv.osv):
573     """
574     Report Creator Filter
575     """
576     _name = "base_report_creator.report.filter"
577     _description = "Report Filters"
578     _columns = {
579         'name': fields.char('Filter Name', size=64, required=True),
580         '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'),
581         'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
582         'condition': fields.selection([('and', 'AND'),
583                                         ('or', 'OR')], 'Condition')
584     }
585     _defaults = {
586         'condition': lambda *args: 'and',
587     }
588 report_creator_filter()
589 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
590