[REF] web_dashboard_tile: Black python code

pull/1428/head
Sylvain LE GAL 2019-11-04 17:52:42 +01:00
parent fe12fcc9d1
commit 07124f3b7f
5 changed files with 265 additions and 216 deletions

View File

@ -6,34 +6,27 @@
"name": "Dashboard Tile", "name": "Dashboard Tile",
"summary": "Add Tiles to Dashboard", "summary": "Add Tiles to Dashboard",
"version": "9.0.1.1.0", "version": "9.0.1.1.0",
"depends": [ "depends": ["web", "board", "mail", "web_widget_color"],
'web', "author": "initOS GmbH & Co. KG, "
'board', "GRAP, "
'mail', "Odoo Community Association (OCA)",
'web_widget_color',
],
'author': 'initOS GmbH & Co. KG, '
'GRAP, '
'Odoo Community Association (OCA)',
"category": "web", "category": "web",
'license': 'AGPL-3', "license": "AGPL-3",
'contributors': [ "contributors": [
'initOS GmbH & Co. KG', "initOS GmbH & Co. KG",
'GRAP', "GRAP",
'Iván Todorovich <ivan.todorovich@gmail.com>' "Iván Todorovich <ivan.todorovich@gmail.com>",
], ],
'data': [ "data": [
'views/tile.xml', "views/tile.xml",
'views/templates.xml', "views/templates.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
'security/rules.xml', "security/rules.xml",
], ],
'demo': [ "demo": [
'demo/res_groups.yml', "demo/res_groups.yml",
'demo/tile_category.yml', "demo/tile_category.yml",
'demo/tile_tile.yml', "demo/tile_tile.yml",
],
'qweb': [
'static/src/xml/custom_xml.xml',
], ],
"qweb": ["static/src/xml/custom_xml.xml"],
} }

View File

@ -8,10 +8,12 @@ def migrate(cr, version):
return return
# Update ir.rule # Update ir.rule
cr.execute(""" cr.execute(
"""
SELECT res_id FROM ir_model_data SELECT res_id FROM ir_model_data
WHERE name = 'model_tile_rule' WHERE name = 'model_tile_rule'
AND module = 'web_dashboard_tile'""") AND module = 'web_dashboard_tile'"""
)
rule_id = cr.fetchone()[0] rule_id = cr.fetchone()[0]
new_domain = """[ new_domain = """[
"|", "|",
@ -21,6 +23,9 @@ def migrate(cr, version):
("group_ids","=",False), ("group_ids","=",False),
("group_ids","in",[g.id for g in user.groups_id]), ("group_ids","in",[g.id for g in user.groups_id]),
]""" ]"""
cr.execute(""" cr.execute(
"""
UPDATE ir_rule SET domain_force = '%(domain)s' 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}
)

View File

@ -6,12 +6,12 @@ from openerp import fields, models
class TileCategory(models.Model): class TileCategory(models.Model):
_name = 'tile.category' _name = "tile.category"
_description = 'Dashboard Tile Category' _description = "Dashboard Tile Category"
_order = 'sequence asc' _order = "sequence asc"
name = fields.Char(required=True) name = fields.Char(required=True)
sequence = fields.Integer( sequence = fields.Integer(
help="Used to order the tile categories", help="Used to order the tile categories", default=0
default=0) )
fold = fields.Boolean('Folded by default') fold = fields.Boolean("Folded by default")

View File

@ -24,124 +24,147 @@ def median(vals):
return sum(sorted(vals)[half : half + even]) / float(even) return sum(sorted(vals)[half : half + even]) / float(even)
FIELD_FUNCTIONS = OrderedDict([ FIELD_FUNCTIONS = OrderedDict(
('count', { [
'name': 'Count', (
'func': False, # its hardcoded in _compute_data "count",
'help': _('Number of records')}), {
('min', { "name": "Count",
'name': 'Minimum', "func": False, # its hardcoded in _compute_data
'func': min, "help": _("Number of records"),
'help': _("Minimum value of '%s'")}), },
('max', { ),
'name': 'Maximum', (
'func': max, "min",
'help': _("Maximum value of '%s'")}), {
('sum', { "name": "Minimum",
'name': 'Sum', "func": min,
'func': sum, "help": _("Minimum value of '%s'"),
'help': _("Total value of '%s'")}), },
('avg', { ),
'name': 'Average', (
'func': lambda vals: sum(vals) / len(vals), "max",
'help': _("Minimum value of '%s'")}), {
('median', { "name": "Maximum",
'name': 'Median', "func": max,
'func': median, "help": _("Maximum value of '%s'"),
'help': _("Median 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 = [ 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): class TileTile(models.Model):
_name = 'tile.tile' _name = "tile.tile"
_description = 'Dashboard Tile' _description = "Dashboard Tile"
_order = 'sequence, name' _order = "sequence, name"
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))
context = self.env.context.copy() context = self.env.context.copy()
context.update({ context.update(
'time': time, {
'datetime': datetime, "time": time,
'relativedelta': relativedelta, "datetime": datetime,
'context_today': _context_today, "relativedelta": relativedelta,
'current_date': fields.Date.today(), "context_today": _context_today,
}) "current_date": fields.Date.today(),
}
)
return context return context
# Column Section # Column Section
name = fields.Char(required=True) name = fields.Char(required=True)
sequence = fields.Integer(default=0, required=True) sequence = fields.Integer(default=0, required=True)
category_id = fields.Many2one('tile.category', 'Category') category_id = fields.Many2one("tile.category", "Category")
user_id = fields.Many2one('res.users', 'User') user_id = fields.Many2one("res.users", "User")
background_color = fields.Char(default='#0E6C7E', oldname='color') background_color = fields.Char(default="#0E6C7E", oldname="color")
font_color = fields.Char(default='#FFFFFF') font_color = fields.Char(default="#FFFFFF")
group_ids = fields.Many2many( group_ids = fields.Many2many(
'res.groups', "res.groups",
string='Groups', string="Groups",
help='If this field is set, only users of this group can view this ' 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 ' "tile. Please note that it will only work for global tiles "
'(that is, when User field is left empty)') "(that is, when User field is left empty)",
)
model_id = fields.Many2one('ir.model', 'Model', required=True) model_id = fields.Many2one("ir.model", "Model", required=True)
domain = fields.Text(default='[]') domain = fields.Text(default="[]")
action_id = fields.Many2one('ir.actions.act_window', 'Action') action_id = fields.Many2one("ir.actions.act_window", "Action")
active = fields.Boolean( active = fields.Boolean(
compute='_compute_active', compute="_compute_active", search="_search_active", readonly=True
search='_search_active', )
readonly=True)
# Primary Value # Primary Value
primary_function = fields.Selection( primary_function = fields.Selection(
FIELD_FUNCTION_SELECTION, FIELD_FUNCTION_SELECTION, string="Function", default="count"
string='Function', )
default='count')
primary_field_id = fields.Many2one( primary_field_id = fields.Many2one(
'ir.model.fields', "ir.model.fields",
string='Field', string="Field",
domain="[('model_id', '=', model_id)," domain="[('model_id', '=', model_id),"
" ('ttype', 'in', ['float', 'integer', 'monetary'])]") " ('ttype', 'in', ['float', 'integer', 'monetary'])]",
)
primary_format = fields.Char( primary_format = fields.Char(
string='Format', string="Format",
help='Python Format String valid with str.format()\n' help="Python Format String valid with str.format()\n"
'ie: \'{:,} Kgs\' will output \'1,000 Kgs\' if value is 1000.') "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.",
primary_value = fields.Char( )
string='Value', primary_value = fields.Char(string="Value", compute="_compute_data")
compute='_compute_data') primary_helper = fields.Char(string="Helper", compute="_compute_helper")
primary_helper = fields.Char(
string='Helper',
compute='_compute_helper')
# Secondary Value # Secondary Value
secondary_function = fields.Selection( secondary_function = fields.Selection(
FIELD_FUNCTION_SELECTION, FIELD_FUNCTION_SELECTION, string="Secondary Function"
string='Secondary Function') )
secondary_field_id = fields.Many2one( secondary_field_id = fields.Many2one(
'ir.model.fields', "ir.model.fields",
string='Secondary Field', string="Secondary Field",
domain="[('model_id', '=', model_id)," domain="[('model_id', '=', model_id),"
" ('ttype', 'in', ['float', 'integer', 'monetary'])]") " ('ttype', 'in', ['float', 'integer', 'monetary'])]",
)
secondary_format = fields.Char( secondary_format = fields.Char(
string='Secondary Format', string="Secondary Format",
help='Python Format String valid with str.format()\n' help="Python Format String valid with str.format()\n"
'ie: \'{:,} Kgs\' will output \'1,000 Kgs\' if value is 1000.') "ie: '{:,} Kgs' will output '1,000 Kgs' if value is 1000.",
)
secondary_value = fields.Char( secondary_value = fields.Char(
string='Secondary Value', string="Secondary Value", compute="_compute_data"
compute='_compute_data') )
secondary_helper = fields.Char( secondary_helper = fields.Char(
string='Secondary Helper', string="Secondary Helper", compute="_compute_helper"
compute='_compute_helper') )
error = fields.Char( error = fields.Char(string="Error Details", compute="_compute_data")
string='Error Details',
compute='_compute_data')
@api.one @api.one
def _compute_data(self): def _compute_data(self):
@ -149,52 +172,62 @@ class TileTile(models.Model):
return return
model = self.env[self.model_id.model] model = self.env[self.model_id.model]
eval_context = self._get_eval_context() eval_context = self._get_eval_context()
domain = self.domain or '[]' domain = self.domain or "[]"
try: try:
count = model.search_count(eval(domain, eval_context)) count = model.search_count(eval(domain, eval_context))
except Exception as e: except Exception as e:
self.primary_value = self.secondary_value = 'ERR!' self.primary_value = self.secondary_value = "ERR!"
self.error = str(e) self.error = str(e)
return return
fields = [f.name for f in [ fields = [
self.primary_field_id, self.secondary_field_id] if f] f.name
read_vals = fields and\ for f in [self.primary_field_id, self.secondary_field_id]
model.search_read(eval(domain, eval_context), fields) or [] if f
for f in ['primary_', 'secondary_']: ]
f_function = f + 'function' read_vals = (
f_field_id = f + 'field_id' fields
f_format = f + 'format' and model.search_read(eval(domain, eval_context), fields)
f_value = f + 'value' 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 value = 0
if not self[f_function]: if not self[f_function]:
self[f_value] = False self[f_value] = False
else: else:
if self[f_function] == 'count': if self[f_function] == "count":
value = count value = count
else: 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] vals = [x[self[f_field_id].name] for x in read_vals]
value = func(vals) value = func(vals)
try: try:
self[f_value] = (self[f_format] or '{:,}').format(value) self[f_value] = (self[f_format] or "{:,}").format(value)
except ValueError as e: except ValueError as e:
self[f_value] = 'F_ERR!' self[f_value] = "F_ERR!"
self.error = str(e) self.error = str(e)
return return
@api.one @api.one
@api.onchange('primary_function', 'primary_field_id', @api.onchange(
'secondary_function', 'secondary_field_id') "primary_function",
"primary_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_function = f + "function"
f_field_id = f + 'field_id' f_field_id = f + "field_id"
f_helper = f + 'helper' 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)
if help: 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 desc = self[f_field_id].field_description
self[f_helper] = help % desc self[f_helper] = help % desc
else: else:
@ -202,77 +235,87 @@ class TileTile(models.Model):
@api.one @api.one
def _compute_active(self): def _compute_active(self):
ima = self.env['ir.model.access'] ima = self.env["ir.model.access"]
for rec in self: 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): 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.')) _("Unimplemented Feature. Search on Active field disabled.")
ima = self.env['ir.model.access'] )
ima = self.env["ir.model.access"]
ids = [] ids = []
cr.execute(""" cr.execute(
"""
SELECT tt.id, im.model SELECT tt.id, im.model
FROM tile_tile tt FROM tile_tile tt
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.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)]
# Constraints and onchanges # Constraints and onchanges
@api.multi @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): def _check_model_id_field_id(self):
for rec in self: for rec in self:
if any([ if any(
rec.primary_field_id and [
rec.primary_field_id.model_id.id != rec.model_id.id, rec.primary_field_id
rec.secondary_field_id and and rec.primary_field_id.model_id.id != rec.model_id.id,
rec.secondary_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( 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): def _onchange_model_id(self):
self.primary_field_id = False self.primary_field_id = False
self.secondary_field_id = False self.secondary_field_id = False
@api.onchange('primary_function', 'secondary_function') @api.onchange("primary_function", "secondary_function")
def _onchange_function(self): def _onchange_function(self):
if self.primary_function in [False, 'count']: if self.primary_function in [False, "count"]:
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
# Action methods # Action methods
@api.multi @api.multi
def open_link(self): def open_link(self):
res = { res = {
'name': self.name, "name": self.name,
'view_type': 'form', "view_type": "form",
'view_mode': 'tree', "view_mode": "tree",
'view_id': [False], "view_id": [False],
'res_model': self.model_id.model, "res_model": self.model_id.model,
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'context': dict(self.env.context, group_by=False), "context": dict(self.env.context, group_by=False),
'nodestroy': True, "nodestroy": True,
'target': 'current', "target": "current",
'domain': self.domain, "domain": self.domain,
} }
if self.action_id: if self.action_id:
res.update(self.action_id.read( res.update(
['view_type', 'view_mode', 'type'])[0]) self.action_id.read(["view_type", "view_mode", "type"])[0]
)
return res return res
@api.model @api.model
def add(self, vals): 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 # need to replace model_name with its id
vals['model_id'] = self.env['ir.model'].search( vals["model_id"] = (
[('model', '=', vals['model_id'])]).id self.env["ir.model"]
.search([("model", "=", vals["model_id"])])
.id
)
self.create(vals) self.create(vals)

View File

@ -7,46 +7,54 @@ from openerp.tests.common import TransactionCase
class TestTile(TransactionCase): class TestTile(TransactionCase):
def test_tile(self): def test_tile(self):
tile_obj = self.env['tile.tile'] tile_obj = self.env["tile.tile"]
model_id = self.env['ir.model'].search([ model_id = self.env["ir.model"].search([("model", "=", "tile.tile")])
('model', '=', 'tile.tile')]) field_id = self.env["ir.model.fields"].search(
field_id = self.env['ir.model.fields'].search([ [("model_id", "=", model_id.id), ("name", "=", "sequence")]
('model_id', '=', model_id.id), )
('name', '=', 'sequence')]) self.tile1 = tile_obj.create(
self.tile1 = tile_obj.create({ {
'name': 'Count / Sum', "name": "Count / Sum",
'sequence': 1, "sequence": 1,
'model_id': model_id.id, "model_id": model_id.id,
'domain': "[('model_id', '=', %d)]" % model_id.id, "domain": "[('model_id', '=', %d)]" % model_id.id,
'secondary_function': 'sum', "secondary_function": "sum",
'secondary_field_id': field_id.id}) "secondary_field_id": field_id.id,
self.tile2 = tile_obj.create({ }
'name': 'Min / Max', )
'sequence': 2, self.tile2 = tile_obj.create(
'model_id': model_id.id, {
'domain': "[('model_id', '=', %d)]" % model_id.id, "name": "Min / Max",
'primary_function': 'min', "sequence": 2,
'primary_field_id': field_id.id, "model_id": model_id.id,
'secondary_function': 'max', "domain": "[('model_id', '=', %d)]" % model_id.id,
'secondary_field_id': field_id.id}) "primary_function": "min",
self.tile3 = tile_obj.create({ "primary_field_id": field_id.id,
'name': 'Avg / Median', "secondary_function": "max",
'sequence': 3, "secondary_field_id": field_id.id,
'model_id': model_id.id, }
'domain': "[('model_id', '=', %d)]" % model_id.id, )
'primary_function': 'avg', self.tile3 = tile_obj.create(
'primary_field_id': field_id.id, {
'secondary_function': 'median', "name": "Avg / Median",
'secondary_field_id': field_id.id}) "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 # count
self.assertEqual(self.tile1.primary_value, '3') self.assertEqual(self.tile1.primary_value, "3")
# sum # sum
self.assertEqual(self.tile1.secondary_value, '6') self.assertEqual(self.tile1.secondary_value, "6")
# min # min
self.assertEqual(self.tile2.primary_value, '1') self.assertEqual(self.tile2.primary_value, "1")
# max # max
self.assertEqual(self.tile2.secondary_value, '3') self.assertEqual(self.tile2.secondary_value, "3")
# average # average
self.assertEqual(self.tile3.primary_value, '2') self.assertEqual(self.tile3.primary_value, "2")
# median # median
self.assertEqual(self.tile3.secondary_value, '2.0') self.assertEqual(self.tile3.secondary_value, "2.0")