|
@ -1,33 +1,4 @@ |
|
|
# -*- coding: utf-8 -*- |
|
|
# -*- coding: utf-8 -*- |
|
|
<<<<<<< HEAD |
|
|
|
|
|
############################################################################## |
|
|
|
|
|
# |
|
|
|
|
|
# OpenERP, Open Source Management Solution |
|
|
|
|
|
# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>). |
|
|
|
|
|
# Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>). |
|
|
|
|
|
# Copyright (C) 2015-Today GRAP |
|
|
|
|
|
# Author Markus Schneider <markus.schneider at initos.com> |
|
|
|
|
|
# @author Sylvain LE GAL (https://twitter.com/legalsylvain) |
|
|
|
|
|
# |
|
|
|
|
|
# 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 openerp import api, fields |
|
|
|
|
|
from openerp.models import Model |
|
|
|
|
|
from openerp.exceptions import except_orm |
|
|
|
|
|
======= |
|
|
|
|
|
# © 2010-2013 OpenERP s.a. (<http://openerp.com>). |
|
|
# © 2010-2013 OpenERP s.a. (<http://openerp.com>). |
|
|
# © 2014 initOS GmbH & Co. KG (<http://www.initos.com>). |
|
|
# © 2014 initOS GmbH & Co. KG (<http://www.initos.com>). |
|
|
# © 2015-Today GRAP |
|
|
# © 2015-Today GRAP |
|
@ -40,13 +11,10 @@ from collections import OrderedDict |
|
|
|
|
|
|
|
|
from openerp import api, fields, models |
|
|
from openerp import api, fields, models |
|
|
from openerp.tools.safe_eval import safe_eval as eval |
|
|
from openerp.tools.safe_eval import safe_eval as eval |
|
|
>>>>>>> 3ab676d... [IMP][8.0][web_dashboard_tile] Refactor (see changes in description) (#476) |
|
|
|
|
|
from openerp.tools.translate import _ |
|
|
from openerp.tools.translate import _ |
|
|
|
|
|
from openerp.exceptions import ValidationError, except_orm |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<<<<<<< HEAD |
|
|
|
|
|
class TileTile(Model): |
|
|
|
|
|
======= |
|
|
|
|
|
def median(vals): |
|
|
def median(vals): |
|
|
# https://docs.python.org/3/library/statistics.html#statistics.median |
|
|
# https://docs.python.org/3/library/statistics.html#statistics.median |
|
|
# TODO : refactor, using statistics.median when Odoo will be available |
|
|
# TODO : refactor, using statistics.median when Odoo will be available |
|
@ -75,7 +43,7 @@ FIELD_FUNCTIONS = OrderedDict([ |
|
|
'help': _("Total value of '%s'")}), |
|
|
'help': _("Total value of '%s'")}), |
|
|
('avg', { |
|
|
('avg', { |
|
|
'name': 'Average', |
|
|
'name': 'Average', |
|
|
'func': lambda vals: sum(vals)/len(vals), |
|
|
|
|
|
|
|
|
'func': lambda vals: sum(vals) / len(vals), |
|
|
'help': _("Minimum value of '%s'")}), |
|
|
'help': _("Minimum value of '%s'")}), |
|
|
('median', { |
|
|
('median', { |
|
|
'name': 'Median', |
|
|
'name': 'Median', |
|
@ -89,56 +57,10 @@ FIELD_FUNCTION_SELECTION = [ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TileTile(models.Model): |
|
|
class TileTile(models.Model): |
|
|
>>>>>>> 3ab676d... [IMP][8.0][web_dashboard_tile] Refactor (see changes in description) (#476) |
|
|
|
|
|
_name = 'tile.tile' |
|
|
_name = 'tile.tile' |
|
|
_description = 'Dashboard Tile' |
|
|
_description = 'Dashboard Tile' |
|
|
_order = 'sequence, name' |
|
|
_order = 'sequence, name' |
|
|
|
|
|
|
|
|
<<<<<<< HEAD |
|
|
|
|
|
def median(self, aList): |
|
|
|
|
|
# https://docs.python.org/3/library/statistics.html#statistics.median |
|
|
|
|
|
# TODO : refactor, using statistics.median when Odoo will be available |
|
|
|
|
|
# in Python 3.4 |
|
|
|
|
|
even = (0 if len(aList) % 2 else 1) + 1 |
|
|
|
|
|
half = (len(aList) - 1) / 2 |
|
|
|
|
|
return sum(sorted(aList)[half:half + even]) / float(even) |
|
|
|
|
|
|
|
|
|
|
|
def _get_tile_info(self): |
|
|
|
|
|
ima_obj = self.env['ir.model.access'] |
|
|
|
|
|
res = {} |
|
|
|
|
|
for r in self: |
|
|
|
|
|
r.active = False |
|
|
|
|
|
r.count = 0 |
|
|
|
|
|
r.computed_value = 0 |
|
|
|
|
|
r.helper = '' |
|
|
|
|
|
if ima_obj.check(r.model_id.model, 'read', False): |
|
|
|
|
|
# Compute count item |
|
|
|
|
|
model = self.env[r.model_id.model] |
|
|
|
|
|
r.count = model.search_count(eval(r.domain)) |
|
|
|
|
|
r.active = True |
|
|
|
|
|
|
|
|
|
|
|
# Compute datas for field_id depending of field_function |
|
|
|
|
|
if r.field_function and r.field_id and r.count != 0: |
|
|
|
|
|
records = model.search(eval(r.domain)) |
|
|
|
|
|
vals = [x[r.field_id.name] for x in records] |
|
|
|
|
|
desc = r.field_id.field_description |
|
|
|
|
|
if r.field_function == 'min': |
|
|
|
|
|
r.computed_value = min(vals) |
|
|
|
|
|
r.helper = _("Minimum value of '%s'") % desc |
|
|
|
|
|
elif r.field_function == 'max': |
|
|
|
|
|
r.computed_value = max(vals) |
|
|
|
|
|
r.helper = _("Maximum value of '%s'") % desc |
|
|
|
|
|
elif r.field_function == 'sum': |
|
|
|
|
|
r.computed_value = sum(vals) |
|
|
|
|
|
r.helper = _("Total value of '%s'") % desc |
|
|
|
|
|
elif r.field_function == 'avg': |
|
|
|
|
|
r.computed_value = sum(vals) / len(vals) |
|
|
|
|
|
r.helper = _("Average value of '%s'") % desc |
|
|
|
|
|
elif r.field_function == 'median': |
|
|
|
|
|
r.computed_value = self.median(vals) |
|
|
|
|
|
r.helper = _("Median value of '%s'") % desc |
|
|
|
|
|
return res |
|
|
|
|
|
======= |
|
|
|
|
|
def _get_eval_context(self): |
|
|
def _get_eval_context(self): |
|
|
def _context_today(): |
|
|
def _context_today(): |
|
|
return fields.Date.from_string(fields.Date.context_today(self)) |
|
|
return fields.Date.from_string(fields.Date.context_today(self)) |
|
@ -238,13 +160,13 @@ class TileTile(models.Model): |
|
|
self.primary_function != 'count', |
|
|
self.primary_function != 'count', |
|
|
self.secondary_function and |
|
|
self.secondary_function and |
|
|
self.secondary_function != 'count' |
|
|
self.secondary_function != 'count' |
|
|
]): |
|
|
|
|
|
records = model.search(eval(domain, eval_context)) |
|
|
|
|
|
|
|
|
]): |
|
|
|
|
|
records = model.search(eval(domain, eval_context)) |
|
|
for f in ['primary_', 'secondary_']: |
|
|
for f in ['primary_', 'secondary_']: |
|
|
f_function = f+'function' |
|
|
|
|
|
f_field_id = f+'field_id' |
|
|
|
|
|
f_format = f+'format' |
|
|
|
|
|
f_value = f+'value' |
|
|
|
|
|
|
|
|
f_function = f + 'function' |
|
|
|
|
|
f_field_id = f + 'field_id' |
|
|
|
|
|
f_format = f + 'format' |
|
|
|
|
|
f_value = f + 'value' |
|
|
value = 0 |
|
|
value = 0 |
|
|
if self[f_function] == 'count': |
|
|
if self[f_function] == 'count': |
|
|
value = count |
|
|
value = count |
|
@ -268,9 +190,9 @@ class TileTile(models.Model): |
|
|
'secondary_function', 'secondary_field_id') |
|
|
'secondary_function', 'secondary_field_id') |
|
|
def _compute_helper(self): |
|
|
def _compute_helper(self): |
|
|
for f in ['primary_', 'secondary_']: |
|
|
for f in ['primary_', 'secondary_']: |
|
|
f_function = f+'function' |
|
|
|
|
|
f_field_id = f+'field_id' |
|
|
|
|
|
f_helper = f+'helper' |
|
|
|
|
|
|
|
|
f_function = f + 'function' |
|
|
|
|
|
f_field_id = f + 'field_id' |
|
|
|
|
|
f_helper = f + 'helper' |
|
|
self[f_helper] = '' |
|
|
self[f_helper] = '' |
|
|
field_func = FIELD_FUNCTIONS.get(self[f_function], {}) |
|
|
field_func = FIELD_FUNCTIONS.get(self[f_function], {}) |
|
|
help = field_func.get('help', False) |
|
|
help = field_func.get('help', False) |
|
@ -285,15 +207,13 @@ class TileTile(models.Model): |
|
|
def _compute_active(self): |
|
|
def _compute_active(self): |
|
|
ima = self.env['ir.model.access'] |
|
|
ima = self.env['ir.model.access'] |
|
|
self.active = ima.check(self.model_id.model, 'read', False) |
|
|
self.active = ima.check(self.model_id.model, 'read', False) |
|
|
>>>>>>> 3ab676d... [IMP][8.0][web_dashboard_tile] Refactor (see changes in description) (#476) |
|
|
|
|
|
|
|
|
|
|
|
def _search_active(self, operator, value): |
|
|
def _search_active(self, operator, value): |
|
|
cr = self.env.cr |
|
|
cr = self.env.cr |
|
|
if operator != '=': |
|
|
if operator != '=': |
|
|
raise except_orm( |
|
|
raise except_orm( |
|
|
'Unimplemented Feature', |
|
|
|
|
|
'Search on Active field disabled.') |
|
|
|
|
|
ima_obj = self.env['ir.model.access'] |
|
|
|
|
|
|
|
|
_('Unimplemented Feature. Search on Active field disabled.')) |
|
|
|
|
|
ima = self.env['ir.model.access'] |
|
|
ids = [] |
|
|
ids = [] |
|
|
cr.execute(""" |
|
|
cr.execute(""" |
|
|
SELECT tt.id, im.model |
|
|
SELECT tt.id, im.model |
|
@ -301,40 +221,10 @@ class TileTile(models.Model): |
|
|
INNER JOIN ir_model im |
|
|
INNER JOIN ir_model im |
|
|
ON tt.model_id = im.id""") |
|
|
ON tt.model_id = im.id""") |
|
|
for result in cr.fetchall(): |
|
|
for result in cr.fetchall(): |
|
|
if (ima_obj.check(result[1], 'read', False) == value): |
|
|
|
|
|
|
|
|
if (ima.check(result[1], 'read', False) == value): |
|
|
ids.append(result[0]) |
|
|
ids.append(result[0]) |
|
|
return [('id', 'in', ids)] |
|
|
return [('id', 'in', ids)] |
|
|
|
|
|
|
|
|
<<<<<<< HEAD |
|
|
|
|
|
# Column Section |
|
|
|
|
|
name = fields.Char(required=True) |
|
|
|
|
|
model_id = fields.Many2one( |
|
|
|
|
|
comodel_name='ir.model', string='Model', required=True) |
|
|
|
|
|
user_id = fields.Many2one( |
|
|
|
|
|
comodel_name='res.users', string='User') |
|
|
|
|
|
domain = fields.Text(default='[]') |
|
|
|
|
|
action_id = fields.Many2one( |
|
|
|
|
|
comodel_name='ir.actions.act_window', string='Action') |
|
|
|
|
|
count = fields.Integer(compute='_get_tile_info') |
|
|
|
|
|
computed_value = fields.Float(compute='_get_tile_info') |
|
|
|
|
|
helper = fields.Char(compute='_get_tile_info') |
|
|
|
|
|
field_function = fields.Selection(selection=[ |
|
|
|
|
|
('min', 'Minimum'), |
|
|
|
|
|
('max', 'Maximum'), |
|
|
|
|
|
('sum', 'Sum'), |
|
|
|
|
|
('avg', 'Average'), |
|
|
|
|
|
('median', 'Median'), |
|
|
|
|
|
], string='Function') |
|
|
|
|
|
field_id = fields.Many2one( |
|
|
|
|
|
comodel_name='ir.model.fields', string='Field', |
|
|
|
|
|
domain="[('model_id', '=', model_id)," |
|
|
|
|
|
" ('ttype', 'in', ['float', 'int'])]") |
|
|
|
|
|
active = fields.Boolean( |
|
|
|
|
|
compute='_get_tile_info', readonly=True, search='_search_active') |
|
|
|
|
|
background_color = fields.Char(default='#0E6C7E', oldname='color') |
|
|
|
|
|
font_color = fields.Char(default='#FFFFFF') |
|
|
|
|
|
sequence = fields.Integer(default=0, required=True) |
|
|
|
|
|
======= |
|
|
|
|
|
# Constraints and onchanges |
|
|
# Constraints and onchanges |
|
|
@api.one |
|
|
@api.one |
|
|
@api.constrains('model_id', 'primary_field_id', 'secondary_field_id') |
|
|
@api.constrains('model_id', 'primary_field_id', 'secondary_field_id') |
|
@ -344,9 +234,9 @@ class TileTile(models.Model): |
|
|
self.primary_field_id.model_id.id != self.model_id.id, |
|
|
self.primary_field_id.model_id.id != self.model_id.id, |
|
|
self.secondary_field_id and |
|
|
self.secondary_field_id and |
|
|
self.secondary_field_id.model_id.id != self.model_id.id |
|
|
self.secondary_field_id.model_id.id != self.model_id.id |
|
|
]): |
|
|
|
|
|
raise ValidationError( |
|
|
|
|
|
_("Please select a field from the selected model.")) |
|
|
|
|
|
|
|
|
]): |
|
|
|
|
|
raise ValidationError( |
|
|
|
|
|
_("Please select a field from the selected model.")) |
|
|
|
|
|
|
|
|
@api.onchange('model_id') |
|
|
@api.onchange('model_id') |
|
|
def _onchange_model_id(self): |
|
|
def _onchange_model_id(self): |
|
@ -359,34 +249,8 @@ class TileTile(models.Model): |
|
|
self.primary_field_id = False |
|
|
self.primary_field_id = False |
|
|
if self.secondary_function in [False, 'count']: |
|
|
if self.secondary_function in [False, 'count']: |
|
|
self.secondary_field_id = False |
|
|
self.secondary_field_id = False |
|
|
>>>>>>> 3ab676d... [IMP][8.0][web_dashboard_tile] Refactor (see changes in description) (#476) |
|
|
|
|
|
|
|
|
|
|
|
# Constraint Section |
|
|
|
|
|
def _check_model_id_field_id(self, cr, uid, ids, context=None): |
|
|
|
|
|
for t in self.browse(cr, uid, ids, context=context): |
|
|
|
|
|
if t.field_id and t.field_id.model_id.id != t.model_id.id: |
|
|
|
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
def _check_field_id_field_function(self, cr, uid, ids, context=None): |
|
|
|
|
|
for t in self.browse(cr, uid, ids, context=context): |
|
|
|
|
|
if t.field_id and not t.field_function or\ |
|
|
|
|
|
t.field_function and not t.field_id: |
|
|
|
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
_constraints = [ |
|
|
|
|
|
( |
|
|
|
|
|
_check_model_id_field_id, |
|
|
|
|
|
"Error ! Please select a field of the selected model.", |
|
|
|
|
|
['model_id', 'field_id']), |
|
|
|
|
|
( |
|
|
|
|
|
_check_field_id_field_function, |
|
|
|
|
|
"Error ! Please set both fields: 'Field' and 'Function'.", |
|
|
|
|
|
['field_id', 'field_function']), |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
# View / action Section |
|
|
|
|
|
|
|
|
# Action methods |
|
|
@api.multi |
|
|
@api.multi |
|
|
def open_link(self): |
|
|
def open_link(self): |
|
|
res = { |
|
|
res = { |
|
@ -403,8 +267,7 @@ class TileTile(models.Model): |
|
|
} |
|
|
} |
|
|
if self.action_id: |
|
|
if self.action_id: |
|
|
res.update(self.action_id.read( |
|
|
res.update(self.action_id.read( |
|
|
['view_type', 'view_mode', 'view_id', 'type'])[0]) |
|
|
|
|
|
# FIXME: restore original Domain + Filter would be better |
|
|
|
|
|
|
|
|
['view_type', 'view_mode', 'type'])[0]) |
|
|
return res |
|
|
return res |
|
|
|
|
|
|
|
|
@api.model |
|
|
@api.model |
|
|