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

Reply via email to