Thibault Delavallée (OpenERP) has proposed merging
lp:~openerp-dev/openobject-server/trunk-need_action-tde into
lp:openobject-server.
Requested reviews:
OpenERP Core Team (openerp)
For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-need_action-tde/+merge/97207
Need action mixin class
=======================
This revision adds a mixin class for objects using the need action feature.
Need action mechanism can be used by objects that have to be able to signal
that an action is required on a particular record. If in the business logic an
action must be performed by somebody, for instance validation by a manager,
this mechanism allows to set a field with the user_id of the user requested to
perform the action.
This class wraps a table (base.needaction_users_rel) that behaves like a
many2many field. However, no field is added to the model inheriting from
base.needaction. The mixin class manages the low-level considerations of
updating relationships. Every change made on the record calls a method that
updates the relationships.
Objects using the need_action feature should override the
``get_needaction_user_ids`` method. This methods returns a dictionary whose
keys are record ids, and values a list of user ids, like in a many2many
relationship. Therefore by defining only one method, you can specify if an
action is required by defining the users that have to do it, in every possible
situation.
This class also offers several global services,:
- ``needaction_get_user_record_references``: for a given uid, get all the
records that asks this user to perform an action. Records are given as
references, a list of tuples (model_name, record_id).
This mechanism is used for instance to display the number of pending actions in
menus, such as Leads (12).
A menu in Settings/Users has been added to allows having a quick look to
need_action_user_ids.
--
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-need_action-tde/+merge/97207
Your team OpenERP R&D Team is subscribed to branch
lp:~openerp-dev/openobject-server/trunk-need_action-tde.
=== added file 'doc/api/need_action_specs.rst'
--- doc/api/need_action_specs.rst 1970-01-01 00:00:00 +0000
+++ doc/api/need_action_specs.rst 2012-04-03 07:57:19 +0000
@@ -0,0 +1,48 @@
+Need action mechanism
+=====================
+
+base.needaction mixin class
++++++++++++++++++++++++++++
+
+This revision adds a mixin class for objects using the need action feature.
+
+Need action feature can be used by objects willing to be able to signal that an action is required on a particular record. If in the business logic an action must be performed by somebody, for instance validation by a manager, this mechanism allows to set a list of users asked to perform an action.
+
+This class wraps a table (base.needaction_users_rel) that behaves like a many2many field. However, no field is added to the model inheriting from base.needaction. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships.
+
+Objects using the need_action feature should override the ``get_needaction_user_ids`` method. This methods returns a dictionary whose keys are record ids, and values a list of user ids, like in a many2many relationship. Therefore by defining only one method, you can specify if an action is required by defining the users that have to do it, in every possible situation.
+
+This class also offers several global services,:
+ - ``needaction_get_record_ids``: for a given model_name and uid, get all record ids that ask this user to perform an action. This mechanism is used for instance to display the number of pending actions in menus, such as Leads (12)
+ - ``needaction_get_action_count``: as ``needaction_get_record_ids`` but returns only the number of action, not the ids (performs a search with count=True)
+ - ``needaction_get_user_record_references``: for a given uid, get all the records that ask this user to perform an action. Records are given as references, a list of tuples (model_name, record_id)
+
+Menu modification
++++++++++++++++++
+
+This revision adds three functional fields to ``ir.ui.menu`` model :
+ - ``uses_needaction``: boolean field. If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.
+ - ``needaction_uid_ctr``: integer field. If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.
+ - ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action.
+
+Those fields are functional, because they must be recalculated for each user, and each time menus are displayed. ``needaction_uid_ctr`` takes into account the domain of the action, in order to display accurate numbers.
+
+Addon implementation example
+++++++++++++++++++++++++++++
+
+In your ``foo`` module, you want to specify that when it is in state ``confirmed``, it has to be validated by a manager, given by the field ``manager_id``. After making ``foo`` inheriting from ``base.needaction``, you override the ``get_needaction_user_ids`` method:
+
+::
+
+ [...]
+ _inherit = [base.needaction]
+ [...]
+ def get_needaction_user_ids(self, cr, uid, ids, context=None):
+ result = dict.fromkeys(ids)
+ for foo_obj in self.browse(cr, uid, ids, context=context):
+ # set the list void by default
+ result[foo_obj.id] = []
+ # if foo_obj is confirmed: manager is required to perform an action
+ if foo_obj.state == 'confirmed':
+ result[foo_obj.id] = [foo_obj.manager_id]
+ return result
=== modified file 'doc/index.rst.inc'
--- doc/index.rst.inc 2012-03-13 08:57:43 +0000
+++ doc/index.rst.inc 2012-04-03 07:57:19 +0000
@@ -14,3 +14,4 @@
:maxdepth: 1
api/user_img_specs
+ api/need_action_specs
=== modified file 'openerp/addons/base/ir/__init__.py'
--- openerp/addons/base/ir/__init__.py 2011-10-04 20:52:54 +0000
+++ openerp/addons/base/ir/__init__.py 2012-04-03 07:57:19 +0000
@@ -21,6 +21,7 @@
import ir_model
import ir_sequence
+import ir_needaction
import ir_ui_menu
import ir_ui_view
import ir_default
=== modified file 'openerp/addons/base/ir/ir.xml'
--- openerp/addons/base/ir/ir.xml 2012-02-08 21:30:47 +0000
+++ openerp/addons/base/ir/ir.xml 2012-04-03 07:57:19 +0000
@@ -661,6 +661,30 @@
<menuitem action="ir_action_wizard" id="menu_ir_action_wizard" parent="base.next_id_6"/>
+ <!-- Needaction mechanism -->
+ <record model="ir.ui.view" id="view_notification_tree">
+ <field name="name">ir.needaction_users_rel.tree</field>
+ <field name="model">ir.needaction_users_rel</field>
+ <field name="type">tree</field>
+ <field name="sequence">10</field>
+ <field name="arch" type="xml">
+ <tree string="Subscription">
+ <field name="user_id"/>
+ <field name="res_model"/>
+ <field name="res_id"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="action_view_needaction_users_rel" model="ir.actions.act_window">
+ <field name="name">Need action relationships</field>
+ <field name="res_model">ir.needaction_users_rel</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="menu_needaction_users_rel" name="Need actions" parent="base.menu_users" sequence="20" action="action_view_needaction_users_rel" groups="base.group_extended"/>
+
<!-- Companies -->
<menuitem id="menu_res_company_global"
parent="base.menu_administration"
=== added file 'openerp/addons/base/ir/ir_needaction.py'
--- openerp/addons/base/ir/ir_needaction.py 1970-01-01 00:00:00 +0000
+++ openerp/addons/base/ir/ir_needaction.py 2012-04-03 07:57:19 +0000
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2009-Today OpenERP SA (<http://www.openerp.com>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+import openerp.pooler as pooler
+from operator import itemgetter
+from osv import osv, fields
+from tools.translate import _
+
+class ir_needaction_users_rel(osv.osv):
+ '''
+ ir_needaction_users_rel holds data related to the needaction
+ mechanism inside OpenERP. A needaction is characterized by:
+ - res_model: model of the record requiring an action
+ - res_id: ID of the record requiring an action
+ - user_id: foreign key to the res.users table, to the user that
+ has to perform the action
+ '''
+
+ _name = 'ir.needaction_users_rel'
+ _description = 'Needaction relationship table'
+ _rec_name = 'id'
+ _order = 'id desc'
+ _columns = {
+ 'res_model': fields.char('Related Document Model', size=128,
+ select=1, required=True),
+ 'res_id': fields.integer('Related Document ID',
+ select=1, required=True),
+ 'user_id': fields.many2one('res.users', string='Related User',
+ ondelete='cascade', select=1, required=True),
+ }
+
+
+class ir_needaction(osv.osv):
+ '''Mixin class for objects using the need action feature.
+
+ Need action feature can be used by objects willing to be able to
+ signal that an action is required on a particular record. If in the
+ business logic an action must be performed by somebody, for instance
+ validation by a manager, this mechanism allows to set a list of
+ users asked to perform an action.
+
+ This class wraps a table (base.needaction_users_rel) that behaves
+ like a many2many field. However, no field is added to the model
+ inheriting from base.needaction. The mixin class manages the low-level
+ considerations of updating relationships. Every change made on the
+ record calls a method that updates the relationships.
+
+ Objects using the need_action feature should override the
+ ``get_needaction_user_ids`` method. This methods returns a dictionary
+ whose keys are record ids, and values a list of user ids, like
+ in a many2many relationship. Therefore by defining only one method,
+ you can specify if an action is required by defining the users
+ that have to do it, in every possible situation.
+
+ This class also offers several global services,:
+ - ``needaction_get_record_ids``: for a given model_name and uid, get
+ all record ids that ask this user to perform an action. This
+ mechanism is used for instance to display the number of pending
+ actions in menus, such as Leads (12)
+ - ``needaction_get_action_count``: as ``needaction_get_record_ids``
+ but returns only the number of action, not the ids (performs a
+ search with count=True)
+ - ``needaction_get_user_record_references``: for a given uid, get all
+ the records that ask this user to perform an action. Records
+ are given as references, a list of tuples (model_name, record_id)
+ '''
+ _name = 'ir.needaction'
+ _description = 'Need action of users on records API'
+
+ _columns = {
+ }
+
+ #------------------------------------------------------
+ # need action relationship management
+ #------------------------------------------------------
+
+ def _get_users(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ needact_obj = self.pool.get('ir.needaction_users_rel')
+ needact_ids = needact_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
+ return needact_obj.read(cr, uid, needact_ids, context=context)
+
+ def _link_users(self, cr, uid, ids, user_ids, context=None):
+ """Given ids of model self._name, add user_ids to the relationship table"""
+ if context is None:
+ context = {}
+ needact_obj = self.pool.get('ir.needaction_users_rel')
+ for id in ids:
+ for user_id in user_ids:
+ needact_obj.create(cr, uid, {'res_model': self._name, 'res_id': id, 'user_id': user_id}, context=context)
+ return True
+
+ def _unlink_users(self, cr, uid, ids, context=None):
+ """Given ids of model self._name, delete all entries in the relationship table"""
+ if context is None:
+ context = {}
+ needact_obj = self.pool.get('ir.needaction_users_rel')
+ to_del_ids = needact_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
+ return needact_obj.unlink(cr, uid, to_del_ids, context=context)
+
+ def _update_users(self, cr, uid, ids, user_ids, context=None):
+ """Given ids of model self._name, update their entries in the relationship table to user_ids"""
+ # read current records
+ cur_needact_objs = self._get_users(cr, uid, ids, context=None)
+ if len(cur_needact_objs) == len(user_ids) and all([cur_needact_obj['user_id'] in user_ids for cur_needact_obj in cur_needact_objs]):
+ return True
+ # unlink old records
+ self._unlink_users(cr, uid, ids, context=context)
+ # link new records
+ self._link_users(cr, uid, ids, user_ids, context=context)
+ return True
+
+ #------------------------------------------------------
+ # Addon API
+ #------------------------------------------------------
+
+ def get_needaction_user_ids(self, cr, uid, ids, context=None):
+ return dict.fromkeys(ids, [])
+
+ def create(self, cr, uid, values, context=None):
+ if context is None:
+ context = {}
+ # perform create
+ obj_id = super(ir_needaction, self).create(cr, uid, values, context=context)
+ # link user_ids
+ needaction_user_ids = self.get_needaction_user_ids(cr, uid, [obj_id], context=context)
+ self._link_users(cr, uid, [obj_id], needaction_user_ids[obj_id], context=context)
+ return obj_id
+
+ def write(self, cr, uid, ids, values, context=None):
+ if context is None:
+ context = {}
+ # perform write
+ write_res = super(ir_needaction, self).write(cr, uid, ids, values, context=context)
+ # get and update user_ids
+ needaction_user_ids = self.get_needaction_user_ids(cr, uid, ids, context=context)
+ for id in ids:
+ self._update_users(cr, uid, [id], needaction_user_ids[id], context=context)
+ return write_res
+
+ def unlink(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ # unlink user_ids
+ self._unlink_users(cr, uid, ids, context=context)
+ # perform unlink
+ return super(ir_needaction, self).unlink(cr, uid, ids, context=context)
+
+ #------------------------------------------------------
+ # Need action API
+ #------------------------------------------------------
+
+ @classmethod
+ def needaction_get_record_ids(cls, cr, uid, model_name, user_id, limit=80, context=None):
+ """Given a model and a user_id
+ get the number of actions it has to perform"""
+ if context is None:
+ context = {}
+ need_act_obj = pooler.get_pool(cr.dbname).get('ir.needaction_users_rel')
+ need_act_ids = need_act_obj.search(cr, uid, [('res_model', '=', model_name), ('user_id', '=', user_id)], limit=limit, context=context)
+ return map(itemgetter('res_id'), need_act_obj.read(cr, uid, need_act_ids, context=context))
+
+ @classmethod
+ def needaction_get_action_count(cls, cr, uid, model_name, user_id, limit=80, context=None):
+ """Given a model and a user_id
+ get the number of actions it has to perform"""
+ if context is None:
+ context = {}
+ need_act_obj = pooler.get_pool(cr.dbname).get('ir.needaction_users_rel')
+ return need_act_obj.search(cr, uid, [('res_model', '=', model_name), ('user_id', '=', user_id)], limit=limit, count=True, context=context)
+
+ @classmethod
+ def needaction_get_record_references(cls, cr, uid, user_id, offset=None, limit=None, order=None, context=None):
+ """For a given user_id, get all the records that asks this user to
+ perform an action. Records are given as references, a list of
+ tuples (model_name, record_id).
+ This method is trans-model."""
+ if context is None:
+ context = {}
+ need_act_obj = pooler.get_pool(cr.dbname).get('ir.needaction_users_rel')
+ need_act_ids = need_act_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context)
+ need_acts = need_act_obj.read(cr, uid, need_act_ids, context=context)
+ return map(itemgetter('res_model', 'id'), need_acts)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'openerp/addons/base/ir/ir_ui_menu.py'
--- openerp/addons/base/ir/ir_ui_menu.py 2012-02-10 08:26:37 +0000
+++ openerp/addons/base/ir/ir_ui_menu.py 2012-04-03 07:57:19 +0000
@@ -255,6 +255,21 @@
return res
+ def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
+ if context is None:
+ context = {}
+ res = dict.fromkeys(ids)
+ for menu in self.browse(cr, uid, ids, context=context):
+ res[menu.id] = {}
+ if menu.action and menu.action.type == 'ir.actions.act_window' and menu.action.res_model:
+ menu_needaction_res = osv.osv.get_needaction_info(cr, uid, menu.action.res_model, uid, domain=menu.action.domain, context=context)
+ else:
+ menu_needaction_res = [False, 0, ()]
+ res[menu.id]['needaction_enabled'] = menu_needaction_res[0]
+ res[menu.id]['needaction_counter'] = menu_needaction_res[1]
+ res[menu.id]['needaction_record_ids'] = menu_needaction_res[2]
+ return res
+
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence'),
@@ -271,6 +286,9 @@
'web_icon_hover':fields.char('Web Icon File (hover)', size=128),
'web_icon_data': fields.function(_get_image_icon, string='Web Icon Image', type='binary', readonly=True, store=True, multi='icon'),
'web_icon_hover_data':fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'),
+ 'needaction_enabled': fields.function(_get_needaction, string='Target model uses the need action mechanism', type='boolean', help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.', multi='_get_needaction'),
+ 'needaction_counter': fields.function(_get_needaction, string='Number of actions the user has to perform', type='integer', help='If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.', multi='_get_needaction'),
+ 'needaction_record_ids': fields.function(_get_needaction, string='Ids of records requesting an action from the user', type='many2many', help='If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action.', multi='_get_needaction'),
'action': fields.function(_action, fnct_inv=_action_inv,
type='reference', string='Action',
selection=[
=== modified file 'openerp/addons/base/security/ir.model.access.csv'
--- openerp/addons/base/security/ir.model.access.csv 2012-02-21 11:31:58 +0000
+++ openerp/addons/base/security/ir.model.access.csv 2012-04-03 07:57:19 +0000
@@ -122,3 +122,4 @@
"access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0
"access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1
"access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0
+"access_ir_needaction_users_rel","ir_needaction_users_rel all","model_ir_needaction_users_rel",,1,1,1,1
=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py 2012-03-31 01:03:35 +0000
+++ openerp/osv/orm.py 2012-04-03 07:57:19 +0000
@@ -57,6 +57,7 @@
import fields
import openerp
import openerp.netsvc as netsvc
+import openerp.pooler as pooler
import openerp.tools as tools
from openerp.tools.config import config
from openerp.tools.safe_eval import safe_eval as eval
@@ -721,6 +722,36 @@
context=context
)
+ @staticmethod
+ def get_needaction_info(cr, uid, model_name, user_id, limit=None, order=None, domain=False, context=None):
+ """Base method for needaction mechanism
+ - see ir.needaction for actual implementation
+ - if the model uses the need action mechanism
+ (hasattr(model_obj, 'needaction_get_record_ids')):
+ - get the record ids on which the user has actions to perform
+ - evaluate the menu domain
+ - compose a new domain: menu domain, limited to ids of
+ records requesting an action
+ - count the number of records maching that domain, that
+ is the number of actions the user has to perform
+ - this method returns default values
+ :param: model_name: the name of the model (ex: hr.holidays)
+ :param: user_id: the id of user
+ :return: [uses_needaction=True/False, needaction_uid_ctr=%d]
+ """
+ model_obj = pooler.get_pool(cr.dbname).get(model_name)
+ if hasattr(model_obj, 'needaction_get_record_ids'):
+ ids = model_obj.needaction_get_record_ids(cr, uid, model_name, user_id, limit=8096, context=context)
+ if not ids:
+ return (True, 0, [])
+ if domain:
+ new_domain = eval(domain) + [('id', 'in', ids)]
+ else:
+ new_domain = [('id', 'in', ids)]
+ return (True, model_obj.search(cr, uid, new_domain, limit=limit, order=order, count=True), ids)
+ else:
+ return (False, 0, [])
+
def view_init(self, cr, uid, fields_list, context=None):
"""Override this method to do specific things when a view on the object is opened."""
pass
_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help : https://help.launchpad.net/ListHelp