odoo/addons/base_report_creator/base_report_creator.py

590 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
from tools.translate import _
from tools import ustr
class report_result(osv.osv):
"""
Report Creator
"""
_name = "base_report_creator_report.result"
_description = "Report"
#
# Should request only used fields
#
def fields_get(self, cr, user, fields=None, context=None):
"""
Get Fields.
@param cr: the current row, from the database cursor,
@param user: the current users ID for security checks,
@param Fields: List of field of customer reports form.
@return: Dictionary of Fields
"""
if context is None:
context = {}
data = context and context.get('report_id', False) or False
if (not context) or 'report_id' not in context:
return super(report_result, self).fields_get(cr, user, fields, context)
if data:
report = self.pool.get('base_report_creator.report').browse(cr, user, data,context=context)
models = {}
#Start Loop
for model in report.model_ids:
models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
#End Loop
fields = {}
i = 0
for f in report.field_ids:
if f.field_id.model:
fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
i += 1
else:
fields['column_count'] = {'readonly': True, 'type': 'integer', 'string': 'Count', 'size': 64, 'name': 'column_count'}
return fields
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
"""
Overrides orm field_view_get.
@param cr: the current row, from the database cursor,
@param user: the current users ID for security checks,
@return: Dictionary of Fields, arch and toolbar.
"""
if context is None:
context = {}
data = context and context.get('report_id', False) or False
if (not context) or 'report_id' not in context:
return super(report_result, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context)
models = {}
for model in report.model_ids:
models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context)
fields = {}
i = 0
for f in report.field_ids:
if f.field_id.model:
fields['field'+str(i)] = models[f.field_id.model][f.field_id.name]
i += 1
else:
fields['column_count'] = {'readonly': True, 'type': 'integer', 'string': 'Count', 'size': 64, 'name': 'column_count'}
arch = '<?xml version="1.0"?>'
if view_type == 'graph':
orientation_eval = {'horz':'horizontal','vert' :'vertical'}
orientation = eval(report.view_graph_orientation,orientation_eval)
arch +='<graph string="%s" type="%s" orientation="%s">' % (report.name, report.view_graph_type, orientation)
i = 0
for val in ('x','y'):
for f in report.field_ids:
if f.graph_mode == val:
if f.field_id.model:
arch += '<field name="%s" select="1"/>' % ('field'+str(i),)
i += 1
else:
arch += '<field name="%s" select="1"/>' % ('column_count',)
elif view_type == 'calendar':
required_types = ['date_start', 'date_delay', 'color']
set_dict = {'view_type':view_type, 'string':report.name}
temp_list = []
i = 0
for f in report.field_ids:
if f.calendar_mode and f.calendar_mode in required_types:
if f.field_id.model:
field_cal = 'field'+str(i)
i += 1
else:
field_cal = 'column_count'
set_dict[f.calendar_mode] = field_cal
del required_types[required_types.index(f.calendar_mode)]
else:
if f.field_id.model:
temp_list.append('''<field name = "%(name)s" select = "1"/>''' % {'name': 'field' + str(i)})
i += 1
else:
temp_list.append('''<field name="%(name)s" select="1"/>''' % {'name':'column_count'})
arch += '''<% (view_type)s string = "%(string)s" date_start = "%(date_start)s" ''' % set_dict
if set_dict.get('date_delay', False):
arch += ''' date_delay = "%(date_delay)s" ''' % set_dict
if set_dict.get('date_stop', False):
arch += ''' date_stop="%(date_stop)s" '''%set_dict
if set_dict.get('color', False):
arch += ''' color="%(color)s"'''%set_dict
arch += '''>'''
arch += ''.join(temp_list)
else:
arch += '<%s string="%s">' % (view_type, report.name)
i = 0
for f in report.field_ids:
if f.field_id.model:
arch += '<field name="%s"/>' % ('field' + str(i),)
i += 1
else:
arch += '<field name="%s"/>' % ('column_count',)
arch += '</%s>' % (view_type,)
result = {
'arch': arch,
'fields': fields
}
result['toolbar'] = {
'print': [],
'action': [],
'relate': []
}
return result
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
"""
overrides orm Read method.Read List of fields for report creator.
@param cr: the current row, from the database cursor,
@param user: the current users ID for security checks,
@param ids: List of report creator's id.
@param fields: List of fields.
@return: List of Dictionary of form [{name_of_the_field: value, ...}, ...]
"""
if context is None:
context = {}
report_id = context.get('active_id', False)
report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context)
cr.execute(report.sql_query)
result = cr.dictfetchall()
return result
def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
"""
overrides orm search method.
@param cr: the current row, from the database cursor,
@param user: the current users ID for security checks,
@param args: list of tuples of form [(name_of_the_field, operator, value), ...].
@return: List of id
"""
if context is None:
context = {}
context_id = context.get('report_id', False)
if (not context) or 'report_id' not in context:
return {}
if context_id:
report = self.pool.get('base_report_creator.report').browse(cr, user, context_id, context=context)
i = 0
fields = {}
for f in report.field_ids:
if f.field_id.model:
fields['field'+str(i)] = (f.field_id.model, f.field_id.name)
i += 1
else:
fields['column_count'] = (False, 'Count')
newargs = []
newargs2 = []
for a in args:
if fields[a[0]][0]:
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)
newargs += res[0]
newargs2 += res[1]
else:
newargs += [("count(*) " + a[1] +" " + str(a[2]))]
ctx = context or {}
ctx['getid'] = True
sql_query = report.sql_query
cr.execute(sql_query) # TODO: FILTER
result = cr.fetchall()
return map(lambda x: x[0], result)
report_result()
class report_creator(osv.osv):
"""
Report Creator
"""
_name = "base_report_creator.report"
_description = "Report"
model_set_id = False
#
# Should request only used fields
#
def export_data(self, cr, uid, ids, fields_to_export, context=None):
if context is None:
context = {}
data_l = self.read(cr, uid, ids, ['sql_query'], context=context)
final_datas = []
#start Loop
for record in data_l:
datas = []
for key in fields_to_export:
value = record.get(key,'')
if isinstance(value, tuple):
datas.append(ustr(value[1]))
else:
datas.append(ustr(value))
final_datas += [datas]
#End Loop
return {'datas': final_datas}
def _path_get(self, cr, uid, models, filter_ids=[]):
"""
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param models: List of object.
"""
self.model_set_id = False
model_dict = {}
from_list = []
where_list = []
filter_list = []
for model in models:
model_dict[model.model] = self.pool.get(model.model)._table
model_list = model_dict.keys()
reference_model_dict = {}
for model in model_dict:
from_list.append(model_dict.get(model))
rest_list = model_dict.keys()
rest_list.remove(model)
model_pool = self.pool.get(model)
fields_get = model_pool.fields_get(cr, uid)
model_columns = {}
def _get_inherit_fields(obj):
pool_model = self.pool.get(obj)
#Adding the columns of the model itself
model_columns.update(pool_model._columns)
#Adding the columns of its _inherits
for record in pool_model._inherits.keys():
_get_inherit_fields(record)
_get_inherit_fields(model)
fields_filter = dict(filter(lambda x:x[1].get('relation', False)
and x[1].get('relation') in rest_list
and x[1].get('type') == 'many2one'
and not (isinstance(model_columns[x[0]], fields.function) or isinstance(model_columns[x[0]], fields.related)), fields_get.items()))
if fields_filter:
model in model_list and model_list.remove(model)
model_count = reference_model_dict.get(model, False)
if model_count:
reference_model_dict[model] = model_count +1
else:
reference_model_dict[model] = 1
for k, v in fields_filter.items():
v.get('relation') in model_list and model_list.remove(v.get('relation'))
relation_count = reference_model_dict.get(v.get('relation'), False)
if relation_count:
reference_model_dict[v.get('relation')] = relation_count+1
else:
reference_model_dict[v.get('relation')]=1
if k in self.pool.get(model)._columns:
str_where = model_dict.get(model)+"."+ k + "=" + model_dict.get(v.get('relation'))+'.id'
where_list.append(str_where)
if reference_model_dict:
self.model_set_id = model_dict.get(reference_model_dict.keys()[reference_model_dict.values().index(min(reference_model_dict.values()))])
if model_list and not len(model_dict.keys()) == 1:
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))
if filter_ids and where_list <> []:
filter_list.append(' and ')
filter_list.append(' ')
for filter_id in filter_ids:
filter_list.append(filter_id.expression)
filter_list.append(' ')
filter_list.append(filter_id.condition)
if len(from_list) == 1 and filter_ids:
from_list.append(' ')
ret_str = "\n where \n".join(from_list)
else:
ret_str = ",\n".join(from_list)
if where_list:
where_list = list(set(where_list))
ret_str += "\n where \n"+" and\n".join(where_list)
ret_str = ret_str.strip()
if filter_list:
ret_str += "\n".join(filter_list)
if ret_str.endswith('and'):
ret_str = ret_str[0:len(ret_str)-3]
if ret_str.endswith('or'):
ret_str = ret_str[0:len(ret_str)-2]
ret_str = ret_str.strip()
return ret_str % {'uid': uid}
def _id_get(self, cr, uid, id, context):
"""
Get Model id
"""
return self.model_set_id and 'min('+self.model_set_id+'.id)'
def _sql_query_get(self, cr, uid, ids, prop, unknow_none, context=None, where_plus=[], limit=None, offset=None):
"""
Get sql query which return on sql_query field.
@return: Dictionary of sql query.
"""
result = {}
for obj in self.browse(cr, uid, ids, context=context):
fields = []
groupby = []
i = 0
for f in obj.field_ids:
# Allowing to use count(*)
if not f.field_id.model and f.group_method == 'count':
fields.insert(0, ('count(*) as column_count'))
continue
t = self.pool.get(f.field_id.model_id.model)._table
if f.group_method == 'group':
fields.append('\t'+t+'.'+f.field_id.name+' as field'+str(i))
groupby.append(t+'.'+f.field_id.name)
else:
fields.append('\t'+f.group_method+'('+t+'.'+f.field_id.name+')'+' as field'+str(i))
i += 1
models = self._path_get(cr, uid, obj.model_ids, obj.filter_ids)
check = self._id_get(cr, uid, ids[0], context)
if check<>False:
fields.insert(0, (check + ' as id'))
if models:
result[obj.id] = """select
%s
from
%s
""" % (',\n'.join(fields), models)
if groupby:
result[obj.id] += "group by\n\t"+', '.join(groupby)
if where_plus:
result[obj.id] += "\nhaving \n\t"+"\n\t and ".join(where_plus)
if limit:
result[obj.id] += " limit "+str(limit)
if offset:
result[obj.id] += " offset "+str(offset)
else:
result[obj.id] = False
return result
_columns = {
'name': fields.char('Report Name', size=64, required=True),
'type': fields.selection([('list', 'Rows And Columns Report'), ], 'Report Type', required=True), #('sum','Summation Report')
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the report without removing it."),
'view_type1': fields.selection([('form', 'Form'),
('tree', 'Tree'),
('graph', 'Graph'),
('calendar', 'Calendar')], 'First View', required=True),
'view_type2': fields.selection([('', '/'),
('form', 'Form'),
('tree', 'Tree'),
('graph', 'Graph'),
('calendar', 'Calendar')], 'Second View'),
'view_type3': fields.selection([('', '/'),
('form', 'Form'),
('tree', 'Tree'),
('graph', 'Graph'),
('calendar', 'Calendar')], 'Third View'),
'view_graph_type': fields.selection([('pie', 'Pie Chart'),
('bar', 'Bar Chart')], 'Graph Type', required=True),
'view_graph_orientation': fields.selection([('horz', 'Horizontal'),
('vert', 'Vertical')], 'Graph Orientation', required=True),
'model_ids': fields.many2many('ir.model', 'base_report_creator_report_model_rel', 'report_id', 'model_id', 'Reported Objects'),
'field_ids': fields.one2many('base_report_creator.report.fields', 'report_id', 'Fields to Display'),
'filter_ids': fields.one2many('base_report_creator.report.filter', 'report_id', 'Filters'),
'sql_query': fields.function(_sql_query_get, type="text", string='SQL Query', store=True),
'group_ids': fields.many2many('res.groups', 'base_report_creator_group_rel', 'report_id', 'group_id', 'Authorized Groups'),
'menu_id': fields.many2one('ir.ui.menu', "Menu", readonly=True),
}
_defaults = {
'type': lambda *args: 'list',
'active': lambda *args: True,
'view_type1': lambda *args: 'tree',
'view_type2': lambda *args: 'graph',
'view_graph_type': lambda *args: 'bar',
'view_graph_orientation': lambda *args: 'horz',
}
def open_report(self, cr, uid, ids, context=None):
"""
This Function opens base creator report view
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of report open's IDs
@param context: A standard dictionary for contextual values
@return : Dictionary value for base creator report form
"""
if context is None:
context = {}
rep = self.browse(cr, uid, ids, context=context)
if not rep:
return False
rep = rep[0]
view_mode = rep.view_type1
if rep.view_type2:
view_mode += ',' + rep.view_type2
if rep.view_type3:
view_mode += ',' + rep.view_type3
value = {
'name': rep.name,
'view_type': 'form',
'view_mode': view_mode,
'res_model': 'base_report_creator_report.result',
'type': 'ir.actions.act_window',
'context': "{'report_id':%d}" % (rep.id,),
'nodestroy': True,
}
return value
def _function_field(self, cr, uid, ids, context=None):
"""
constraints function which specify that
not display field which are not stored in Database.
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of Report creator's id.
@return: True if display field which are stored in database.
or false if display field which are not store in dtabase.
"""
this_objs = self.browse(cr, uid, ids, context=context)
for obj in this_objs:
for fld in obj.field_ids:
# Allowing to use count(*)
if not fld.field_id.model and fld.group_method == 'count':
continue
model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
if (isinstance(model_column, fields.function) or isinstance(model_column, fields.related)) and not model_column.store:
return False
return True
def _aggregation_error(self, cr, uid, ids, context=None):
"""
constraints function which specify that
aggregate function to the non calculated field..
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of Report creator's id.
@return: True if model colume type is in integer or float.
or false model colume type is not in integer or float.
"""
aggregate_columns = ('integer', 'float')
apply_functions = ('sum', 'min', 'max', 'avg', 'count')
this_objs = self.browse(cr, uid, ids, context=context)
for obj in this_objs:
for fld in obj.field_ids:
# Allowing to use count(*)
if not fld.field_id.model and fld.group_method == 'count':
continue
model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
if model_column._type not in aggregate_columns and fld.group_method in apply_functions:
return False
return True
def _calander_view_error(self, cr, uid, ids, context=None):
required_types = []
this_objs = self.browse(cr, uid, ids, context=context)
for obj in this_objs:
if obj.view_type1 == 'calendar' or obj.view_type2 == 'calendar' or obj.view_type3 == 'calendar':
for fld in obj.field_ids:
# Allowing to use count(*)
if not fld.field_id.model and fld.group_method == 'count':
continue
model_column = self.pool.get(fld.field_id.model)._columns[fld.field_id.name]
if fld.calendar_mode in ('date_start','date_end') and model_column._type not in ('date','datetime'):
return False
elif fld.calendar_mode=='date_delay' and model_column._type not in ('int','float'):
return False
else:
required_types.append(fld.calendar_mode)
if 'date_start' not in required_types:
return False
return True
_constraints = [
(_function_field, 'You can not display field which are not stored in Database.', ['field_ids']),
(_aggregation_error, 'You can apply aggregate function to the non calculated field.', ['field_ids']),
(_calander_view_error, "You must have to give calendar view's color,start date and delay.", ['field_ids']),
]
report_creator()
class report_creator_field(osv.osv):
"""
Report Creator Field
"""
_name = "base_report_creator.report.fields"
_description = "Display Fields"
_rec_name = 'field_id'
_order = "sequence,id"
_columns = {
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of fields."),
'field_id': fields.many2one('ir.model.fields', 'Field'),
'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
'group_method': fields.selection([('group', 'Grouped'),
('sum', 'Sum'),
('min', 'Minimum'),
('count', 'Count'),
('max', 'Maximum'),
('avg', 'Average')], 'Grouping Method', required=True),
'graph_mode': fields.selection([('', '/'),
('x', 'X Axis'),
('y', 'Y Axis')], 'Graph Mode'),
'calendar_mode': fields.selection([('', '/'),
('date_start', 'Starting Date'),
('date_end', 'Ending Date'), ('date_delay', 'Delay'), ('date_stop', 'End Date'), ('color', 'Unique Colors')], 'Calendar Mode'),
}
_defaults = {
'group_method': lambda *args: 'group',
'graph_mode': lambda *args: '',
}
report_creator_field()
class report_creator_filter(osv.osv):
"""
Report Creator Filter
"""
_name = "base_report_creator.report.filter"
_description = "Report Filters"
_columns = {
'name': fields.char('Filter Name', size=64, required=True),
'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'),
'report_id': fields.many2one('base_report_creator.report', 'Report', on_delete='cascade'),
'condition': fields.selection([('and', 'AND'),
('or', 'OR')], 'Condition')
}
_defaults = {
'condition': lambda *args: 'and',
}
report_creator_filter()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: