commit
e0180490a8
|
@ -16,6 +16,10 @@ class KpiDashboard(models.Model):
|
|||
"kpi.dashboard.item", inverse_name="dashboard_id", copy=True,
|
||||
)
|
||||
number_of_columns = fields.Integer(default=5, required=True)
|
||||
compute_on_fly_refresh = fields.Integer(
|
||||
default=0,
|
||||
help="Seconds to refresh on fly elements"
|
||||
)
|
||||
width = fields.Integer(compute="_compute_width")
|
||||
margin_y = fields.Integer(default=10, required=True)
|
||||
margin_x = fields.Integer(default=10, required=True)
|
||||
|
@ -43,6 +47,15 @@ class KpiDashboard(models.Model):
|
|||
+ rec.widget_dimension_x * rec.number_of_columns
|
||||
)
|
||||
|
||||
def read_dashboard_on_fly(self):
|
||||
self.ensure_one()
|
||||
result = []
|
||||
for item in self.item_ids:
|
||||
if not item.kpi_id.compute_on_fly:
|
||||
continue
|
||||
result.append(item._read_dashboard())
|
||||
return result
|
||||
|
||||
def read_dashboard(self):
|
||||
self.ensure_one()
|
||||
result = {
|
||||
|
@ -52,6 +65,7 @@ class KpiDashboard(models.Model):
|
|||
"max_cols": self.number_of_columns,
|
||||
"margin_x": self.margin_x,
|
||||
"margin_y": self.margin_y,
|
||||
"compute_on_fly_refresh": self.compute_on_fly_refresh,
|
||||
"widget_dimension_x": self.widget_dimension_x,
|
||||
"widget_dimension_y": self.widget_dimension_y,
|
||||
"background_color": self.background_color,
|
||||
|
@ -167,10 +181,19 @@ class KpiDashboardItem(models.Model):
|
|||
"kpi_id": self.kpi_id.id,
|
||||
"suffix": self.kpi_id.suffix or "",
|
||||
"prefix": self.kpi_id.prefix or "",
|
||||
"value": self.kpi_id.value,
|
||||
"value_last_update": self.kpi_id.value_last_update,
|
||||
"compute_on_fly": self.kpi_id.compute_on_fly,
|
||||
}
|
||||
)
|
||||
if self.kpi_id.compute_on_fly:
|
||||
vals.update({
|
||||
"value": self.kpi_id._compute_value(),
|
||||
"value_last_update": fields.Datetime.now(),
|
||||
})
|
||||
else:
|
||||
vals.update({
|
||||
"value": self.kpi_id.value,
|
||||
"value_last_update": self.kpi_id.value_last_update,
|
||||
})
|
||||
if self.kpi_id.action_ids:
|
||||
vals["actions"] = self.kpi_id.action_ids.read_dashboard()
|
||||
else:
|
||||
|
|
|
@ -5,7 +5,11 @@ from odoo import api, fields, models, _
|
|||
from odoo.exceptions import ValidationError
|
||||
import ast
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
from odoo.addons.base.models.ir_cron import _intervalTypes
|
||||
from odoo.tools.float_utils import float_compare
|
||||
import re
|
||||
import json
|
||||
import datetime
|
||||
|
||||
|
||||
class KpiKpi(models.Model):
|
||||
|
@ -38,6 +42,26 @@ class KpiKpi(models.Model):
|
|||
help="Actions that can be opened from the KPI"
|
||||
)
|
||||
code = fields.Text("Code")
|
||||
store_history = fields.Boolean()
|
||||
store_history_interval = fields.Selection(
|
||||
selection=lambda self:
|
||||
self.env['ir.cron']._fields['interval_type'].selection,
|
||||
)
|
||||
store_history_interval_number = fields.Integer()
|
||||
compute_on_fly = fields.Boolean()
|
||||
history_ids = fields.One2many("kpi.kpi.history", inverse_name="kpi_id")
|
||||
computed_value = fields.Serialized(compute='_compute_computed_value')
|
||||
computed_date = fields.Datetime(compute='_compute_computed_value')
|
||||
|
||||
@api.depends('value', 'value_last_update', 'compute_on_fly')
|
||||
def _compute_computed_value(self):
|
||||
for record in self:
|
||||
if record.compute_on_fly:
|
||||
record.computed_value = record._compute_value()
|
||||
record.computed_date = fields.Datetime.now()
|
||||
else:
|
||||
record.computed_value = record.value
|
||||
record.computed_date = record.value_last_update
|
||||
|
||||
def _cron_vals(self):
|
||||
return {
|
||||
|
@ -55,14 +79,32 @@ class KpiKpi(models.Model):
|
|||
record._compute()
|
||||
return True
|
||||
|
||||
def _generate_history_vals(self, value):
|
||||
return {
|
||||
"kpi_id": self.id,
|
||||
"value": value,
|
||||
"widget": self.widget,
|
||||
}
|
||||
|
||||
def _compute_value(self):
|
||||
return getattr(self, "_compute_value_%s" % self.computation_method)()
|
||||
|
||||
def _compute(self):
|
||||
self.write(
|
||||
{
|
||||
"value": getattr(
|
||||
self, "_compute_value_%s" % self.computation_method
|
||||
)()
|
||||
}
|
||||
)
|
||||
value = self._compute_value()
|
||||
self.write({"value": value})
|
||||
if self.store_history:
|
||||
last = self.env['kpi.kpi.history'].search([
|
||||
('kpi_id', '=', self.id)
|
||||
], limit=1)
|
||||
if (
|
||||
not last or
|
||||
not self.store_history_interval or
|
||||
last.create_date + _intervalTypes[self.store_history_interval](
|
||||
self.store_history_interval_number) < fields.Datetime.now()
|
||||
):
|
||||
self.env["kpi.kpi.history"].create(
|
||||
self._generate_history_vals(value)
|
||||
)
|
||||
notifications = []
|
||||
for dashboard_item in self.dashboard_item_ids:
|
||||
channel = "kpi_dashboard_%s" % dashboard_item.dashboard_id.id
|
||||
|
@ -92,6 +134,8 @@ class KpiKpi(models.Model):
|
|||
return {
|
||||
"self": self,
|
||||
"model": self.browse(),
|
||||
"datetime": datetime,
|
||||
"float_compare": float_compare,
|
||||
}
|
||||
|
||||
def _forbidden_code(self):
|
||||
|
@ -115,6 +159,20 @@ class KpiKpi(models.Model):
|
|||
self.env.cr.execute("rollback to %s" % savepoint)
|
||||
return results.get("result", {})
|
||||
|
||||
def show_value(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('kpi_dashboard.kpi_kpi_act_window')
|
||||
result = action.read()[0]
|
||||
result.update({
|
||||
'res_id': self.id,
|
||||
'target': 'new',
|
||||
'view_mode': 'form',
|
||||
'views': [(self.env.ref(
|
||||
'kpi_dashboard.kpi_kpi_widget_form_view'
|
||||
).id, 'form')],
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
class KpiKpiAction(models.Model):
|
||||
_name = 'kpi.kpi.action'
|
||||
|
@ -129,13 +187,49 @@ class KpiKpiAction(models.Model):
|
|||
('ir.actions.client', 'ir.actions.client')],
|
||||
required=True,
|
||||
)
|
||||
context = fields.Char()
|
||||
|
||||
def read_dashboard(self):
|
||||
result = []
|
||||
result = {}
|
||||
for r in self:
|
||||
result.append({
|
||||
result[r.id] = {
|
||||
'id': r.action.id,
|
||||
'type': r.action._name,
|
||||
'name': r.action.name
|
||||
})
|
||||
'name': r.action.name,
|
||||
'context': safe_eval(r.context or '{}')
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class KpiKpiHistory(models.Model):
|
||||
_name = 'kpi.kpi.history'
|
||||
_description = 'KPI history'
|
||||
_order = 'create_date DESC'
|
||||
|
||||
kpi_id = fields.Many2one(
|
||||
'kpi.kpi', required=True, ondelete='cascade', readonly=True
|
||||
)
|
||||
value = fields.Serialized(readonly=True)
|
||||
raw_value = fields.Char(compute='_compute_raw_value')
|
||||
name = fields.Char(related='kpi_id.name')
|
||||
widget = fields.Selection(
|
||||
selection=lambda self:
|
||||
self.env['kpi.kpi']._fields['widget'].selection,
|
||||
required=True)
|
||||
|
||||
@api.depends('value')
|
||||
def _compute_raw_value(self):
|
||||
for record in self:
|
||||
record.raw_value = json.dumps(record.value)
|
||||
|
||||
def show_form(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('kpi_dashboard.kpi_kpi_history_act_window')
|
||||
result = action.read()[0]
|
||||
result.update({
|
||||
'res_id': self.id,
|
||||
'target': 'new',
|
||||
'view_mode': 'form',
|
||||
'views': [(self.env.context.get('form_id'), 'form')],
|
||||
})
|
||||
return result
|
||||
|
|
|
@ -3,7 +3,9 @@ access_kpi_dashboard,access_kpi_dashboard,model_kpi_dashboard,base.group_user,1,
|
|||
access_kpi_dashboard_kpi,access_kpi_dashboard_kpi,model_kpi_dashboard_item,base.group_user,1,0,0,0
|
||||
access_kpi_kpi,access_kpi_kpi,model_kpi_kpi,base.group_user,1,0,0,0
|
||||
access_kpi_kpi_action,access_kpi_kpi_action,model_kpi_kpi_action,base.group_user,1,0,0,0
|
||||
access_kpi_kpi_history,access_kpi_kpi_history,model_kpi_kpi_history,base.group_user,1,0,0,0
|
||||
manage_kpi_dashboard,manage_kpi_dashboard,model_kpi_dashboard,group_kpi_dashboard_manager,1,1,1,1
|
||||
manage_kpi_dashboard_kpi,manage_kpi_dashboard_kpi,model_kpi_dashboard_item,group_kpi_dashboard_manager,1,1,1,1
|
||||
manage_kpi_kpi,manage_kpi_kpi,model_kpi_kpi,group_kpi_dashboard_manager,1,1,1,1
|
||||
manage_kpi_kpi_action,manage_kpi_kpi_action,model_kpi_kpi_action,group_kpi_dashboard_manager,1,1,1,1
|
||||
manage_kpi_kpi_history,manage_kpi_kpi_history,model_kpi_kpi_history,group_kpi_dashboard_manager,1,1,1,1
|
||||
|
|
|
|
@ -10,7 +10,29 @@ odoo.define('kpi_dashboard.DashboardController', function (require) {
|
|||
var DashboardController = BasicController.extend({
|
||||
custom_events: _.extend({}, BasicController.prototype.custom_events, {
|
||||
addDashboard: '_addDashboard',
|
||||
refresh_on_fly: '_refreshOnFly',
|
||||
}),
|
||||
_refreshOnFly: function (event) {
|
||||
var self = this;
|
||||
this._rpc({
|
||||
model: this.modelName,
|
||||
method: 'read_dashboard_on_fly',
|
||||
args: [[this.renderer.state.res_id]],
|
||||
context: _.extend(
|
||||
{},
|
||||
this.model.get(this.handle, {raw: true}).getContext(),
|
||||
{bin_size: true}
|
||||
),
|
||||
}).then(function (data) {
|
||||
_.each(data, function (item) {
|
||||
// We will follow the same logic used on Bus Notifications
|
||||
self.renderer._onNotification([[
|
||||
"kpi_dashboard_" + self.renderer.state.res_id,
|
||||
item
|
||||
]])
|
||||
});
|
||||
});
|
||||
},
|
||||
renderPager: function ($node, options) {
|
||||
options = _.extend({}, options, {
|
||||
validate: this.canBeDiscarded.bind(this),
|
||||
|
|
|
@ -53,8 +53,21 @@ odoo.define('kpi_dashboard.DashboardRenderer', function (require) {
|
|||
'bus_service', 'onNotification',
|
||||
this, this._onNotification
|
||||
);
|
||||
if (this.state.specialData.compute_on_fly_refresh > 0) {
|
||||
// Setting the refresh interval
|
||||
this.on_fly_interval = setInterval(function () {
|
||||
self.trigger_up('refresh_on_fly');
|
||||
}, this.state.specialData.compute_on_fly_refresh *1000);
|
||||
};
|
||||
return $.when();
|
||||
},
|
||||
on_detach_callback: function () {
|
||||
// We want to clear the refresh interval once we exit the view
|
||||
if (this.on_fly_interval) {
|
||||
clearInterval(this.on_fly_interval)
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
_onNotification: function (notifications) {
|
||||
var self = this;
|
||||
_.each(notifications, function (notification) {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
odoo.define('kpi_dashboard.KpiFieldWidget', function(require) {
|
||||
"use strict";
|
||||
|
||||
var basic_fields = require('web.basic_fields');
|
||||
var field_registry = require('web.field_registry');
|
||||
var core = require('web.core');
|
||||
var qweb = core.qweb;
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
|
||||
var KpiFieldWidget = basic_fields.FieldChar.extend({
|
||||
jsLibs: [
|
||||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js',
|
||||
],
|
||||
cssLibs: [
|
||||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css',
|
||||
],
|
||||
className: 'o_dashboard_view',
|
||||
_renderReadonly: function () {
|
||||
this.$el.html($(qweb.render('dashboard_kpi.dashboard')));
|
||||
var marginx = 0;
|
||||
var marginy = 0;
|
||||
var widgetx = 400;
|
||||
var widgety = 400;
|
||||
this.$el.find('.gridster').css('width', widgety);
|
||||
this.$grid = this.$el.find('.gridster ul');
|
||||
var widgetVals = {
|
||||
value: this.value,
|
||||
col: 1,
|
||||
row: 1,
|
||||
sizex: 1,
|
||||
sizey: 1,
|
||||
name: this.recordData[this.nodeOptions.name],
|
||||
value_last_update: this.recordData[this.nodeOptions.date]
|
||||
}
|
||||
var Widget = registry.getAny([
|
||||
this.recordData[this.nodeOptions.widget], 'abstract',
|
||||
]);
|
||||
this.state = {
|
||||
specialData: {
|
||||
margin_x: marginx,
|
||||
margin_y: marginy,
|
||||
widget_dimension_x: widgetx,
|
||||
widget_dimension_y: widgety,
|
||||
}
|
||||
}
|
||||
var widget = new Widget(this, widgetVals);
|
||||
var element = $(qweb.render(
|
||||
'kpi_dashboard.kpi', {widget: widgetVals}));
|
||||
element.css('background-color', 'white');
|
||||
element.css('color', 'black');
|
||||
this.$grid.append(element);
|
||||
widget.appendTo(element)
|
||||
this.$grid.gridster({
|
||||
widget_margins: [
|
||||
marginx,
|
||||
marginy,
|
||||
],
|
||||
widget_base_dimensions: [
|
||||
widgetx,
|
||||
widgety,
|
||||
],
|
||||
cols: 1,
|
||||
}).data('gridster').disable();
|
||||
},
|
||||
});
|
||||
field_registry.add('kpi', KpiFieldWidget);
|
||||
return KpiFieldWidget;
|
||||
});
|
|
@ -54,7 +54,7 @@ odoo.define('kpi_dashboard.AbstractWidget', function (require) {
|
|||
return;
|
||||
this.fillWidget(values);
|
||||
var item = this.$el.find('[data-bind="value_last_update_display"]');
|
||||
if (item && values.value_last_update !== undefined) {
|
||||
if (item && ! values.compute_on_fly && values.value_last_update !== undefined) {
|
||||
var value = field_utils.parse.datetime(values.value_last_update);
|
||||
item.text(value.clone().add(
|
||||
this.getSession().getTZOffset(value), 'minutes').format(
|
||||
|
@ -82,7 +82,10 @@ odoo.define('kpi_dashboard.AbstractWidget', function (require) {
|
|||
_onClickDirectAction: function(event) {
|
||||
event.preventDefault();
|
||||
var $data = $(event.currentTarget).closest('a');
|
||||
return this.do_action($($data).data('id'));
|
||||
var action = this.actions[$($data).data('id')];
|
||||
return this.do_action(action.id, {
|
||||
additional_context: action.context
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
</t>
|
||||
<t t-name="kpi_dashboard.ManagePanel">
|
||||
<t t-if="widget.actions" >
|
||||
<t t-foreach="widget.actions" t-as="action">
|
||||
<t t-foreach="widget.actions" t-as="action_id">
|
||||
<t t-set="action" t-value="widget.actions[action_id]"/>
|
||||
<div role="menuitem" class="">
|
||||
<a role="menuitem" href="#" class="direct_action" t-att-data-id="action.id" t-att-data-type="action.type">Go to <t t-esc="action.name"/></a>
|
||||
<a role="menuitem" href="#" class="direct_action" t-att-data-id="action_id" t-att-data-type="action.type">Go to <t t-esc="action.name"/></a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
|
|
@ -57,3 +57,25 @@ result['previous'] = len(model.search([('id', '!=', %s)]))
|
|||
self.assertTrue(value.get("value"))
|
||||
self.assertEqual(value.get("value"), 1)
|
||||
self.assertEqual(value.get("previous"), self.kpi.search_count([]) - 1)
|
||||
self.assertFalse(self.kpi.history_ids)
|
||||
|
||||
def test_computation_history(self):
|
||||
self.assertFalse(self.kpi.value)
|
||||
self.kpi.store_history = True
|
||||
self.kpi.compute()
|
||||
self.assertTrue(self.kpi.history_ids)
|
||||
self.assertEqual(self.kpi.value, {})
|
||||
self.kpi.code = """
|
||||
result = {}
|
||||
result['value'] = len(model.search([('id', '=', %s)]))
|
||||
result['previous'] = len(model.search([('id', '!=', %s)]))
|
||||
""" % (
|
||||
self.kpi.id,
|
||||
self.kpi.id,
|
||||
)
|
||||
self.kpi.compute()
|
||||
value = self.kpi.value
|
||||
self.assertTrue(value.get("value"))
|
||||
self.assertEqual(value.get("value"), 1)
|
||||
self.assertEqual(value.get("previous"), self.kpi.search_count([]) - 1)
|
||||
self.assertTrue(self.kpi.history_ids)
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
<group name="color">
|
||||
<field name="background_color" widget="color"/>
|
||||
</group>
|
||||
<group name="config">
|
||||
<field name="compute_on_fly_refresh"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page name="group" string="Groups">
|
||||
|
|
|
@ -4,6 +4,55 @@
|
|||
|
||||
<odoo>
|
||||
|
||||
<record model="ir.actions.act_window" id="kpi_kpi_history_act_window">
|
||||
<field name="name">Kpi History</field>
|
||||
<field name="res_model">kpi.kpi.history</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="domain">[('kpi_id', '=', active_id)]</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_widget_form_view">
|
||||
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="create_date" invisible="1"/>
|
||||
<field name="widget" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="value"
|
||||
widget="kpi"
|
||||
options="{'date': 'create_date', 'widget': 'widget', 'name': 'name'}"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_raw_form_view">
|
||||
<field name="name">kpi.kpi.history.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="raw_value"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_history_tree_view">
|
||||
<field name="name">kpi.kpi.history.tree (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi.history</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" delete="0">
|
||||
<field name="create_date"/>
|
||||
<button name="show_form" string="Show value" type="object"
|
||||
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_widget_form_view)d}"
|
||||
/>
|
||||
<button name="show_form" string="Raw value" type="object"
|
||||
context="{'form_id': %(kpi_dashboard.kpi_kpi_history_raw_form_view)d}"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_form_view">
|
||||
<field name="name">kpi.kpi.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi</field>
|
||||
|
@ -11,11 +60,20 @@
|
|||
<form>
|
||||
<header>
|
||||
<button name="generate_cron" string="Generate cron" type="object"
|
||||
attrs="{'invisible': [('cron_id', '!=',False)]}"/>
|
||||
<button name="compute" string="Compute now" type="object"/>
|
||||
attrs="{'invisible': ['|', ('cron_id', '!=',False), ('compute_on_fly', '=', True)]}"/>
|
||||
<button name="compute" string="Compute now" type="object" attrs="{'invisible': [('compute_on_fly', '=', True)]}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="%(kpi_dashboard.kpi_kpi_history_act_window)d"
|
||||
string="Show history"
|
||||
type="action"
|
||||
attrs="{'invisible': ['|', ('store_history', '=', False), ('compute_on_fly', '=', True)]}"
|
||||
icon="fa-history"/>
|
||||
<button string="Show value" type="object" name="show_value"
|
||||
icon="fa-paint-brush"
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
<field name="name"/>
|
||||
</h2>
|
||||
|
@ -23,6 +81,10 @@
|
|||
<group>
|
||||
<field name="computation_method"/>
|
||||
<field name="widget"/>
|
||||
<field name="store_history" attrs="{'invisible': [('compute_on_fly', '=', True)]}"/>
|
||||
<field name="store_history_interval" attrs="{'invisible': [('store_history', '=', False)]}"/>
|
||||
<field name="store_history_interval_number" attrs="{'invisible': [('store_history', '=', False)]}"/>
|
||||
<field name="compute_on_fly" attrs="{'invisible': [('store_history', '=', True)]}"/>
|
||||
<field name="model_id" attrs="{'invisible': [('computation_method', '!=', 'function')]}"/>
|
||||
<field name="function" attrs="{'required': [('computation_method', '=', 'function')], 'invisible': [('computation_method', '!=', 'function')]}"/>
|
||||
<field name="args" attrs="{'invisible': [('computation_method', '!=', 'function')]}"/>
|
||||
|
@ -42,6 +104,7 @@
|
|||
<field name="action_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="action"/>
|
||||
<field name="context"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
@ -56,6 +119,21 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_widget_form_view">
|
||||
<field name="name">kpi.kpi.raw.form (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="computed_date" invisible="1"/>
|
||||
<field name="widget" invisible="1"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="computed_value"
|
||||
widget="kpi"
|
||||
options="{'date': 'computed_date', 'widget': 'widget', 'name': 'name'}"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="kpi_kpi_search_view">
|
||||
<field name="name">kpi.kpi.search (in kpi_dashboard)</field>
|
||||
<field name="model">kpi.kpi</field>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/meter_widget.js"/>
|
||||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/graph_widget.js"/>
|
||||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/widget/text_widget.js"/>
|
||||
<script type="text/javascript" src="/kpi_dashboard/static/src/js/field_widget.js"/>
|
||||
|
||||
<link rel="stylesheet" type="text/scss" href="/kpi_dashboard/static/src/scss/kpi_dashboard.scss"/>
|
||||
</xpath>
|
||||
|
|
Loading…
Reference in New Issue