mirror of https://github.com/OCA/web.git
[IMP] web_menu_navbar_needaction: reflow, new features (#265)
[IMP] web_menu_navbar_needaction: Several improvements: * Make the menu reflow after updating needactions * Allow to disable needaction completely or to set a custom domain * Support for clicking the number to end up on the first action * No need to block the UI for our request * Don't crash on corner cases, filter out search defaults from context, disable custom filters * Allow to define needaction domains for any menu * Support server actions * Support models implementing the function, but not the interface * Show a main menu's child needaction counterspull/1028/head
parent
1994a8b98e
commit
c75987972b
|
@ -1,5 +1,6 @@
|
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:alt: License: AGPL-3
|
||||
|
||||
================================
|
||||
Needaction counters in main menu
|
||||
================================
|
||||
|
@ -15,6 +16,8 @@ To configure the update frequency, set the configuration parameter `web_menu_nav
|
|||
|
||||
To disable updates, set the parameter to 0.
|
||||
|
||||
For more fine-grained control over the menus, you can turn off needaction for a menu by setting its field `needaction` to `False`, or in case you need a different needaction domain, set `needaction_domain` on the menuitem to the domain you need. A side effect of this is that you can use this addon to add needaction capabilities to any model, independent of it implementing the respective mixin by simply filling in your domain there.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
'mail',
|
||||
],
|
||||
"data": [
|
||||
"views/ir_ui_menu.xml",
|
||||
"data/ir_config_parameter.xml",
|
||||
'views/templates.xml',
|
||||
],
|
||||
|
|
|
@ -17,25 +17,123 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp import models, api
|
||||
import operator
|
||||
from openerp import _, models, api, fields
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.exceptions import Warning as UserError
|
||||
from openerp.osv import expression
|
||||
|
||||
|
||||
class IrUiMenu(models.Model):
|
||||
_inherit = 'ir.ui.menu'
|
||||
|
||||
needaction = fields.Boolean(
|
||||
help='Set to False to disable needaction for specific menu items',
|
||||
default=True)
|
||||
needaction_domain = fields.Text(
|
||||
help='If your menu item needs a different domain, set it here. It '
|
||||
'will override the model\'s needaction domain completely.')
|
||||
|
||||
@api.multi
|
||||
def get_navbar_needaction_data(self):
|
||||
result = {}
|
||||
for this in self:
|
||||
count_per_model = {}
|
||||
action_menu = self.env['ir.ui.menu'].browse([])
|
||||
if not this.needaction:
|
||||
continue
|
||||
for menu_id, needaction in self.search(
|
||||
[('id', 'child_of', this.ids)])._filter_visible_menus()\
|
||||
.get_needaction_data().iteritems():
|
||||
if needaction['needaction_enabled']:
|
||||
model = self.env['ir.ui.menu'].browse(menu_id).action\
|
||||
.res_model
|
||||
menu = self.env['ir.ui.menu'].browse(menu_id)
|
||||
model = menu._get_needaction_model()
|
||||
count_per_model[model] = max(
|
||||
count_per_model.get(model),
|
||||
needaction['needaction_counter'])
|
||||
result[this.id] = sum(count_per_model.itervalues())
|
||||
needaction['needaction_counter']
|
||||
)
|
||||
if needaction['needaction_counter'] and not action_menu:
|
||||
action_menu = menu
|
||||
result[this.id] = {
|
||||
'count': sum(count_per_model.itervalues()),
|
||||
}
|
||||
if action_menu:
|
||||
result[this.id].update({
|
||||
'action_id': action_menu.action and
|
||||
action_menu.action.id or None,
|
||||
'action_domain': action_menu._eval_needaction_domain(),
|
||||
})
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def get_needaction_data(self):
|
||||
result = super(IrUiMenu, self).get_needaction_data()
|
||||
for this in self.sorted(operator.itemgetter('parent_left'), True):
|
||||
data = result[this.id]
|
||||
if data['needaction_enabled'] or this.needaction and\
|
||||
this.needaction_domain:
|
||||
if not this.needaction:
|
||||
data['needaction_enabled'] = False
|
||||
data['needaction_counter'] = 0
|
||||
continue
|
||||
if this.needaction_domain and\
|
||||
this._get_needaction_model() is not None:
|
||||
data['needaction_enabled'] = True
|
||||
data['needaction_counter'] = this._get_needaction_model()\
|
||||
.search_count(this._eval_needaction_domain())
|
||||
if not data['needaction_enabled'] and this.needaction and\
|
||||
this.child_id and this.parent_id and this.parent_id.parent_id:
|
||||
# if the user didn't turn it off, show counters for submenus
|
||||
# but only from the 3rd level (the first that is closed by
|
||||
# default)
|
||||
for child in this.child_id:
|
||||
data['needaction_counter'] += result.get(child.id, {}).get(
|
||||
'needaction_counter', 0)
|
||||
data['needaction_enabled'] = bool(data['needaction_counter'])
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def _eval_needaction_domain(self):
|
||||
self.ensure_one()
|
||||
eval_context = {
|
||||
'uid': self.env.user.id,
|
||||
'user': self.env.user,
|
||||
}
|
||||
if self.needaction_domain:
|
||||
return safe_eval(self.needaction_domain, locals_dict=eval_context)
|
||||
model = self._get_needaction_model()
|
||||
if model is None or not hasattr(model, '_needaction_domain_get'):
|
||||
return []
|
||||
return expression.AND([
|
||||
safe_eval(
|
||||
'domain' in self.action._fields and self.action.domain or '[]',
|
||||
locals_dict=eval_context),
|
||||
model._needaction_domain_get(),
|
||||
])
|
||||
|
||||
@api.multi
|
||||
def _get_needaction_model(self):
|
||||
if not self.action:
|
||||
return None
|
||||
model = None
|
||||
if 'res_model' in self.action._fields:
|
||||
model = self.action.res_model
|
||||
elif 'model_id' in self.action._fields:
|
||||
model = self.action.model_id.model
|
||||
if model in self.env.registry:
|
||||
return self.env[model]
|
||||
return None
|
||||
|
||||
@api.constrains('needaction_domain')
|
||||
@api.multi
|
||||
def _check_needaction_domain(self):
|
||||
for this in self:
|
||||
try:
|
||||
expression.AND([
|
||||
this._eval_needaction_domain(),
|
||||
expression.TRUE_DOMAIN,
|
||||
])
|
||||
except Exception as ex:
|
||||
raise UserError(
|
||||
_('Cannot evaluate %s to a search domain:\n%s') %
|
||||
(self.needaction_domain, ex))
|
||||
|
|
|
@ -2,3 +2,7 @@
|
|||
{
|
||||
margin-left: .3em;
|
||||
}
|
||||
#oe_main_menu_navbar .badge:hover
|
||||
{
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
|
|
@ -50,29 +50,72 @@ openerp.web_menu_navbar_needaction = function(instance)
|
|||
.map(function() { return parseInt(jQuery(this).attr('data-menu')); })
|
||||
.get();
|
||||
return new instance.web.Model('ir.ui.menu')
|
||||
.call('get_navbar_needaction_data', [this.navbar_menu_ids])
|
||||
.call('get_navbar_needaction_data', [this.navbar_menu_ids],
|
||||
{}, {shadow: true})
|
||||
.then(this.proxy(this.process_navbar_needaction));
|
||||
},
|
||||
process_navbar_needaction: function(data)
|
||||
{
|
||||
var self = this;
|
||||
_.each(data, function (needaction_count, menu_id)
|
||||
_.each(data, function (needaction_data, menu_id)
|
||||
{
|
||||
var $item = self.$el.parents('body').find(
|
||||
_.str.sprintf('#oe_main_menu_navbar a[data-menu="%s"]',
|
||||
menu_id));
|
||||
if(!$item.length)
|
||||
if(!$item.length || _.isEmpty(needaction_data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$item.find('.badge').remove();
|
||||
if(needaction_count)
|
||||
if(needaction_data.count)
|
||||
{
|
||||
$item.append(
|
||||
instance.web.qweb.render("Menu.needaction_counter",
|
||||
{widget : {needaction_counter: needaction_count}}));
|
||||
var $counter = jQuery(
|
||||
instance.web.qweb.render("Menu.needaction_counter",
|
||||
{
|
||||
widget: {
|
||||
needaction_counter: needaction_data.count,
|
||||
}
|
||||
}))
|
||||
.appendTo($item);
|
||||
if(needaction_data.action_id)
|
||||
{
|
||||
$counter.click(function(ev)
|
||||
{
|
||||
var parent = self.getParent();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
return parent.menu_dm.add(
|
||||
self.rpc('/web/action/load', {
|
||||
action_id: needaction_data.action_id,
|
||||
}))
|
||||
.then(function(action)
|
||||
{
|
||||
return parent.action_mutex.exec(function()
|
||||
{
|
||||
action.domain = needaction_data.action_domain;
|
||||
action.context = new instance.web.CompoundContext(
|
||||
action.context || {}
|
||||
);
|
||||
action.context = instance.web.pyeval.eval(
|
||||
'context', action.context
|
||||
);
|
||||
_.each(_.keys(action.context), function(key)
|
||||
{
|
||||
if(key.startsWith('search_default'))
|
||||
{
|
||||
delete action.context[key];
|
||||
}
|
||||
});
|
||||
return parent.action_manager.do_action(
|
||||
action, {disable_custom_filters: true}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
instance.web.bus.trigger('resize');
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="edit_menu_access" model="ir.ui.view">
|
||||
<field name="model">ir.ui.menu</field>
|
||||
<field name="inherit_id" ref="base.edit_menu_access" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="sequence" position="after">
|
||||
<field name="needaction_enabled" invisible="1" />
|
||||
<field name="needaction" />
|
||||
<field name="needaction_domain" attrs="{'invisible': [('needaction', '=', False)]}" placeholder="Fill in a domain for a custom needaction" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
Loading…
Reference in New Issue