From 227e26d39d082838e181831c6ce96cd76a297ca3 Mon Sep 17 00:00:00 2001 From: flachica Date: Thu, 4 Jun 2020 14:12:11 +0200 Subject: [PATCH] [IMP] kpi: black, isort --- kpi/__manifest__.py | 25 ++- kpi/data/kpi_data.xml | 8 +- kpi/models/kpi.py | 199 ++++++++++++------------ kpi/models/kpi_category.py | 4 +- kpi/models/kpi_history.py | 18 +-- kpi/models/kpi_threshold.py | 75 +++++---- kpi/models/kpi_threshold_range.py | 165 +++++++++++--------- kpi/readme/DESCRIPTION.rst | 2 +- kpi/readme/USAGE.rst | 2 +- kpi/security/kpi_security.xml | 45 +++--- kpi/tests/test_kpi.py | 174 +++++++++++---------- kpi/views/kpi_category_views.xml | 18 +-- kpi/views/kpi_history_views.xml | 25 ++- kpi/views/kpi_threshold_range_views.xml | 114 +++++++++----- kpi/views/kpi_threshold_views.xml | 54 ++++--- kpi/views/kpi_views.xml | 148 ++++++++++-------- kpi/views/menu.xml | 99 ++++++------ 17 files changed, 641 insertions(+), 534 deletions(-) diff --git a/kpi/__manifest__.py b/kpi/__manifest__.py index 7ebaf1b41..636a13e63 100644 --- a/kpi/__manifest__.py +++ b/kpi/__manifest__.py @@ -8,20 +8,17 @@ "website": "http://www.savoirfairelinux.com", "license": "AGPL-3", "category": "Report", - "depends": [ - 'base_external_dbsource', - 'web_widget_color', - ], + "depends": ["base_external_dbsource", "web_widget_color",], "data": [ - 'security/kpi_security.xml', - 'security/ir.model.access.csv', - 'views/kpi_category_views.xml', - 'views/kpi_history_views.xml', - 'views/kpi_threshold_range_views.xml', - 'views/kpi_threshold_views.xml', - 'views/kpi_views.xml', - 'views/menu.xml', - 'data/kpi_data.xml', + "security/kpi_security.xml", + "security/ir.model.access.csv", + "views/kpi_category_views.xml", + "views/kpi_history_views.xml", + "views/kpi_threshold_range_views.xml", + "views/kpi_threshold_views.xml", + "views/kpi_views.xml", + "views/menu.xml", + "data/kpi_data.xml", ], "images": [ "images/kpi_definition.png", @@ -29,5 +26,5 @@ "images/kpi_threshold.png", "images/kpi_range.png", ], - 'installable': True, + "installable": True, } diff --git a/kpi/data/kpi_data.xml b/kpi/data/kpi_data.xml index 8bf99d95e..0dce29dec 100644 --- a/kpi/data/kpi_data.xml +++ b/kpi/data/kpi_data.xml @@ -1,15 +1,15 @@ - + Update KPI values - + 1 hours -1 - - + + code model.update_kpi_value() diff --git a/kpi/models/kpi.py b/kpi/models/kpi.py index b9a6771ed..b0bac03ea 100644 --- a/kpi/models/kpi.py +++ b/kpi/models/kpi.py @@ -1,39 +1,46 @@ # Copyright 2012 - Now Savoir-faire Linux # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from datetime import datetime -from dateutil.relativedelta import relativedelta -from odoo import fields, models, api -from odoo.tools.safe_eval import safe_eval -from odoo.tools import ( - DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT, -) -import re import logging +import re +from datetime import datetime + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT +from odoo.tools.safe_eval import safe_eval + _logger = logging.getLogger(__name__) def is_one_value(result): # check if sql query returns only one value - if type(result) is dict and 'value' in result.dictfetchone(): + if type(result) is dict and "value" in result.dictfetchone(): return True - elif type(result) is list and 'value' in result[0]: + elif type(result) is list and "value" in result[0]: return True else: return False -RE_SELECT_QUERY = re.compile('.*(' + '|'.join(( - 'INSERT', - 'UPDATE', - 'DELETE', - 'CREATE', - 'ALTER', - 'DROP', - 'GRANT', - 'REVOKE', - 'INDEX', -)) + ')') +RE_SELECT_QUERY = re.compile( + ".*(" + + "|".join( + ( + "INSERT", + "UPDATE", + "DELETE", + "CREATE", + "ALTER", + "DROP", + "GRANT", + "REVOKE", + "INDEX", + ) + ) + + ")" +) def is_sql_or_ddl_statement(query): @@ -47,70 +54,63 @@ class KPI(models.Model): _name = "kpi" _description = "Key Performance Indicator" - name = fields.Char('Name', required=True) - description = fields.Text('Description') - category_id = fields.Many2one( - 'kpi.category', - 'Category', - required=True, - ) - threshold_id = fields.Many2one( - 'kpi.threshold', - 'Threshold', - required=True, - ) - periodicity = fields.Integer('Periodicity', default=1) + name = fields.Char("Name", required=True) + description = fields.Text("Description") + category_id = fields.Many2one("kpi.category", "Category", required=True,) + threshold_id = fields.Many2one("kpi.threshold", "Threshold", required=True,) + periodicity = fields.Integer("Periodicity", default=1) - periodicity_uom = fields.Selection(( - ('minute', 'Minute'), - ('hour', 'Hour'), - ('day', 'Day'), - ('week', 'Week'), - ('month', 'Month') - ), 'Periodicity UoM', required=True, default='day') - - next_execution_date = fields.Datetime( - 'Next execution date', - readonly=True, + periodicity_uom = fields.Selection( + ( + ("minute", "Minute"), + ("hour", "Hour"), + ("day", "Day"), + ("week", "Week"), + ("month", "Month"), + ), + "Periodicity UoM", + required=True, + default="day", ) - value = fields.Float(string='Value', - compute="_compute_display_last_kpi_value", - ) - color = fields.Text('Color', compute="_compute_display_last_kpi_value",) + + next_execution_date = fields.Datetime("Next execution date", readonly=True,) + value = fields.Float(string="Value", compute="_compute_display_last_kpi_value",) + color = fields.Text("Color", compute="_compute_display_last_kpi_value",) last_execution = fields.Datetime( - 'Last execution', compute="_compute_display_last_kpi_value",) - kpi_type = fields.Selection(( - ('python', 'Python'), - ('local', 'SQL - Local DB'), - ('external', 'SQL - External DB') - ), 'KPI Computation Type') + "Last execution", compute="_compute_display_last_kpi_value", + ) + kpi_type = fields.Selection( + ( + ("python", "Python"), + ("local", "SQL - Local DB"), + ("external", "SQL - External DB"), + ), + "KPI Computation Type", + ) - dbsource_id = fields.Many2one( - 'base.external.dbsource', - 'External DB Source', - ) + dbsource_id = fields.Many2one("base.external.dbsource", "External DB Source",) kpi_code = fields.Text( - 'KPI Code', - help=("SQL code must return the result as 'value' " - "(i.e. 'SELECT 5 AS value')."), - ) - history_ids = fields.One2many( - 'kpi.history', - 'kpi_id', - 'History', + "KPI Code", + help=( + "SQL code must return the result as 'value' " "(i.e. 'SELECT 5 AS value')." + ), ) + history_ids = fields.One2many("kpi.history", "kpi_id", "History",) active = fields.Boolean( - 'Active', - help=("Only active KPIs will be updated by the scheduler based on" - " the periodicity configuration."), default=True + "Active", + help=( + "Only active KPIs will be updated by the scheduler based on" + " the periodicity configuration." + ), + default=True, ) company_id = fields.Many2one( - 'res.company', 'Company', - default=lambda self: self.env.user.company_id.id) + "res.company", "Company", default=lambda self: self.env.user.company_id.id + ) @api.multi def _compute_display_last_kpi_value(self): - history_obj = self.env['kpi.history'] + history_obj = self.env["kpi.history"] for obj in self: history_ids = history_obj.search([("kpi_id", "=", obj.id)]) if history_ids: @@ -120,7 +120,7 @@ class KPI(models.Model): obj.last_execution = his.date else: obj.value = 0 - obj.color = '#FFFFFF' + obj.color = "#FFFFFF" obj.last_execution = False @api.multi @@ -128,51 +128,53 @@ class KPI(models.Model): self.ensure_one() kpi_value = 0 if self.kpi_code: - if self.kpi_type == 'local' and is_sql_or_ddl_statement( - self.kpi_code): + if self.kpi_type == "local" and is_sql_or_ddl_statement(self.kpi_code): self.env.cr.execute(self.kpi_code) dic = self.env.cr.dictfetchall() if is_one_value(dic): - kpi_value = dic[0]['value'] - elif (self.kpi_type == 'external' and self.dbsource_id.id and - is_sql_or_ddl_statement(self.kpi_code)): + kpi_value = dic[0]["value"] + elif ( + self.kpi_type == "external" + and self.dbsource_id.id + and is_sql_or_ddl_statement(self.kpi_code) + ): dbsrc_obj = self.dbsource_id res = dbsrc_obj.execute(self.kpi_code) if is_one_value(res): - kpi_value = res[0]['value'] - elif self.kpi_type == 'python': - kpi_value = safe_eval(self.kpi_code, {'self': self}) + kpi_value = res[0]["value"] + elif self.kpi_type == "python": + kpi_value = safe_eval(self.kpi_code, {"self": self}) if isinstance(kpi_value, dict): res = kpi_value else: threshold_obj = self.threshold_id res = { - 'value': kpi_value, - 'color': threshold_obj.get_color(kpi_value), + "value": kpi_value, + "color": threshold_obj.get_color(kpi_value), } - res.update({'kpi_id': self.id}) + res.update({"kpi_id": self.id}) return res @api.multi def compute_kpi_value(self): for obj in self: history_vals = obj._get_kpi_value() - history_obj = self.env['kpi.history'] + history_obj = self.env["kpi.history"] history_obj.sudo().create(history_vals) return True @api.multi def update_next_execution_date(self): for obj in self: - if obj.periodicity_uom == 'hour': + if obj.periodicity_uom == "hour": delta = relativedelta(hours=obj.periodicity) - elif obj.periodicity_uom == 'minute': + elif obj.periodicity_uom == "minute": delta = relativedelta(minutes=obj.periodicity) - elif obj.periodicity_uom == 'day': + elif obj.periodicity_uom == "day": delta = relativedelta(days=obj.periodicity) - elif obj.periodicity_uom == 'week': + elif obj.periodicity_uom == "week": delta = relativedelta(weeks=obj.periodicity) - elif obj.periodicity_uom == 'month': + elif obj.periodicity_uom == "month": delta = relativedelta(months=obj.periodicity) else: delta = relativedelta() @@ -186,15 +188,14 @@ class KPI(models.Model): @api.model def update_kpi_value(self): filters = [ - '&', - '|', - ('active', '=', True), - ('next_execution_date', '<=', datetime.now().strftime( - DATETIME_FORMAT)), - ('next_execution_date', '=', False), + "&", + "|", + ("active", "=", True), + ("next_execution_date", "<=", datetime.now().strftime(DATETIME_FORMAT)), + ("next_execution_date", "=", False), ] - if 'filters' in self.env.context: - filters.extend(self.env.context['filters']) + if "filters" in self.env.context: + filters.extend(self.env.context["filters"]) obj_ids = self.search(filters) res = None diff --git a/kpi/models/kpi_category.py b/kpi/models/kpi_category.py index 3c97e3aa9..17c572792 100644 --- a/kpi/models/kpi_category.py +++ b/kpi/models/kpi_category.py @@ -9,5 +9,5 @@ class KPICategory(models.Model): _name = "kpi.category" _description = "KPI Category" - name = fields.Char('Name', size=50, required=True) - description = fields.Text('Description') + name = fields.Char("Name", size=50, required=True) + description = fields.Text("Description") diff --git a/kpi/models/kpi_history.py b/kpi/models/kpi_history.py index 256aa5183..4090479f4 100644 --- a/kpi/models/kpi_history.py +++ b/kpi/models/kpi_history.py @@ -11,18 +11,16 @@ class KPIHistory(models.Model): _description = "History of the KPI" _order = "date desc" - name = fields.Char('Name', size=150, required=True, - default=fields.Datetime.now(),) - kpi_id = fields.Many2one('kpi', 'KPI', required=True) + name = fields.Char("Name", size=150, required=True, default=fields.Datetime.now(),) + kpi_id = fields.Many2one("kpi", "KPI", required=True) date = fields.Datetime( - 'Execution Date', + "Execution Date", required=True, readonly=True, - default=lambda r: fields.Datetime.now() + default=lambda r: fields.Datetime.now(), ) - value = fields.Float('Value', required=True, readonly=True) - color = fields.Text('Color', required=True, - readonly=True, default='#FFFFFF') + value = fields.Float("Value", required=True, readonly=True) + color = fields.Text("Color", required=True, readonly=True, default="#FFFFFF") company_id = fields.Many2one( - 'res.company', 'Company', - default=lambda self: self.env.user.company_id.id) + "res.company", "Company", default=lambda self: self.env.user.company_id.id + ) diff --git a/kpi/models/kpi_threshold.py b/kpi/models/kpi_threshold.py index dcc6199cf..6623f03e6 100644 --- a/kpi/models/kpi_threshold.py +++ b/kpi/models/kpi_threshold.py @@ -1,7 +1,7 @@ # Copyright 2012 - Now Savoir-faire Linux # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models, api, exceptions, _ +from odoo import _, api, exceptions, fields, models class KPIThreshold(models.Model): @@ -20,9 +20,11 @@ class KPIThreshold(models.Model): if not range1.valid: obj.valid = False break - for range2 in (obj.range_ids-range1): - if (range1.max_value >= range2.min_value and - range1.min_value <= range2.max_value): + for range2 in obj.range_ids - range1: + if ( + range1.max_value >= range2.min_value + and range1.min_value <= range2.max_value + ): obj.valid = False break if obj.valid: @@ -30,53 +32,64 @@ class KPIThreshold(models.Model): else: obj.invalid_message = ( "Some ranges are invalid or overlapping. " - "Please make sure your ranges do not overlap.") + "Please make sure your ranges do not overlap." + ) - name = fields.Char('Name', size=50, required=True) + name = fields.Char("Name", size=50, required=True) range_ids = fields.Many2many( - 'kpi.threshold.range', - 'kpi_threshold_range_rel', - 'threshold_id', - 'range_id', - 'Ranges' + "kpi.threshold.range", + "kpi_threshold_range_rel", + "threshold_id", + "range_id", + "Ranges", ) - valid = fields.Boolean(string='Valid', required=True, - compute="_compute_is_valid_threshold", default=True) - invalid_message = fields.Char(string='Message', size=100, - compute="_compute_is_valid_threshold") - kpi_ids = fields.One2many('kpi', 'threshold_id', 'KPIs') + valid = fields.Boolean( + string="Valid", + required=True, + compute="_compute_is_valid_threshold", + default=True, + ) + invalid_message = fields.Char( + string="Message", size=100, compute="_compute_is_valid_threshold" + ) + kpi_ids = fields.One2many("kpi", "threshold_id", "KPIs") company_id = fields.Many2one( - 'res.company', 'Company', - default=lambda self: self.env.user.company_id.id) + "res.company", "Company", default=lambda self: self.env.user.company_id.id + ) @api.model def create(self, data): # check if ranges overlap # TODO: This code can be done better - range_obj1 = self.env['kpi.threshold.range'] - range_obj2 = self.env['kpi.threshold.range'] - if data.get('range_ids'): - for range1 in data['range_ids'][0][2]: + range_obj1 = self.env["kpi.threshold.range"] + range_obj2 = self.env["kpi.threshold.range"] + if data.get("range_ids"): + for range1 in data["range_ids"][0][2]: range_obj1 = range_obj1.browse(range1) - for range2 in data['range_ids'][0][2]: + for range2 in data["range_ids"][0][2]: range_obj2 = range_obj2.browse(range2) - if (range_obj1.valid and range_obj2.valid and - range_obj1.min_value < range_obj2.min_value): + if ( + range_obj1.valid + and range_obj2.valid + and range_obj1.min_value < range_obj2.min_value + ): if range_obj1.max_value > range_obj2.min_value: raise exceptions.Warning( _("Two of your ranges are overlapping."), - _("Make sure your ranges do not overlap!") + _("Make sure your ranges do not overlap!"), ) - range_obj2 = self.env['kpi.threshold.range'] - range_obj1 = self.env['kpi.threshold.range'] + range_obj2 = self.env["kpi.threshold.range"] + range_obj1 = self.env["kpi.threshold.range"] return super(KPIThreshold, self).create(data) @api.multi def get_color(self, kpi_value): - color = '#FFFFFF' + color = "#FFFFFF" for obj in self: for range_obj in obj.range_ids: - if (range_obj.min_value <= kpi_value <= range_obj.max_value and - range_obj.valid): + if ( + range_obj.min_value <= kpi_value <= range_obj.max_value + and range_obj.valid + ): color = range_obj.color return color diff --git a/kpi/models/kpi_threshold_range.py b/kpi/models/kpi_threshold_range.py index fb40b836e..cde2b5242 100644 --- a/kpi/models/kpi_threshold_range.py +++ b/kpi/models/kpi_threshold_range.py @@ -1,32 +1,39 @@ # Copyright 2012 - Now Savoir-faire Linux # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models, api -from odoo.tools.safe_eval import safe_eval import re +from odoo import api, fields, models +from odoo.tools.safe_eval import safe_eval + def is_one_value(result): # check if sql query returns only one value - if type(result) is dict and 'value' in result.dictfetchone(): + if type(result) is dict and "value" in result.dictfetchone(): return True - elif type(result) is list and 'value' in result[0]: + elif type(result) is list and "value" in result[0]: return True else: return False -RE_SELECT_QUERY = re.compile('.*(' + '|'.join(( - 'INSERT', - 'UPDATE', - 'DELETE', - 'CREATE', - 'ALTER', - 'DROP', - 'GRANT', - 'REVOKE', - 'INDEX', -)) + ')') +RE_SELECT_QUERY = re.compile( + ".*(" + + "|".join( + ( + "INSERT", + "UPDATE", + "DELETE", + "CREATE", + "ALTER", + "DROP", + "GRANT", + "REVOKE", + "INDEX", + ) + ) + + ")" +) def is_sql_or_ddl_statement(query): @@ -38,60 +45,64 @@ class KPIThresholdRange(models.Model): """ KPI Threshold Range """ + _name = "kpi.threshold.range" _description = "KPI Threshold Range" - name = fields.Char('Name', size=50, required=True) - valid = fields.Boolean(string='Valid', required=True, - compute="_compute_is_valid_range", default=True) - invalid_message = fields.Char(string='Message', size=100, - compute="_compute_is_valid_range") - min_type = fields.Selection(( - ('static', 'Fixed value'), - ('python', 'Python Code'), - ('local', 'SQL - Local DB'), - ('external', 'SQL - Externa DB'), - ), 'Min Type', required=True) - min_value = fields.Float(string='Minimum Value', - compute="_compute_min_value") - min_fixed_value = fields.Float('Minimum Fixed Value') - min_code = fields.Text('Minimum Computation Code') - min_error = fields.Char('Minimum Error', compute="_compute_min_value") - min_dbsource_id = fields.Many2one( - 'base.external.dbsource', - 'External DB Source Minimum', + name = fields.Char("Name", size=50, required=True) + valid = fields.Boolean( + string="Valid", required=True, compute="_compute_is_valid_range", default=True ) - max_type = fields.Selection(( - ('static', 'Fixed value'), - ('python', 'Python Code'), - ('local', 'SQL - Local DB'), - ('external', 'SQL - External DB'), - ), 'Max Type', required=True) - max_value = fields.Float(string='Maximum Value', - compute="_compute_max_value") - max_fixed_value = fields.Float('Maximum Fixed Value') - max_code = fields.Text('Maximum Computation Code') - max_error = fields.Char('Maximum Error', compute="_compute_max_value") + invalid_message = fields.Char( + string="Message", size=100, compute="_compute_is_valid_range" + ) + min_type = fields.Selection( + ( + ("static", "Fixed value"), + ("python", "Python Code"), + ("local", "SQL - Local DB"), + ("external", "SQL - Externa DB"), + ), + "Min Type", + required=True, + ) + min_value = fields.Float(string="Minimum Value", compute="_compute_min_value") + min_fixed_value = fields.Float("Minimum Fixed Value") + min_code = fields.Text("Minimum Computation Code") + min_error = fields.Char("Minimum Error", compute="_compute_min_value") + min_dbsource_id = fields.Many2one( + "base.external.dbsource", "External DB Source Minimum", + ) + max_type = fields.Selection( + ( + ("static", "Fixed value"), + ("python", "Python Code"), + ("local", "SQL - Local DB"), + ("external", "SQL - External DB"), + ), + "Max Type", + required=True, + ) + max_value = fields.Float(string="Maximum Value", compute="_compute_max_value") + max_fixed_value = fields.Float("Maximum Fixed Value") + max_code = fields.Text("Maximum Computation Code") + max_error = fields.Char("Maximum Error", compute="_compute_max_value") max_dbsource_id = fields.Many2one( - 'base.external.dbsource', - 'External DB Source Maximum', + "base.external.dbsource", "External DB Source Maximum", ) - color = fields.Char( - string="Color", - help="Choose your color" - ) + color = fields.Char(string="Color", help="Choose your color") threshold_ids = fields.Many2many( - 'kpi.threshold', - 'kpi_threshold_range_rel', - 'range_id', - 'threshold_id', - 'Thresholds', + "kpi.threshold", + "kpi_threshold_range_rel", + "range_id", + "threshold_id", + "Thresholds", ) company_id = fields.Many2one( - 'res.company', 'Company', - default=lambda self: self.env.user.company_id.id) + "res.company", "Company", default=lambda self: self.env.user.company_id.id + ) @api.multi def _compute_min_value(self): @@ -99,19 +110,21 @@ class KPIThresholdRange(models.Model): value = None error = None try: - if obj.min_type == 'local' and is_sql_or_ddl_statement( - obj.min_code): + if obj.min_type == "local" and is_sql_or_ddl_statement(obj.min_code): self.env.cr.execute(obj.min_code) dic = self.env.cr.dictfetchall() if is_one_value(dic): - value = dic[0]['value'] - elif (obj.min_type == 'external' and obj.min_dbsource_id.id and - is_sql_or_ddl_statement(obj.min_code)): + value = dic[0]["value"] + elif ( + obj.min_type == "external" + and obj.min_dbsource_id.id + and is_sql_or_ddl_statement(obj.min_code) + ): dbsrc_obj = obj.min_dbsource_id res = dbsrc_obj.execute(obj.min_code) if is_one_value(res): - value = res[0]['value'] - elif obj.min_type == 'python': + value = res[0]["value"] + elif obj.min_type == "python": value = safe_eval(obj.min_code) else: value = obj.min_fixed_value @@ -127,19 +140,21 @@ class KPIThresholdRange(models.Model): value = None error = None try: - if obj.max_type == 'local' and is_sql_or_ddl_statement( - obj.max_code): + if obj.max_type == "local" and is_sql_or_ddl_statement(obj.max_code): self.env.cr.execute(obj.max_code) dic = self.env.cr.dictfetchall() if is_one_value(dic): - value = dic[0]['value'] - elif (obj.max_type == 'external' and obj.max_dbsource_id.id and - is_sql_or_ddl_statement(obj.max_code)): + value = dic[0]["value"] + elif ( + obj.max_type == "external" + and obj.max_dbsource_id.id + and is_sql_or_ddl_statement(obj.max_code) + ): dbsrc_obj = obj.max_dbsource_id res = dbsrc_obj.execute(obj.max_code) if is_one_value(res): - value = res[0]['value'] - elif obj.max_type == 'python': + value = res[0]["value"] + elif obj.max_type == "python": value = safe_eval(obj.max_code) else: value = obj.max_fixed_value @@ -156,12 +171,14 @@ class KPIThresholdRange(models.Model): obj.valid = False obj.invalid_message = ( "Either minimum or maximum value has " - "computation errors. Please fix them.") + "computation errors. Please fix them." + ) elif obj.max_value < obj.min_value: obj.valid = False obj.invalid_message = ( "Minimum value is greater than the maximum " - "value! Please adjust them.") + "value! Please adjust them." + ) else: obj.valid = True obj.invalid_message = "" diff --git a/kpi/readme/DESCRIPTION.rst b/kpi/readme/DESCRIPTION.rst index c73ee9b90..e98040be7 100644 --- a/kpi/readme/DESCRIPTION.rst +++ b/kpi/readme/DESCRIPTION.rst @@ -12,4 +12,4 @@ A threshold is a list of ranges and a range is: * a name (like Good, Warning, Bad) * a minimum value (fixed, sql query or python code) * a maximum value (fixed, sql query or python code) -* color (RGB code like #00FF00 for green, #FFA500 for orange, #FF0000 for red) \ No newline at end of file +* color (RGB code like #00FF00 for green, #FFA500 for orange, #FF0000 for red) diff --git a/kpi/readme/USAGE.rst b/kpi/readme/USAGE.rst index c70fc22a5..8306397cd 100644 --- a/kpi/readme/USAGE.rst +++ b/kpi/readme/USAGE.rst @@ -1,2 +1,2 @@ Example of usage: -https://www.youtube.com/watch?v=OC4-y2klzIk \ No newline at end of file +https://www.youtube.com/watch?v=OC4-y2klzIk diff --git a/kpi/security/kpi_security.xml b/kpi/security/kpi_security.xml index 64a20cca0..bd0d80011 100755 --- a/kpi/security/kpi_security.xml +++ b/kpi/security/kpi_security.xml @@ -1,46 +1,47 @@ - + - Manage KPI's - - + + - - kpi multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - kpi_threshold_range multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - kpi_threshold multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - kpi_history multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - diff --git a/kpi/tests/test_kpi.py b/kpi/tests/test_kpi.py index 9bfcb2455..00e363b49 100644 --- a/kpi/tests/test_kpi.py +++ b/kpi/tests/test_kpi.py @@ -4,113 +4,125 @@ from odoo.tests.common import TransactionCase class TestKPI(TransactionCase): - def setUp(self): super(TestKPI, self).setUp() def test_invalid_threshold_range(self): - range1 = self.env['kpi.threshold.range'].create({ - 'name': 'Range1', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 3, - 'max_fixed_value': 1, - }) - range2 = self.env['kpi.threshold.range'].create({ - 'name': 'Range2', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 4, - 'max_fixed_value': 10, - }) + range1 = self.env["kpi.threshold.range"].create( + { + "name": "Range1", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 3, + "max_fixed_value": 1, + } + ) + range2 = self.env["kpi.threshold.range"].create( + { + "name": "Range2", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 4, + "max_fixed_value": 10, + } + ) self.assertFalse(range1.valid) self.assertTrue(range2.valid) def test_invalid_threshold(self): - range1 = self.env['kpi.threshold.range'].create({ - 'name': 'Range1', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 1, - 'max_fixed_value': 4, - }) - range2 = self.env['kpi.threshold.range'].create({ - 'name': 'Range2', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 4, - 'max_fixed_value': 10, - }) - range3 = self.env['kpi.threshold.range'].create({ - 'name': 'Range3', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 1, - 'max_fixed_value': 3, - }) - range_invalid = self.env['kpi.threshold.range'].create({ - 'name': 'RangeInvalid', - 'min_type': 'static', - 'max_type': 'static', - 'min_fixed_value': 3, - 'max_fixed_value': 1, - }) + range1 = self.env["kpi.threshold.range"].create( + { + "name": "Range1", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 1, + "max_fixed_value": 4, + } + ) + range2 = self.env["kpi.threshold.range"].create( + { + "name": "Range2", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 4, + "max_fixed_value": 10, + } + ) + range3 = self.env["kpi.threshold.range"].create( + { + "name": "Range3", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 1, + "max_fixed_value": 3, + } + ) + range_invalid = self.env["kpi.threshold.range"].create( + { + "name": "RangeInvalid", + "min_type": "static", + "max_type": "static", + "min_fixed_value": 3, + "max_fixed_value": 1, + } + ) - threshold1 = self.env['kpi.threshold'].create({ - 'name': 'Threshold1', - 'range_ids': [(6, 0, [range1.id, range2.id])], - }) + threshold1 = self.env["kpi.threshold"].create( + {"name": "Threshold1", "range_ids": [(6, 0, [range1.id, range2.id])],} + ) - threshold2 = self.env['kpi.threshold'].create({ - 'name': 'Threshold1', - 'range_ids': [(6, 0, [range3.id, range2.id])], - }) + threshold2 = self.env["kpi.threshold"].create( + {"name": "Threshold1", "range_ids": [(6, 0, [range3.id, range2.id])],} + ) - threshold3 = self.env['kpi.threshold'].create({ - 'name': 'Threshold1', - 'range_ids': [(6, 0, [range_invalid.id, range2.id])], - }) + threshold3 = self.env["kpi.threshold"].create( + { + "name": "Threshold1", + "range_ids": [(6, 0, [range_invalid.id, range2.id])], + } + ) self.assertFalse(threshold1.valid) self.assertTrue(threshold2.valid) self.assertFalse(threshold3.valid) def test_invalid_threshold_range_exception(self): - range_error = self.env['kpi.threshold.range'].create({ - 'name': 'RangeError', - 'min_type': 'python', - 'min_code': '', - 'max_type': 'static', - 'max_fixed_value': 1, - }) + range_error = self.env["kpi.threshold.range"].create( + { + "name": "RangeError", + "min_type": "python", + "min_code": "", + "max_type": "static", + "max_fixed_value": 1, + } + ) self.assertFalse(range_error.valid) def test_kpi_python(self): - kpi_category = self.env['kpi.category'].create({ - 'name': 'Dynamic KPIs' - }) - kpi_threshold = self.env['kpi.threshold'].create({ - 'name': 'KPI Threshold for dynamic KPIs' - }) + kpi_category = self.env["kpi.category"].create({"name": "Dynamic KPIs"}) + kpi_threshold = self.env["kpi.threshold"].create( + {"name": "KPI Threshold for dynamic KPIs"} + ) kpi_code = """ { 'value': 1.0, 'color': '#00FF00' } """ - kpi = self.env['kpi'].create({ - 'name': 'Dynamic python kpi', - 'description': 'Dynamic python kpi', - 'category_id': kpi_category.id, - 'threshold_id': kpi_threshold.id, - 'periodicity': 1, - 'periodicity_uom': 'day', - 'kpi_type': 'python', - 'kpi_code': kpi_code, - }) + kpi = self.env["kpi"].create( + { + "name": "Dynamic python kpi", + "description": "Dynamic python kpi", + "category_id": kpi_category.id, + "threshold_id": kpi_threshold.id, + "periodicity": 1, + "periodicity_uom": "day", + "kpi_type": "python", + "kpi_code": kpi_code, + } + ) kpi.update_kpi_value() - kpi_history = self.env['kpi.history'].search( - [('kpi_id', '=', kpi.id)]) + kpi_history = self.env["kpi.history"].search([("kpi_id", "=", kpi.id)]) self.assertEqual(len(kpi_history), 1) - self.assertEqual(kpi_history.color, '#00FF00') + self.assertEqual(kpi_history.color, "#00FF00") self.assertEqual(kpi_history.value, 1.0) diff --git a/kpi/views/kpi_category_views.xml b/kpi/views/kpi_category_views.xml index bfd3b976b..cca3fdbfb 100644 --- a/kpi/views/kpi_category_views.xml +++ b/kpi/views/kpi_category_views.xml @@ -1,20 +1,17 @@ - + - - - + kpi.category.tree kpi.category - + - kpi.category.form kpi.category @@ -22,20 +19,19 @@
- - - + + +
- Categories kpi.category form tree,form - +
diff --git a/kpi/views/kpi_history_views.xml b/kpi/views/kpi_history_views.xml index b302adedf..c2abf7ebd 100644 --- a/kpi/views/kpi_history_views.xml +++ b/kpi/views/kpi_history_views.xml @@ -1,7 +1,6 @@ - + - @@ -9,11 +8,11 @@ kpi.history - - - - - + + + + + @@ -24,12 +23,12 @@
- - - - - - + + + + + +
diff --git a/kpi/views/kpi_threshold_range_views.xml b/kpi/views/kpi_threshold_range_views.xml index 958cb77f4..7e577f17c 100644 --- a/kpi/views/kpi_threshold_range_views.xml +++ b/kpi/views/kpi_threshold_range_views.xml @@ -1,7 +1,6 @@ - + - @@ -9,16 +8,15 @@ kpi.threshold.range - - - - - - + + + + + + - kpi.threshold.range.form kpi.threshold.range @@ -26,46 +24,90 @@
- - - + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - + + + +
- Ranges kpi.threshold.range form tree,form - +
diff --git a/kpi/views/kpi_threshold_views.xml b/kpi/views/kpi_threshold_views.xml index e18084043..52a788206 100644 --- a/kpi/views/kpi_threshold_views.xml +++ b/kpi/views/kpi_threshold_views.xml @@ -1,7 +1,6 @@ - + - @@ -9,13 +8,12 @@ kpi.threshold - - - + + + - kpi.threshold.form kpi.threshold @@ -23,31 +21,43 @@
- - - - - - - - - - - - - - + + + + + + + + + + + + + +
- Thresholds kpi.threshold form tree,form - +
diff --git a/kpi/views/kpi_views.xml b/kpi/views/kpi_views.xml index b8283b64f..346bf0d85 100644 --- a/kpi/views/kpi_views.xml +++ b/kpi/views/kpi_views.xml @@ -1,4 +1,4 @@ - + @@ -6,14 +6,14 @@ kpi.tree kpi - + - - - - - + + + + + @@ -22,20 +22,32 @@ kpi - - - - - + + + + +
-
-
- -
- +
+ + + +
+
+ + + +
+
+
@@ -44,32 +56,32 @@ - kpi.filter kpi - - - - - + + + + + - + - - + + - kpi.form kpi @@ -79,76 +91,78 @@ - - + + - - - -