Browse Source

[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 counters
pull/1028/head
Holger Brunn 8 years ago
parent
commit
c75987972b
No known key found for this signature in database GPG Key ID: 1C9760FECA3AE18
  1. 3
      web_menu_navbar_needaction/README.rst
  2. 1
      web_menu_navbar_needaction/__manifest__.py
  3. 108
      web_menu_navbar_needaction/models/ir_ui_menu.py
  4. 4
      web_menu_navbar_needaction/static/src/css/web_menu_navbar_needaction.css
  5. 57
      web_menu_navbar_needaction/static/src/js/web_menu_navbar_needaction.js
  6. 16
      web_menu_navbar_needaction/views/ir_ui_menu.xml

3
web_menu_navbar_needaction/README.rst

@ -1,5 +1,6 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:alt: License: AGPL-3 :alt: License: AGPL-3
================================ ================================
Needaction counters in main menu 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. 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 Usage
===== =====

1
web_menu_navbar_needaction/__manifest__.py

@ -29,6 +29,7 @@
'mail', 'mail',
], ],
"data": [ "data": [
"views/ir_ui_menu.xml",
"data/ir_config_parameter.xml", "data/ir_config_parameter.xml",
'views/templates.xml', 'views/templates.xml',
], ],

108
web_menu_navbar_needaction/models/ir_ui_menu.py

@ -17,25 +17,123 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # 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): class IrUiMenu(models.Model):
_inherit = 'ir.ui.menu' _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 @api.multi
def get_navbar_needaction_data(self): def get_navbar_needaction_data(self):
result = {} result = {}
for this in self: for this in self:
count_per_model = {} count_per_model = {}
action_menu = self.env['ir.ui.menu'].browse([])
if not this.needaction:
continue
for menu_id, needaction in self.search( for menu_id, needaction in self.search(
[('id', 'child_of', this.ids)])._filter_visible_menus()\ [('id', 'child_of', this.ids)])._filter_visible_menus()\
.get_needaction_data().iteritems(): .get_needaction_data().iteritems():
if needaction['needaction_enabled']: 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[model] = max(
count_per_model.get(model), 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 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))

4
web_menu_navbar_needaction/static/src/css/web_menu_navbar_needaction.css

@ -2,3 +2,7 @@
{ {
margin-left: .3em; margin-left: .3em;
} }
#oe_main_menu_navbar .badge:hover
{
transform: scale(1.1);
}

57
web_menu_navbar_needaction/static/src/js/web_menu_navbar_needaction.js

@ -50,29 +50,72 @@ openerp.web_menu_navbar_needaction = function(instance)
.map(function() { return parseInt(jQuery(this).attr('data-menu')); }) .map(function() { return parseInt(jQuery(this).attr('data-menu')); })
.get(); .get();
return new instance.web.Model('ir.ui.menu') 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)); .then(this.proxy(this.process_navbar_needaction));
}, },
process_navbar_needaction: function(data) process_navbar_needaction: function(data)
{ {
var self = this; var self = this;
_.each(data, function (needaction_count, menu_id)
_.each(data, function (needaction_data, menu_id)
{ {
var $item = self.$el.parents('body').find( var $item = self.$el.parents('body').find(
_.str.sprintf('#oe_main_menu_navbar a[data-menu="%s"]', _.str.sprintf('#oe_main_menu_navbar a[data-menu="%s"]',
menu_id)); menu_id));
if(!$item.length)
if(!$item.length || _.isEmpty(needaction_data))
{ {
return; return;
} }
$item.find('.badge').remove(); $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');
}, },
}) })

16
web_menu_navbar_needaction/views/ir_ui_menu.xml

@ -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…
Cancel
Save