[fix] desactivated href in header
[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             newargs2 = []
205             for a in args:
206                 if fields[a[0]][0]:
207                     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)
208                     newargs += res[0]
209                     newargs2 += res[1]
210                 else:
211                     newargs += [("count(*) " + a[1] +" " + str(a[2]))]
212             ctx = context or {}
213             ctx['getid'] = True
214             sql_query = report.sql_query
215             cr.execute(sql_query) # TODO: FILTER
216             result = cr.fetchall()
217             return map(lambda x: x[0], result)
218
219 report_result()
220
221
222 class report_creator(osv.osv):
223     """
224     Report Creator
225     """
226     _name = "base_report_creator.report"
227     _description = "Report"
228     model_set_id = False
229     #
230     # Should request only used fields
231     #
232     def export_data(self, cr, uid, ids, fields_to_export, context=None):
233         if context is None:
234             context = {}
235         data_l = self.read(cr, uid, ids, ['sql_query'], context=context)
236         final_datas = []
237         #start Loop
238         for record in data_l:
239             datas = []
240             for key in fields_to_export:
241                 value = record.get(key,'')
242                 if isinstance(value, tuple):
243                     datas.append(ustr(value[1]))
244                 else:
245                     datas.append(ustr(value))
246             final_datas += [datas]
247             #End Loop
248         return {'datas': final_datas}
249
250
251     def _path_get(self, cr, uid, models, filter_ids=[]):
252         """
253         @param cr: the current row, from the database cursor,
254         @param uid: the current user’s ID for security checks,
255         @param models: List of object.
256         """
257         self.model_set_id = False
258         model_dict = {}
259         from_list = []
260         where_list = []
261         filter_list = []
262         for model in models:
263             model_dict[model.model] = self.pool.get(model.model)._table
264
265         model_list = model_dict.keys()
266         reference_model_dict = {}
267         for model in model_dict:
268             from_list.append(model_dict.get(model))
269             rest_list = model_dict.keys()
270             rest_list.remove(model)
271             model_pool = self.pool.get(model)
272             fields_get = model_pool.fields_get(cr, uid)
273             model_columns = {}
274
275             def _get_inherit_fields(obj):
276                 pool_model = self.pool.get(obj)
277                 #Adding the columns of the model itself
278                 model_columns.update(pool_model._columns)
279                 #Adding the columns of its _inherits
280                 for record in pool_model._inherits.keys():
281                     _get_inherit_fields(record)
282
283             _get_inherit_fields(model)
284
285             fields_filter = dict(filter(lambda x:x[1].get('relation', False)
286                                         and x[1].get('relation') in rest_list
287                                         and x[1].get('type') == 'many2one'
288                                         and not (isinstance(model_columns[x[0]], fields.function) or isinstance(model_columns[x[0]], fields.related)), fields_get.items()))
289             if fields_filter:
290                 model in model_list and model_list.remove(model)
291             model_count = reference_model_dict.get(model, False)
292             if model_count:
293                 reference_model_dict[model] = model_count +1
294             else:
295                 reference_model_dict[model] = 1
296             for k, v in fields_filter.items():
297                 v.get('relation') in model_list and model_list.remove(v.get('relation'))
298                 relation_count = reference_model_dict.get(v.get('relation'), False)
299                 if relation_count:
300                     reference_model_dict[v.get('relation')] = relation_count+1
301                 else:
302                     reference_model_dict[v.get('relation')]=1
303                 if k in self.pool.get(model)._columns:
304                     str_where = model_dict.get(model)+"."+ k + "=" + model_dict.get(v.get('relation'))+'.id'
305                     where_list.append(str_where)
306
307         if reference_model_dict:
308             self.model_set_id = model_dict.get(reference_model_dict.keys()[reference_model_dict.values().index(min(reference_model_dict.values()))])
309         if model_list and not len(model_dict.keys()) == 1:
310             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))
311
312         if filter_ids and where_list <> []:
313             filter_list.append(' and ')
314             filter_list.append(' ')
315
316         for filter_id in filter_ids:
317             filter_list.append(filter_id.expression)
318             filter_list.append(' ')
319             filter_list.append(filter_id.condition)
320
321         if len(from_list) == 1 and filter_ids:
322             from_list.append(' ')
323             ret_str = "\n where \n".join(from_list)
324         else:
325             ret_str = ",\n".join(from_list)
326
327         if where_list:
328             where_list = list(set(where_list))
329             ret_str += "\n where \n"+" and\n".join(where_list)
330             ret_str = ret_str.strip()
331         if filter_list:
332             ret_str += "\n".join(filter_list)
333             if ret_str.endswith('and'):
334                 ret_str = ret_str[0:len(ret_str)-3]
335             if ret_str.endswith('or'):
336                 ret_str = ret_str[0:len(ret_str)-2]
337             ret_str = ret_str.strip()
338
339         return ret_str % {'uid': uid}
340
341     def _id_get(self, cr, uid, id, context):
342         """
343         Get Model id
344         """
345         return self.model_set_id and 'min('+self.model_set_id+'.id)'
346
347     def _sql_query_get(self, cr, uid, ids, prop, unknow_none, context=None, where_plus=[], limit=None, offset=None):
348         """
349         Get sql query which return on sql_query field.
350         @return: Dictionary of sql query.
351         """
352         result = {}
353         for obj in self.browse(cr, uid, ids, context=context):
354             fields = []
355             groupby = []
356             i = 0
357             for f in obj.field_ids:
358                 # Allowing to use count(*)
359                 if not f.field_id.model and f.group_method == 'count':
360                     fields.insert(0, ('count(*) as column_count'))
361                     continue
362                 t = self.pool.get(f.field_id.model_id.model)._table
363                 if f.group_method == 'group':
364                     fields.append('\t'+t+'.'+f.field_id.name+' as field'+str(i))
365                     groupby.append(t+'.'+f.field_id.name)
366                 else:
367                     fields.append('\t'+f.group_method+'('+t+'.'+f.field_id.name+')'+' as field'+str(i))
368
369                 i += 1
370             models = self._path_get(cr, uid, obj.model_ids, obj.filter_ids)
371             check = self._id_get(cr, uid, ids[0], context)
372             if check<>False:
373                 fields.insert(0, (check + ' as id'))
374
375             if models:
376                 result[obj.id] = """select
377     %s
378     from
379     %s
380                 """ % (',\n'.join(fields), models)
381                 if groupby:
382                     result[obj.id] += "group by\n\t"+', '.join(groupby)
383                 if where_plus:
384                     result[obj.id] += "\nhaving \n\t"+"\n\t and ".join(where_plus)
385                 if limit:
386                     result[obj.id] += " limit "+str(limit)
387                 if offset:
388                     result[obj.id] += " offset "+str(offset)
389             else:
390                 result[obj.id] = False
391         return result
392
393     _columns = {
394         'name': fields.char('Report Name', size=64, required=True),
395         'type': fields.selection([('list', 'Rows And Columns Report'), ], 'Report Type', required=True), #('sum','Summation Report')
396         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the report without removing it."),
397         'view_type1': fields.selection([('form', 'Form'),
398                                         ('tree', 'Tree'),
399                                         ('graph', 'Graph'),
400                                         ('calendar', 'Calendar')], 'First View', required=True),
401         'view_type2': fields.selection([('', '/'),
402                                         ('form', 'Form'),
403                                         ('tree', 'Tree'),
404                                         ('graph', 'Graph'),
405                                         ('calendar', 'Calendar')], 'Second View'),
406         'view_type3': fields.selection([('', '/'),
407                                         ('form', 'Form'),
408                                         ('tree', 'Tree'),
409                                         ('graph', 'Graph'),
410                                         ('calendar', 'Calendar')], 'Third View'),
411         'view_graph_type': fields.selection([('pie', 'Pie Chart'),
412                                              ('bar', 'Bar Chart')], 'Graph Type', required=True),
413         'view_graph_orientation': fields.selection([('horz', 'Horizontal'),
414                                                     ('vert', 'Vertical')], 'Graph Orientation', required=True),
415         'model_ids': fields.many2many('ir.model', 'base_report_creator_report_model_rel', 'report_id', 'model_id', 'Reported Objects'),
416         'field_ids': fields.one2many('base_report_creator.report.fields', 'report_id', 'Fields to Display'),
417         'filter_ids': fields.one2many('base_report_creator.report.filter', 'report_id', 'Filters'),
418         'sql_query': fields.function(_sql_query_get, type="text", string='SQL Query', store=True),
419         'group_ids': fields.many2many('res.groups', 'base_report_creator_group_rel', 'report_id', 'group_id', 'Authorized Groups'),
420         'menu_id': fields.many2one('ir.ui.menu', "Menu", readonly=True),
421     }
422     _defaults = {
423         'type': lambda *args: 'list',
424         'active': lambda *args: True,
425         'view_type1': lambda *args: 'tree',
426         'view_type2': lambda *args: 'graph',
427         'view_graph_type': lambda *args: 'bar',
428         'view_graph_orientation': lambda *args: 'horz',
429     }
430
431     def open_report(self, cr, uid, ids, context=None):
432         """
433         This Function opens base creator report  view
434         @param self: The object pointer
435         @param cr: the current row, from the database cursor,
436         @param uid: the current user’s ID for security checks,
437         @param ids: List of report open's IDs
438         @param context: A standard dictionary for contextual values
439         @return : Dictionary value for base creator report form
440         """
441         if context is None:
442             context = {}
443
444         rep = self.browse(cr, uid, ids, context=context)
445         if not rep:
446             return False
447
448         rep = rep[0]
449         view_mode = rep.view_type1
450         if rep.view_type2:
451             view_mode += ',' + rep.view_type2
452         if rep.view_type3:
453             view_mode += ',' + rep.view_type3
454         value = {
455             'name': rep.name,
456             'view_type': 'form',
457             'view_mode': view_mode,
458             'res_model': 'base_report_creator_report.result',
459             'type': 'ir.actions.act_window',
460             'context': "{'report_id':%d}" % (rep.id,),
461             'nodestroy': True,
462         }
463
464         return value
465
466     def _function_field(self, cr, uid, ids, context=None):
467         """
468         constraints function which specify that
469                 not display field which are not stored in Database.
470         @param cr: the current row, from the database cursor,
471         @param uid: the current user’s ID for security checks,
472         @param ids: List of Report creator's id.
473         @return: True if display field which are  stored in database.
474                      or false if display field which are not store in dtabase.
475         """
476         this_objs = self.browse(cr, uid, ids, context=context)
477         for obj in this_objs:
478             for fld in obj.field_ids:
479                 # Allowing to use count(*)
480                 if not fld.field_id.model and fld.group_method == 'count':
481                     continue
482                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
483                 if (isinstance(model_column, fields.function) or isinstance(model_column, fields.related)) and not model_column.store:
484                     return False
485         return True
486
487     def _aggregation_error(self, cr, uid, ids, context=None):
488         """
489         constraints function which specify that
490                 aggregate function to the non calculated field..
491         @param cr: the current row, from the database cursor,
492         @param uid: the current user’s ID for security checks,
493         @param ids: List of Report creator's id.
494         @return: True if model colume type is in integer or float.
495                      or false model colume type is not in integer or float.
496         """
497         aggregate_columns = ('integer', 'float')
498         apply_functions = ('sum', 'min', 'max', 'avg', 'count')
499         this_objs = self.browse(cr, uid, ids, context=context)
500         for obj in this_objs:
501             for fld in obj.field_ids:
502                 # Allowing to use count(*)
503                 if not fld.field_id.model and fld.group_method == 'count':
504                     continue
505                 model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
506                 if model_column._type not in aggregate_columns and fld.group_method in apply_functions:
507                     return False
508         return True
509
510     def _calander_view_error(self, cr, uid, ids, context=None):
511         required_types = []
512         this_objs = self.browse(cr, uid, ids, context=context)
513         for obj in this_objs:
514             if obj.view_type1 == 'calendar' or obj.view_type2 == 'calendar' or obj.view_type3 == 'calendar':
515                 for fld in obj.field_ids:
516                     # Allowing to use count(*)
517                     if not fld.field_id.model and fld.group_method == 'count':
518                         continue
519                     model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
520                     if fld.calendar_mode in ('date_start','date_end') and model_column._type not in ('date','datetime'):
521                         return False
522                     elif fld.calendar_mode=='date_delay' and model_column._type not in ('int','float'):
523                         return False
524                     else:
525                         required_types.append(fld.calendar_mode)
526                 if 'date_start' not in required_types:
527                     return False
528         return True
529
530
531     _constraints = [
532         (_function_field, 'You can not display field which are not stored in database.', ['field_ids']),
533         (_aggregation_error, 'You can apply aggregate function to the non calculated field.', ['field_ids']),
534         (_calander_view_error, "You must have to give calendar view's color,start date and delay.", ['field_ids']),
535     ]
536 report_creator()
537
538
539 class report_creator_field(osv.osv):
540     """
541     Report Creator Field
542     """
543     _name = "base_report_creator.report.fields"
544     _description = "Display Fields"
545     _rec_name = 'field_id'
546     _order = "sequence,id"
547     _columns = {
548         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of fields."),
549         'field_id': fields.many2one('ir.model.fields', 'Field'),
550         'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
551         'group_method': fields.selection([('group', 'Grouped'),
552                                           ('sum', 'Sum'),
553                                           ('min', 'Minimum'),
554                                           ('count', 'Count'),
555                                           ('max', 'Maximum'),
556                                           ('avg', 'Average')], 'Grouping Method', required=True),
557         'graph_mode': fields.selection([('', '/'),
558                                         ('x', 'X Axis'),
559                                         ('y', 'Y Axis')], 'Graph Mode'),
560         'calendar_mode': fields.selection([('', '/'),
561                                            ('date_start', 'Starting Date'),
562                                            ('date_end', 'Ending Date'), ('date_delay', 'Delay'), ('date_stop', 'End Date'), ('color', 'Unique Colors')], 'Calendar Mode'),
563     }
564     _defaults = {
565         'group_method': lambda *args: 'group',
566         'graph_mode': lambda *args: '',
567     }
568 report_creator_field()
569
570
571 class report_creator_filter(osv.osv):
572     """
573     Report Creator Filter
574     """
575     _name = "base_report_creator.report.filter"
576     _description = "Report Filters"
577     _columns = {
578         'name': fields.char('Filter Name', size=64, required=True),
579         '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'),
580         'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
581         'condition': fields.selection([('and', 'AND'),
582                                         ('or', 'OR')], 'Condition')
583     }
584     _defaults = {
585         'condition': lambda *args: 'and',
586     }
587 report_creator_filter()
588 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
589