Browse Source

upload "dashboard" modules

pull/1/head
Ivan Yelizariev 10 years ago
commit
507846838f
  1. 1
      __init__.py
  2. 19
      __openerp__.py
  3. 220
      models.py
  4. 2
      security/ir.model.access.csv
  5. 23
      static/src/css/main.css
  6. 115
      static/src/js/main.js
  7. 122
      static/src/xml/main.xml
  8. 103
      views.xml

1
__init__.py

@ -0,0 +1 @@
import models

19
__openerp__.py

@ -0,0 +1,19 @@
{
'name' : 'Extra Widgets for mail wall',
'version' : '1.0.0',
'author' : 'Ivan Yelizariev',
'category' : 'Custom',
'website' : 'https://it-projects.info',
'description': """
Tested on odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d
""",
'depends' : ['mail','gamification'],
'data':[
'views.xml',
#'data.xml',
'security/ir.model.access.csv',
],
'qweb': ['static/src/xml/main.xml'],
'installable': True,
}

220
models.py

@ -0,0 +1,220 @@
from openerp.osv import osv,fields as old_fields
from openerp import api, models, fields, tools
from openerp.tools.safe_eval import safe_eval
from openerp.addons.email_template.email_template import mako_template_env
import copy
from openerp.tools.translate import _
class mail_wall_widgets_widget(models.Model):
_name = 'mail.wall.widgets.widget'
_order = "sequence, id"
_columns = {
'name': old_fields.char('Name', required=True, translate=True),
'type': old_fields.selection(string='Type', selection=[
('list', 'List'),
('funnel', 'Funnel'),
('slice', 'Slice'),
#('', ''),
#('', ''),
#('', ''),
#('', ''),
], help='''
Slice - use "domain" for total and "won_domain" for target
'''),
'description': old_fields.text('Description', translate=True),
'group_ids': old_fields.many2many('res.groups', relation='mail_wall_widgets_widget_group', column1='widget_id', column2='group_id', string='Groups', help="User groups to show widget"),
'model_id': old_fields.many2one('ir.model', string='Model', help='The model object for the field to evaluate'),
'domain': old_fields.char("Filter Domain", help="Domain for filtering records. General rule, not user depending, e.g. [('state', '=', 'done')]. The expression can contain reference to 'user' which is a browse record of the current user if not in batch mode.", required=True),
'limit': old_fields.integer('Limit', help='Limit count of records to show'),
'order': old_fields.char('Order', help='Order of records to show'),
'value_field_id': old_fields.many2one('ir.model.fields',
string='Value field',
help='The field containing the value of record'),
'stage_field_id': old_fields.many2one('ir.model.fields',
string='Stage field',
help='Field to split records in funnel. It can be selection type or many2one (the later should have "sequence" field)'),
#'stage_field_domain': old_fields.many2one('ir.model.fields',
# string='Stage field domain',
# help='(for many2one stage_field_id) Domain to find stage objects'),
'won_domain': old_fields.char('Won domain',
help='Domain to find won objects'),
'field_date_id': old_fields.many2one('ir.model.fields',
string='Date Field',
help='The date to use for the time period evaluated'),
'start_date': old_fields.date('Start Date'),
'end_date': old_fields.date('End Date'), # no start and end = always active
'content': old_fields.char('Line template', help='Mako template to show content'),
'value_field_monetary': old_fields.boolean('Value is monetary'),
'cache': old_fields.boolean('Cache'),
'active': old_fields.boolean('Active'),
'sequence': old_fields.integer('Sequence', help='Sequence number for ordering'),
}
_defaults = {
'active': True,
'cache': False,
'limit': None,
'order': None,
}
@api.one
def get_data(self, user):
domain = safe_eval(self.domain, {'user': user})
won_domain = safe_eval(self.won_domain or '[]', {'user': user})
field_date_name = self.field_date_id and self.field_date_id.name
if self.start_date and field_date_name:
domain.append((field_date_name, '>=', self.start_date))
if self.end_date and field_date_name:
domain.append((field_date_name, '<=', self.end_date))
res = {
'name': self.name,
'type': self.type,
'model': self.model_id.model,
'domain': str(domain),
}
obj = self.env[self.model_id.model]
if self.type == 'list':
res.update({
'more': self.limit and self.limit < obj.search_count(domain),
'lines': [],
})
for r in obj.search(domain, limit=self.limit, order=self.order):
mako = mako_template_env.from_string(tools.ustr(self.content))
content = mako.render({'record':r})
r_json = {
'id': r.id,
#'fields': dict( (f,getattr(r,f)) for f in fields),
'display_mode': 'progress',
'state': 'inprogress',
'completeness': 0,
'name': content,
'description': '',
}
if self.value_field_id:
r_json['current'] = getattr(r, self.value_field_id.name)
res['lines'].append(r_json)
elif self.type == 'funnel':
stage_ids = [] # [key]
for group in obj.read_group(domain, [], [self.stage_field_id.name]):
key = group[self.stage_field_id.name]
if isinstance(key, (list, tuple)):
key = key[0]
stage_ids.append(key)
stages = [] # [{'name':Name, 'id': key}]
if self.stage_field_id.ttype == 'selection':
d = dict (self.stage_field_id.selection)
stages = [ {'id':id, 'name':d[id]} for id in stage_ids ]
else: # many2one
stage_model = self.stage_field_id.relation
for r in self.env[stage_model].browse(stage_ids):
stages.append({'id': r.id, 'name':r.name_get()[0][1]})
value_field_name = self.value_field_id.name
for stage in stages:
d = copy.copy(domain)
d.append( (self.stage_field_id.name, '=', stage['id']) )
result = obj.read_group(d, [value_field_name], [])
stage['closed_value'] = result and result[0][value_field_name] or 0.0
stage['domain'] = str(d)
# won value
d = domain + won_domain
result = obj.read_group(domain, [value_field_name], [])
won = {'name': _('Won'),
'id':'__won__',
'closed_value': result and result[0][value_field_name] or 0.0
}
stages.append(won)
cur = 0
for stage in reversed(stages):
cur += stage['closed_value']
stage['abs_value'] = cur
total_value = stages[0]['abs_value']
precision = 0.1
for s in stages:
s['rel_value'] = round(100*s['abs_value']/total_value/precision)*precision if total_value else 100
# dummy fields
s['display_mode'] = 'progress'
s['monetary'] = 1
res['stages'] = stages
res['won'] = won
res['conversion_rate'] = stages[-1]['rel_value']
elif self.type == 'slice':
value_field_name = self.value_field_id.name
for f,d in [('total', domain), ('won', won_domain)]:
result = obj.read_group(d, [value_field_name], [])
res[f] = result and result[0][value_field_name] or 0.0
res['domain'] = str(domain)
res['won_domain'] = str(won_domain)
precision = 10
total_value = res['total']
res['slice'] = round(100*res['won']/res['total']/precision)*precision if res['total'] else 100
# dummy fields
res['display_mode'] = 'progress'
res['monetary'] = self.value_field_monetary
return res
class mail_wall_widgets_cache(models.Model):
_name = 'mail.wall.widgets.cache'
cache = fields.Text('Cached data')
res_id = fields.Integer('Resource ID')
res_model = fields.Integer('Resource Model')
user_id = fields.Many2one('res.users')
class res_users(models.Model):
_inherit = 'res.users'
@api.v7
def get_serialised_mail_wall_widgets_summary(self, cr, uid, excluded_categories=None, context=None):
return self._get_serialised_mail_wall_widgets_summary(cr, uid, uid, excluded_categories=excluded_categories, context=context)[0]
@api.one
def _get_serialised_mail_wall_widgets_summary(self, excluded_categories=None):
"""
[
{
'id': ...,
'model': ...,
'currency': <res.currency id>,
'data': (depend on model)
},
]
"""
user = self.env.user
res = []
model = 'mail.wall.widgets.widget'
domain = [('group_ids', 'in', user.groups_id.ids), ('active', '=', True)]
for widget in self.env[model].search(domain, order='sequence'):
if widget.cache:
#TODO
continue
res.append({
'model': model,
'id': widget.id,
'currency': user.company_id.currency_id.id,
'data': widget.get_data(user)[0],
})
return res
#def get_challenge_suggestions(self, cr, uid, context=None):
# """Return the list of challenges suggested to the user"""
# challenge_info = []
# challenge_obj = self.pool.get('mail_wall_widgets.challenge')
# challenge_ids = challenge_obj.search(cr, uid, [('invited_user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
# for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
# values = {
# 'id': challenge.id,
# 'name': challenge.name,
# 'description': challenge.description,
# }
# challenge_info.append(values)
# return challenge_info

2
security/ir.model.access.csv

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_wall_widgets,mail.wall.widgets.widget,model_mail_wall_widgets_widget,,1,1,1,1

23
static/src/css/main.css

@ -0,0 +1,23 @@
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_mail_wall_widgets{
background-color: #ededf6;
}
.oe_open_record:hover, .oe_open_record_list:hover{
cursor:pointer;
}
.oe_goal_outer_box .more{
text-align:center;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list.funnel .oe_cell.oe_goal_current {
font-size: 150%;
font-weight: bold;
min-width: 65px;
padding: 0 5px;
}
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current span.small {
font-weight: normal;
font-size: 70%;
}
.oe_open_record:hover, .oe_open_record_list_funnel:hover{
cursor:pointer;
}

115
static/src/js/main.js

@ -0,0 +1,115 @@
openerp.mail_wall_widgets = function(instance) {
var QWeb = instance.web.qweb;
var _t = instance.web._t;
instance.mail_wall_widgets.Sidebar = instance.web.Widget.extend({
template: 'mail_wall_widgets.UserWallSidebar',
init: function (parent, action) {
var self = this;
this._super(parent, action);
this.deferred = $.Deferred();
//$(document).off('keydown.klistener');
this.widget_templates = {
'mail.wall.widgets.widget': "mail_wall_widgets.Widget"
}
},
events: {
'click .oe_open_record': function(event){
var $t = $(event.currentTarget);
this.do_action({
'name': _t('Details'),
'type': 'ir.actions.act_window',
'res_model': $t.parent().attr('data-model'),
'res_id': parseInt($t.attr('data-id')),
'target': 'current',
'views': [[false, 'form'],[false, 'list']],
'domain': $t.parent().attr('data-domain'),
})
},
'click .oe_open_record_list': function(event){
var $t = $(event.currentTarget);
this.do_action({
'name': _t('More...'),
'type': 'ir.actions.act_window',
'res_model': $t.parent().attr('data-model'),
'target': 'current',
'views': [[false, 'list'],[false, 'form']],
'domain': $t.parent().attr('data-domain'),
})
},
'click .oe_open_record_list_funnel': function(event){
var $t = $(event.currentTarget);
this.do_action({
'name': _t('More...'),
'type': 'ir.actions.act_window',
'res_model': $t.parent().attr('data-model'),
'target': 'current',
'views': [[false, 'list'],[false, 'form']],
'domain': $t.attr('data-domain'),
})
},
},
start: function() {
var self = this;
this._super.apply(this, arguments);
self.get_widgets_info();
},
get_widgets_info: function() {
var self = this;
new instance.web.Model('res.users').call('get_serialised_mail_wall_widgets_summary', []).then(function(result) {
if (result.length === 0) {
self.$el.find(".oe_mail_wall_widgets").hide();
} else {
self.$el.find(".oe_mail_wall_widgets").empty();
_.each(result, function(item){
var $item = $(QWeb.render(self.widget_templates[item.model], {info: item}));
self.render_money_fields($item);
//self.render_user_avatars($item);
self.$el.find('.oe_mail_wall_widgets').append($item);
});
}
});
},
render_money_fields: function(item) {
var self = this;
self.dfm = new instance.web.form.DefaultFieldManager(self);
// Generate a FieldMonetary for each .oe_goal_field_monetary
item.find(".oe_goal_field_monetary").each(function() {
var currency_id = parseInt( $(this).attr('data-id'), 10);
money_field = new instance.web.form.FieldMonetary(self.dfm, {
attrs: {
modifiers: '{"readonly": true}'
}
});
money_field.set('currency', currency_id);
money_field.get_currency_info();
money_field.set('value', parseInt($(this).text(), 10));
money_field.replace($(this));
});
},
render_user_avatars: function(item) {
var self = this;
item.find(".oe_user_avatar").each(function() {
var user_id = parseInt( $(this).attr('data-id'), 10);
var url = instance.session.url('/web/binary/image', {model: 'res.users', field: 'image_small', id: user_id});
$(this).attr("src", url);
});
}
});
instance.web.WebClient.include({
to_kitten: function() {
this._super();
new instance.web.Model('mail_wall_widgets.badge').call('check_progress', []);
}
});
instance.mail.Wall.include({
start: function() {
this._super();
var sidebar = new instance.mail_wall_widgets.Sidebar(this);
sidebar.appendTo($('.oe_mail_wall_aside'));
},
});
};

122
static/src/xml/main.xml

@ -0,0 +1,122 @@
<templates>
<t t-name="mail_wall_widgets.UserWallSidebar" class="oe_mail_wall_widgets_user_wall_sidebar">
<div>
<div class="oe_mail_wall_widgets"></div>
</div>
</t>
<t t-name="mail_wall_widgets.Widget">
<div class="oe_goal">
<div>
<a class="oe_update_challenge oe_e" rol="button" t-attf-id="{info.id}" t-attf-model="{info.model}">e</a>
<h4><t t-esc="info.data.name" /></h4>
</div>
<t t-if="info.model=='mail.wall.widgets.widget' and info.data.type == 'list'">
<div class="oe_table oe_goals_list" t-att-data-model="info.data.model" t-att-data-domain="info.data.domain">
<div t-foreach="info.data.lines" t-as="line" t-attf-class="oe_row oe_goal_outer_box oe_open_record record #{line.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}" t-att-data-id="line.id" >
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+line.completeness+'%;' : 'width:0;'}"></div>
<div class="oe_cell oe_goal_current"><t t-esc="line.current" /></div>
</t>
<div class="oe_cell">
<span t-att-title="line.description"><t t-raw="line.name" /></span>
</div>
</div>
<div t-if="info.data.more" class="oe_row oe_goal_outer_box oe_open_record_list">
<div class="oe_goal_progress_background"></div>
<div class="more">
More...
</div>
</div>
</div>
</t>
<t t-if="info.model=='mail.wall.widgets.widget' and info.data.type == 'funnel'">
<div class="oe_table oe_goals_list funnel" t-att-data-model="info.data.model" t-att-data-domain="info.data.domain">
<div t-foreach="info.data.stages" t-as="line" t-attf-class="oe_row oe_goal_outer_box oe_open_record_list_funnel record #{line.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}" t-att-data-id="line.id" t-att-data-domain="line.domain" >
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+line.rel_value+'%;' : 'width:0;'}"></div>
<div class="oe_cell oe_goal_current"><t t-esc="line.rel_value" /><span class="small">%</span></div>
</t>
<div class="oe_cell">
<span t-att-title="line.description"><b><t t-raw="line.name" /></b></span> - <span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? info.currency : ''}"><t t-esc="line.abs_value" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</div>
</div>
</div>
</t>
<t t-if="info.model=='mail.wall.widgets.widget' and info.data.type == 'slice'">
<div class="oe_table oe_goals_list funnel" t-att-data-model="info.data.model" t-att-data-domain="info.data.domain">
<t t-set="line" t-value="info.data"/>
<div t-attf-class="oe_row oe_goal_outer_box oe_open_record_list record #{line.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}" t-att-data-id="line.id" t-att-data-domain="line.won_domain" >
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+line.slice+'%;' : 'width:0;'}"></div>
<div class="oe_cell oe_goal_current"><t t-esc="line.slice" /><span class="small">%</span></div>
</t>
<div class="oe_cell">
<span t-att-title="line.description"><b><span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? info.currency : ''}"><t t-esc="line.won" /></span></b></span> from <span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? info.currency : ''}"><t t-esc="line.total" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</div>
</div>
</div>
</t>
<t t-if="0 and challenge.visibility_mode == 'ranking'">
<div t-foreach="challenge.lines" t-as="line" class="oe_goals_list oe_table">
<div class="oe_row">
<div class="oe_cell oe_thead" colspan="3" t-attf-title="#{line.description ? line.description : ''}">
<strong><t t-esc="line.name"/></strong>
<br/>
<div class="oe_grey">
<t t-if="line.definition_condition == 'higher'">
Target:
</t>
<t t-if="line.definition_condition == 'lower'">
Target: &lt;=
</t>
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="line.target" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</div>
</div>
</div>
<div t-foreach="line.goals" t-as="goal" t-attf-class="#{goal.id == line.own_goal_id ? 'oe_bold' : ''}">
<div t-attf-class="oe_row oe_goal_outer_box #{goal.state == 'reached' ? 'oe_goal_reached' : ''} #{line.display_mode != 'progress' ? 'oe_no_progress' : ''}">
<t t-if="line.display_mode == 'progress'">
<div class="oe_goal_progress_background"></div>
<div class="oe_goal_progress" t-attf-style="#{line.display_mode == 'progress' ? 'width: '+goal.completeness+'%;' : 'width:0;'}"></div>
</t>
<div class="oe_cell col0"><t t-esc="goal.rank" /></div>
<div class="oe_cell col1"><img class="oe_user_avatar" t-attf-alt="#{goal.name}" t-attf-data-id="#{goal.user_id}"/></div>
<div class="oe_cell col2">
<t t-if="line.display_mode == 'progress'">
<!-- progress, action on current value -->
<t t-esc="goal.name"/><br/>
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</t>
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
<a class="oe_goal_action" t-att-id="line.own_goal_id">
<span t-attf-class="#{line.monetary ? 'oe_goal_field_monetary' : ''}" t-attf-data-id="#{line.monetary ? challenge.currency : ''}"><t t-esc="goal.current" /></span><t t-if="line.suffix"> <t t-esc="line.suffix"/></t>
</a>
</t>
</t>
<t t-if="line.display_mode != 'progress'">
<!-- not progress, action on user name -->
<t t-if="goal.id != line.own_goal_id or (line.computation_mode != 'manually' and !line.action)">
<t t-esc="goal.name"/>
</t>
<t t-if="goal.id == line.own_goal_id and (line.action or line.computation_mode == 'manually')">
<a class="oe_goal_action" t-att-id="line.own_goal_id"><t t-esc="goal.name"/></a>
</t>
</t>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
</templates>

103
views.xml

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets_backend" name="assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/mail_wall_widgets/static/src/css/main.css"/>
<script type="text/javascript" src="/mail_wall_widgets/static/src/js/main.js"></script>
</xpath>
</template>
<!-- Widget -->
<record id="mail_wall_widgets_widget_action" model="ir.actions.act_window">
<field name="name">Widgets</field>
<field name="res_model">mail.wall.widgets.widget</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a Widget.
</p>
<p>
Widget allows to show some information at the inbox
</p>
</field>
</record>
<record id="mail_wall_widgets_list_view" model="ir.ui.view">
<field name="name">Widget list view</field>
<field name="model">mail.wall.widgets.widget</field>
<field name="arch" type="xml">
<tree string="Widgets">
<field name="name"/>
<field name="sequence"/>
<field name="domain"/>
</tree>
</field>
</record>
<record id="mail_wall_widgets_form_view" model="ir.ui.view">
<field name="name">Widget form view</field>
<field name="model">mail.wall.widgets.widget</field>
<field name="arch" type="xml">
<form string="Widget">
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" class="oe_inline"/>
</h1>
<field name="type" class="oe_inline"/>
<label for="description" class="oe_edit_only"/>
<div>
<field name="description" class="oe_inline"/>
</div>
<group string="How to select records?">
<field name="model_id" class="oe_inline"/>
<field name="domain" />
<field name="won_domain" attrs="{'invisible':[('type', 'not in', ['funnel','slice'])]}" />
<field name="field_date_id"/>
<field name="start_date"/>
<field name="end_date"/>
</group>
<group string="How to select users?">
<field name="group_ids" class="oe_inline" widget="many2many_tags"/>
</group>
<group string="How show data?">
<field name="stage_field_id" attrs="{'invisible':[('type', 'not in', ['funnel'])]}"/>
<field name="value_field_id" class="oe_inline"/>
<field name="value_field_monetary" class="oe_inline"/>
<field name="content" attrs="{'invisible':[('type', 'not in', ['list'])]}"/>
<field name="limit" attrs="{'invisible':[('type', 'not in', ['list'])]}" class="oe_inline"/>
<field name="order" attrs="{'invisible':[('type', 'not in', ['list'])]}" class="oe_inline"/>
</group>
<group string="Other">
<field name="sequence" class="oe_inline"/>
<field name="active"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="mail_wall_widgets_search_view" model="ir.ui.view">
<field name="name">Widget Search</field>
<field name="model">mail.wall.widgets.widget</field>
<field name="arch" type="xml">
<search string="Search Widget">
<field name="name"/>
<field name="model_id"/>
<group expand="0" string="Group By">
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
</group>
</search>
</field>
</record>
<!-- menus in settings - technical feature required -->
<menuitem id="mail_wall_widgets_widget_menu" parent="gamification.gamification_menu" action="mail_wall_widgets_widget_action" sequence="50"/>
</data>
</openerp>
Loading…
Cancel
Save