diff --git a/web_dashboard_tile/__openerp__.py b/web_dashboard_tile/__openerp__.py index 53e32271d..7d78e47ea 100644 --- a/web_dashboard_tile/__openerp__.py +++ b/web_dashboard_tile/__openerp__.py @@ -6,34 +6,27 @@ "name": "Dashboard Tile", "summary": "Add Tiles to Dashboard", "version": "9.0.1.1.0", - "depends": [ - 'web', - 'board', - 'mail', - 'web_widget_color', - ], - 'author': 'initOS GmbH & Co. KG, ' - 'GRAP, ' - 'Odoo Community Association (OCA)', + "depends": ["web", "board", "mail", "web_widget_color"], + "author": "initOS GmbH & Co. KG, " + "GRAP, " + "Odoo Community Association (OCA)", "category": "web", - 'license': 'AGPL-3', - 'contributors': [ - 'initOS GmbH & Co. KG', - 'GRAP', - 'Iván Todorovich ' + "license": "AGPL-3", + "contributors": [ + "initOS GmbH & Co. KG", + "GRAP", + "Iván Todorovich ", ], - 'data': [ - 'views/tile.xml', - 'views/templates.xml', - 'security/ir.model.access.csv', - 'security/rules.xml', + "data": [ + "views/tile.xml", + "views/templates.xml", + "security/ir.model.access.csv", + "security/rules.xml", ], - 'demo': [ - 'demo/res_groups.yml', - 'demo/tile_category.yml', - 'demo/tile_tile.yml', - ], - 'qweb': [ - 'static/src/xml/custom_xml.xml', + "demo": [ + "demo/res_groups.yml", + "demo/tile_category.yml", + "demo/tile_tile.yml", ], + "qweb": ["static/src/xml/custom_xml.xml"], } diff --git a/web_dashboard_tile/migrations/8.0.4.0/post-migration.py b/web_dashboard_tile/migrations/8.0.4.0/post-migration.py index fd7ddd773..674121edc 100644 --- a/web_dashboard_tile/migrations/8.0.4.0/post-migration.py +++ b/web_dashboard_tile/migrations/8.0.4.0/post-migration.py @@ -8,10 +8,12 @@ def migrate(cr, version): return # Update ir.rule - cr.execute(""" + cr.execute( + """ SELECT res_id FROM ir_model_data WHERE name = 'model_tile_rule' - AND module = 'web_dashboard_tile'""") + AND module = 'web_dashboard_tile'""" + ) rule_id = cr.fetchone()[0] new_domain = """[ "|", @@ -21,6 +23,9 @@ def migrate(cr, version): ("group_ids","=",False), ("group_ids","in",[g.id for g in user.groups_id]), ]""" - cr.execute(""" + cr.execute( + """ UPDATE ir_rule SET domain_force = '%(domain)s' - WHERE id = '%(id)s' """ % {'domain': new_domain, 'id': rule_id}) + WHERE id = '%(id)s' """ + % {"domain": new_domain, "id": rule_id} + ) diff --git a/web_dashboard_tile/models/tile_category.py b/web_dashboard_tile/models/tile_category.py index 5e4d9b2cd..e7b8d843c 100644 --- a/web_dashboard_tile/models/tile_category.py +++ b/web_dashboard_tile/models/tile_category.py @@ -6,12 +6,12 @@ from openerp import fields, models class TileCategory(models.Model): - _name = 'tile.category' - _description = 'Dashboard Tile Category' - _order = 'sequence asc' + _name = "tile.category" + _description = "Dashboard Tile Category" + _order = "sequence asc" name = fields.Char(required=True) sequence = fields.Integer( - help="Used to order the tile categories", - default=0) - fold = fields.Boolean('Folded by default') + help="Used to order the tile categories", default=0 + ) + fold = fields.Boolean("Folded by default") diff --git a/web_dashboard_tile/models/tile_tile.py b/web_dashboard_tile/models/tile_tile.py index 7a0c36954..eeacb9bc6 100644 --- a/web_dashboard_tile/models/tile_tile.py +++ b/web_dashboard_tile/models/tile_tile.py @@ -21,127 +21,150 @@ def median(vals): # in Python 3.4 even = (0 if len(vals) % 2 else 1) + 1 half = (len(vals) - 1) / 2 - return sum(sorted(vals)[half:half + even]) / float(even) + return sum(sorted(vals)[half : half + even]) / float(even) -FIELD_FUNCTIONS = OrderedDict([ - ('count', { - 'name': 'Count', - 'func': False, # its hardcoded in _compute_data - 'help': _('Number of records')}), - ('min', { - 'name': 'Minimum', - 'func': min, - 'help': _("Minimum value of '%s'")}), - ('max', { - 'name': 'Maximum', - 'func': max, - 'help': _("Maximum value of '%s'")}), - ('sum', { - 'name': 'Sum', - 'func': sum, - 'help': _("Total value of '%s'")}), - ('avg', { - 'name': 'Average', - 'func': lambda vals: sum(vals) / len(vals), - 'help': _("Minimum value of '%s'")}), - ('median', { - 'name': 'Median', - 'func': median, - 'help': _("Median value of '%s'")}), -]) +FIELD_FUNCTIONS = OrderedDict( + [ + ( + "count", + { + "name": "Count", + "func": False, # its hardcoded in _compute_data + "help": _("Number of records"), + }, + ), + ( + "min", + { + "name": "Minimum", + "func": min, + "help": _("Minimum value of '%s'"), + }, + ), + ( + "max", + { + "name": "Maximum", + "func": max, + "help": _("Maximum value of '%s'"), + }, + ), + ( + "sum", + {"name": "Sum", "func": sum, "help": _("Total value of '%s'")}, + ), + ( + "avg", + { + "name": "Average", + "func": lambda vals: sum(vals) / len(vals), + "help": _("Minimum value of '%s'"), + }, + ), + ( + "median", + { + "name": "Median", + "func": median, + "help": _("Median value of '%s'"), + }, + ), + ] +) FIELD_FUNCTION_SELECTION = [ - (k, FIELD_FUNCTIONS[k].get('name')) for k in FIELD_FUNCTIONS] + (k, FIELD_FUNCTIONS[k].get("name")) for k in FIELD_FUNCTIONS +] class TileTile(models.Model): - _name = 'tile.tile' - _description = 'Dashboard Tile' - _order = 'sequence, name' + _name = "tile.tile" + _description = "Dashboard Tile" + _order = "sequence, name" def _get_eval_context(self): def _context_today(): return fields.Date.from_string(fields.Date.context_today(self)) + context = self.env.context.copy() - context.update({ - 'time': time, - 'datetime': datetime, - 'relativedelta': relativedelta, - 'context_today': _context_today, - 'current_date': fields.Date.today(), - }) + context.update( + { + "time": time, + "datetime": datetime, + "relativedelta": relativedelta, + "context_today": _context_today, + "current_date": fields.Date.today(), + } + ) return context # Column Section name = fields.Char(required=True) sequence = fields.Integer(default=0, required=True) - category_id = fields.Many2one('tile.category', 'Category') - user_id = fields.Many2one('res.users', 'User') - background_color = fields.Char(default='#0E6C7E', oldname='color') - font_color = fields.Char(default='#FFFFFF') + category_id = fields.Many2one("tile.category", "Category") + user_id = fields.Many2one("res.users", "User") + background_color = fields.Char(default="#0E6C7E", oldname="color") + font_color = fields.Char(default="#FFFFFF") group_ids = fields.Many2many( - 'res.groups', - string='Groups', - help='If this field is set, only users of this group can view this ' - 'tile. Please note that it will only work for global tiles ' - '(that is, when User field is left empty)') + "res.groups", + string="Groups", + help="If this field is set, only users of this group can view this " + "tile. Please note that it will only work for global tiles " + "(that is, when User field is left empty)", + ) - model_id = fields.Many2one('ir.model', 'Model', required=True) - domain = fields.Text(default='[]') - action_id = fields.Many2one('ir.actions.act_window', 'Action') + model_id = fields.Many2one("ir.model", "Model", required=True) + domain = fields.Text(default="[]") + action_id = fields.Many2one("ir.actions.act_window", "Action") active = fields.Boolean( - compute='_compute_active', - search='_search_active', - readonly=True) + compute="_compute_active", search="_search_active", readonly=True + ) # Primary Value primary_function = fields.Selection( - FIELD_FUNCTION_SELECTION, - string='Function', - default='count') + FIELD_FUNCTION_SELECTION, string="Function", default="count" + ) primary_field_id = fields.Many2one( - 'ir.model.fields', - string='Field', + "ir.model.fields", + string="Field", domain="[('model_id', '=', model_id)," - " ('ttype', 'in', ['float', 'integer', 'monetary'])]") + " ('ttype', 'in', ['float', 'integer', 'monetary'])]", + ) primary_format = fields.Char( - string='Format', - help='Python Format String valid with str.format()\n' - 'ie: \'{:,} Kgs\' will output \'1,000 Kgs\' if value is 1000.') - primary_value = fields.Char( - string='Value', - compute='_compute_data') - primary_helper = fields.Char( - string='Helper', - compute='_compute_helper') + string="Format", + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) + primary_value = fields.Char(string="Value", compute="_compute_data") + primary_helper = fields.Char(string="Helper", compute="_compute_helper") # Secondary Value secondary_function = fields.Selection( - FIELD_FUNCTION_SELECTION, - string='Secondary Function') + FIELD_FUNCTION_SELECTION, string="Secondary Function" + ) secondary_field_id = fields.Many2one( - 'ir.model.fields', - string='Secondary Field', + "ir.model.fields", + string="Secondary Field", domain="[('model_id', '=', model_id)," - " ('ttype', 'in', ['float', 'integer', 'monetary'])]") + " ('ttype', 'in', ['float', 'integer', 'monetary'])]", + ) secondary_format = fields.Char( - string='Secondary Format', - help='Python Format String valid with str.format()\n' - 'ie: \'{:,} Kgs\' will output \'1,000 Kgs\' if value is 1000.') + string="Secondary Format", + help="Python Format String valid with str.format()\n" + "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.", + ) secondary_value = fields.Char( - string='Secondary Value', - compute='_compute_data') + string="Secondary Value", compute="_compute_data" + ) secondary_helper = fields.Char( - string='Secondary Helper', - compute='_compute_helper') + string="Secondary Helper", compute="_compute_helper" + ) - error = fields.Char( - string='Error Details', - compute='_compute_data') + error = fields.Char(string="Error Details", compute="_compute_data") @api.one def _compute_data(self): @@ -149,52 +172,62 @@ class TileTile(models.Model): return model = self.env[self.model_id.model] eval_context = self._get_eval_context() - domain = self.domain or '[]' + domain = self.domain or "[]" try: count = model.search_count(eval(domain, eval_context)) except Exception as e: - self.primary_value = self.secondary_value = 'ERR!' + self.primary_value = self.secondary_value = "ERR!" self.error = str(e) return - fields = [f.name for f in [ - self.primary_field_id, self.secondary_field_id] if f] - read_vals = fields and\ - model.search_read(eval(domain, eval_context), fields) or [] - for f in ['primary_', 'secondary_']: - f_function = f + 'function' - f_field_id = f + 'field_id' - f_format = f + 'format' - f_value = f + 'value' + fields = [ + f.name + for f in [self.primary_field_id, self.secondary_field_id] + if f + ] + read_vals = ( + fields + and model.search_read(eval(domain, eval_context), fields) + or [] + ) + for f in ["primary_", "secondary_"]: + f_function = f + "function" + f_field_id = f + "field_id" + f_format = f + "format" + f_value = f + "value" value = 0 if not self[f_function]: self[f_value] = False else: - if self[f_function] == 'count': + if self[f_function] == "count": value = count else: - func = FIELD_FUNCTIONS[self[f_function]]['func'] + func = FIELD_FUNCTIONS[self[f_function]]["func"] vals = [x[self[f_field_id].name] for x in read_vals] value = func(vals) try: - self[f_value] = (self[f_format] or '{:,}').format(value) + self[f_value] = (self[f_format] or "{:,}").format(value) except ValueError as e: - self[f_value] = 'F_ERR!' + self[f_value] = "F_ERR!" self.error = str(e) return @api.one - @api.onchange('primary_function', 'primary_field_id', - 'secondary_function', 'secondary_field_id') + @api.onchange( + "primary_function", + "primary_field_id", + "secondary_function", + "secondary_field_id", + ) def _compute_helper(self): - for f in ['primary_', 'secondary_']: - f_function = f + 'function' - f_field_id = f + 'field_id' - f_helper = f + 'helper' - self[f_helper] = '' + for f in ["primary_", "secondary_"]: + f_function = f + "function" + f_field_id = f + "field_id" + f_helper = f + "helper" + self[f_helper] = "" field_func = FIELD_FUNCTIONS.get(self[f_function], {}) - help = field_func.get('help', False) + help = field_func.get("help", False) if help: - if self[f_function] != 'count' and self[f_field_id]: + if self[f_function] != "count" and self[f_field_id]: desc = self[f_field_id].field_description self[f_helper] = help % desc else: @@ -202,77 +235,87 @@ class TileTile(models.Model): @api.one def _compute_active(self): - ima = self.env['ir.model.access'] + ima = self.env["ir.model.access"] for rec in self: - rec.active = ima.check(rec.model_id.model, 'read', False) + rec.active = ima.check(rec.model_id.model, "read", False) def _search_active(self, operator, value): cr = self.env.cr - if operator != '=': + if operator != "=": raise except_orm( - _('Unimplemented Feature. Search on Active field disabled.')) - ima = self.env['ir.model.access'] + _("Unimplemented Feature. Search on Active field disabled.") + ) + ima = self.env["ir.model.access"] ids = [] - cr.execute(""" + cr.execute( + """ SELECT tt.id, im.model FROM tile_tile tt INNER JOIN ir_model im - ON tt.model_id = im.id""") + ON tt.model_id = im.id""" + ) for result in cr.fetchall(): - if (ima.check(result[1], 'read', False) == value): + if ima.check(result[1], "read", False) == value: ids.append(result[0]) - return [('id', 'in', ids)] + return [("id", "in", ids)] # Constraints and onchanges @api.multi - @api.constrains('model_id', 'primary_field_id', 'secondary_field_id') + @api.constrains("model_id", "primary_field_id", "secondary_field_id") def _check_model_id_field_id(self): for rec in self: - if any([ - rec.primary_field_id and - rec.primary_field_id.model_id.id != rec.model_id.id, - rec.secondary_field_id and - rec.secondary_field_id.model_id.id != rec.model_id.id - ]): + if any( + [ + rec.primary_field_id + and rec.primary_field_id.model_id.id != rec.model_id.id, + rec.secondary_field_id + and rec.secondary_field_id.model_id.id != rec.model_id.id, + ] + ): raise ValidationError( - _("Please select a field from the selected model.")) + _("Please select a field from the selected model.") + ) - @api.onchange('model_id') + @api.onchange("model_id") def _onchange_model_id(self): self.primary_field_id = False self.secondary_field_id = False - @api.onchange('primary_function', 'secondary_function') + @api.onchange("primary_function", "secondary_function") def _onchange_function(self): - if self.primary_function in [False, 'count']: + if self.primary_function in [False, "count"]: self.primary_field_id = False - if self.secondary_function in [False, 'count']: + if self.secondary_function in [False, "count"]: self.secondary_field_id = False # Action methods @api.multi def open_link(self): res = { - 'name': self.name, - 'view_type': 'form', - 'view_mode': 'tree', - 'view_id': [False], - 'res_model': self.model_id.model, - 'type': 'ir.actions.act_window', - 'context': dict(self.env.context, group_by=False), - 'nodestroy': True, - 'target': 'current', - 'domain': self.domain, + "name": self.name, + "view_type": "form", + "view_mode": "tree", + "view_id": [False], + "res_model": self.model_id.model, + "type": "ir.actions.act_window", + "context": dict(self.env.context, group_by=False), + "nodestroy": True, + "target": "current", + "domain": self.domain, } if self.action_id: - res.update(self.action_id.read( - ['view_type', 'view_mode', 'type'])[0]) + res.update( + self.action_id.read(["view_type", "view_mode", "type"])[0] + ) return res @api.model def add(self, vals): - if 'model_id' in vals and not vals['model_id'].isdigit(): + if "model_id" in vals and not vals["model_id"].isdigit(): # need to replace model_name with its id - vals['model_id'] = self.env['ir.model'].search( - [('model', '=', vals['model_id'])]).id + vals["model_id"] = ( + self.env["ir.model"] + .search([("model", "=", vals["model_id"])]) + .id + ) self.create(vals) diff --git a/web_dashboard_tile/tests/test_tile.py b/web_dashboard_tile/tests/test_tile.py index 90604ab9a..ec6b309b1 100644 --- a/web_dashboard_tile/tests/test_tile.py +++ b/web_dashboard_tile/tests/test_tile.py @@ -7,46 +7,54 @@ from openerp.tests.common import TransactionCase class TestTile(TransactionCase): def test_tile(self): - tile_obj = self.env['tile.tile'] - model_id = self.env['ir.model'].search([ - ('model', '=', 'tile.tile')]) - field_id = self.env['ir.model.fields'].search([ - ('model_id', '=', model_id.id), - ('name', '=', 'sequence')]) - self.tile1 = tile_obj.create({ - 'name': 'Count / Sum', - 'sequence': 1, - 'model_id': model_id.id, - 'domain': "[('model_id', '=', %d)]" % model_id.id, - 'secondary_function': 'sum', - 'secondary_field_id': field_id.id}) - self.tile2 = tile_obj.create({ - 'name': 'Min / Max', - 'sequence': 2, - 'model_id': model_id.id, - 'domain': "[('model_id', '=', %d)]" % model_id.id, - 'primary_function': 'min', - 'primary_field_id': field_id.id, - 'secondary_function': 'max', - 'secondary_field_id': field_id.id}) - self.tile3 = tile_obj.create({ - 'name': 'Avg / Median', - 'sequence': 3, - 'model_id': model_id.id, - 'domain': "[('model_id', '=', %d)]" % model_id.id, - 'primary_function': 'avg', - 'primary_field_id': field_id.id, - 'secondary_function': 'median', - 'secondary_field_id': field_id.id}) + tile_obj = self.env["tile.tile"] + model_id = self.env["ir.model"].search([("model", "=", "tile.tile")]) + field_id = self.env["ir.model.fields"].search( + [("model_id", "=", model_id.id), ("name", "=", "sequence")] + ) + self.tile1 = tile_obj.create( + { + "name": "Count / Sum", + "sequence": 1, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "secondary_function": "sum", + "secondary_field_id": field_id.id, + } + ) + self.tile2 = tile_obj.create( + { + "name": "Min / Max", + "sequence": 2, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "primary_function": "min", + "primary_field_id": field_id.id, + "secondary_function": "max", + "secondary_field_id": field_id.id, + } + ) + self.tile3 = tile_obj.create( + { + "name": "Avg / Median", + "sequence": 3, + "model_id": model_id.id, + "domain": "[('model_id', '=', %d)]" % model_id.id, + "primary_function": "avg", + "primary_field_id": field_id.id, + "secondary_function": "median", + "secondary_field_id": field_id.id, + } + ) # count - self.assertEqual(self.tile1.primary_value, '3') + self.assertEqual(self.tile1.primary_value, "3") # sum - self.assertEqual(self.tile1.secondary_value, '6') + self.assertEqual(self.tile1.secondary_value, "6") # min - self.assertEqual(self.tile2.primary_value, '1') + self.assertEqual(self.tile2.primary_value, "1") # max - self.assertEqual(self.tile2.secondary_value, '3') + self.assertEqual(self.tile2.secondary_value, "3") # average - self.assertEqual(self.tile3.primary_value, '2') + self.assertEqual(self.tile3.primary_value, "2") # median - self.assertEqual(self.tile3.secondary_value, '2.0') + self.assertEqual(self.tile3.secondary_value, "2.0")