[IMP] need action & mails

bzr revid: fp@openerp.com-20120819145928-5bw5lo6dn4ni4gl7
This commit is contained in:
Fabien Pinckaers 2012-08-19 16:59:28 +02:00
parent e1cd328909
commit 03f4b992ad
6 changed files with 50 additions and 225 deletions

View File

@ -556,30 +556,6 @@
<menuitem action="ir_action_wizard" id="menu_ir_action_wizard" parent="base.next_id_6"/>
<!-- Needaction mechanism -->
<record model="ir.ui.view" id="view_ir_needaction_users_rel_tree">
<field name="name">ir.needaction_users_rel.tree</field>
<field name="model">ir.needaction_users_rel</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_no_one"/>
<!-- View -->
<record id="view_view_form" model="ir.ui.view">
<field name="name">ir.ui.view</field>

View File

@ -19,180 +19,49 @@
#
##############################################################################
import openerp.pooler as pooler
from operator import itemgetter
from osv import osv, fields
from tools.translate import _
class ir_needaction_users_rel(osv.Model):
''' ir_needaction_users_rel holds data related to the needaction
mechanism inside OpenERP. A row in this model 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
This model can be seen as a many2many, linking (res_model, res_id) to users
(those whose attention is required on the record). '''
_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),
}
def _get_users(self, cr, uid, res_ids, res_model, context=None):
"""Given res_ids of res_model, get user_ids present in table"""
rel_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context)
return list(set(map(itemgetter('user_id'), self.read(cr, uid, rel_ids, ['user_id'], context=context))))
def create_users(self, cr, uid, res_ids, res_model, user_ids, context=None):
"""Given res_ids of res_model, add user_ids to the relationship table"""
for res_id in res_ids:
for user_id in user_ids:
self.create(cr, uid, {'res_model': res_model, 'res_id': res_id, 'user_id': user_id}, context=context)
return True
def unlink_users(self, cr, uid, res_ids, res_model, context=None):
"""Given res_ids of res_model, delete all entries in the relationship table"""
to_del_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context)
return self.unlink(cr, uid, to_del_ids, context=context)
def update_users(self, cr, uid, res_ids, res_model, user_ids, context=None):
"""Given res_ids of res_model, update their entries in the relationship table to user_ids"""
# read current records
cur_users = self._get_users(cr, uid, res_ids, res_model, context=context)
if len(cur_users) == len(user_ids) and all(cur_user in user_ids for cur_user in cur_users):
return True
# unlink old records
self.unlink_users(cr, uid, res_ids, res_model, context=context)
# link new records
self.create_users(cr, uid, res_ids, res_model, user_ids, context=context)
return True
class ir_needaction_mixin(osv.Model):
'''Mixin class for objects using the need action feature.
Need action feature can be used by objects having 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
Need action feature can be used by objects having 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 class (ir.ir_needaction_users_rel) that
behaves like a many2many field. This class handles 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 the current model 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)
Objects using the 'need_action' feature should override the
``needaction_domain_get`` method. This methods returns a
domain to filter records requiring an action for a specific user.
This class also offers several global services:
- ``needaction_get_action_count``: as ``needaction_get_record_ids``
but returns only the number of action, not the ids (performs a
but returns only the number of action, not the ids (performs a
search with count=True)
'''
The ``ir_needaction_mixin`` class adds a calculated field
``needaction_pending``. This function field allows to state
whether a given record has a needaction for the current user.
This is usefull if you want to customize views according to the
needaction feature. For example, you may want to set records in
bold in a list view if the current user has an action to perform
on the record. '''
_name = 'ir.needaction_mixin'
_description = 'Need action mixin'
def get_needaction_pending(self, cr, uid, ids, name, arg, context=None):
res = {}
needaction_user_ids = self.get_needaction_user_ids(cr, uid, ids, context=context)
for id in ids:
res[id] = uid in needaction_user_ids[id]
return res
_needaction = True
def search_needaction_pending(self, cr, uid, self_again, field_name, criterion, context=None):
ids = self.needaction_get_record_ids(
cr, uid, uid, limit=1024, context=context)
return [('id', 'in', ids)]
_columns = {
'needaction_pending': fields.function(
get_needaction_pending, type='boolean',
fnct_search=search_needaction_pending,
string='Need action pending',
help="If True, this field states that users have to perform an " \
"action This field comes from the ir.needaction_mixin class."),
}
#------------------------------------------------------
# Addon API
#------------------------------------------------------
def get_needaction_user_ids(self, cr, uid, ids, context=None):
""" Returns the user_ids that have to perform an action
:return: dict { record_id: [user_ids], }
def _needaction_domain_get(self, cr, uid, context=None):
""" Returns the domain to filter records that require an action
:return: domain or False is no need action
"""
return dict((id,list()) for id in ids)
def create(self, cr, uid, values, context=None):
rel_obj = self.pool.get('ir.needaction_users_rel')
# perform create
obj_id = super(ir_needaction_mixin, self).create(cr, uid, values, context=context)
# link user_ids
needaction_user_ids = self.get_needaction_user_ids(cr, uid, [obj_id], context=context)
rel_obj.create_users(cr, uid, [obj_id], self._name, needaction_user_ids[obj_id], context=context)
return obj_id
def write(self, cr, uid, ids, values, context=None):
rel_obj = self.pool.get('ir.needaction_users_rel')
# perform write
write_res = super(ir_needaction_mixin, 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:
rel_obj.update_users(cr, uid, [id], self._name, needaction_user_ids[id], context=context)
return write_res
def unlink(self, cr, uid, ids, context=None):
# unlink user_ids
rel_obj = self.pool.get('ir.needaction_users_rel')
rel_obj.unlink_users(cr, uid, ids, self._name, context=context)
# perform unlink
return super(ir_needaction_mixin, self).unlink(cr, uid, ids, context=context)
return False
#------------------------------------------------------
# "Need action" API
#------------------------------------------------------
def needaction_get_record_ids(self, cr, uid, user_id, limit=80, context=None):
"""Given the current model and a user_id
return the record ids that require the user to perform an
action"""
rel_obj = self.pool.get('ir.needaction_users_rel')
rel_ids = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context)
return map(itemgetter('res_id'), rel_obj.read(cr, uid, rel_ids, ['res_id'], context=context))
def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None):
def _needaction_count(self, cr, uid, domain=[], context=None):
"""Given the current model and a user_id
get the number of actions it has to perform"""
rel_obj = self.pool.get('ir.needaction_users_rel')
return rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context)
dom = self._needaction_domain_get(cr, uid, context=context)
if dom is False:
return 0
return self.search(cr, uid, domain+dom, context=context, count=True)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -255,23 +255,24 @@ class ir_ui_menu(osv.osv):
return res
def _get_needaction_info(self, cr, uid, id, domain=[], context={}):
return [False, 0]
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
if context is None:
context = {}
res = {}
for menu in self.browse(cr, uid, ids, context=context):
res[menu.id] = {}
res[menu.id]['needaction_enabled'] = False
res[menu.id]['needaction_counter'] = False
res[menu.id] = {}
if menu.action and menu.action.type == 'ir.actions.act_window' and menu.action.res_model:
menu_needaction_res = self.pool.get(menu.action.res_model)._get_needaction_info(cr, uid, 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]
obj = self.pool.get(menu.action.res_model)
if obj._needaction:
res[menu.id]['needaction_enabled'] = obj._needaction
# check domain and context: should we evaluate the domain ?
# and add context of the action ?
res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, menu.action.domain, context=context)
return res
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'sequence': fields.integer('Sequence'),

View File

@ -118,6 +118,5 @@
"access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
"access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0
"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","model_ir_needaction_users_rel",,1,1,1,1
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
118 access_ir_config_parameter ir_config_parameter model_ir_config_parameter 1 0 0 0
119 access_ir_mail_server_all ir_mail_server model_ir_mail_server 1 0 0 0
120 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 model_ir_needaction_users_rel 1 1 1 1
121 access_ir_needaction_mixin ir_needaction_mixin model_ir_needaction_mixin 1 1 1 1
122

View File

@ -514,7 +514,10 @@ class one2many(_column):
for id in ids:
res[id] = []
ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
dom = self._domain
if isinstance(self._domain, type(lambda: None)):
dom = self._domain(obj)
ids2 = obj.pool.get(self._obj).search(cr, user, dom + [(self._fields_id, 'in', ids)], limit=self._limit, context=context)
for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'):
if r[self._fields_id] in res:
res[r[self._fields_id]].append(r['id'])
@ -556,7 +559,10 @@ class one2many(_column):
reverse_rel = obj._all_columns.get(self._fields_id)
assert reverse_rel, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the o2m has a static domain we must respect it when unlinking
extra_domain = self._domain if isinstance(getattr(self, '_domain', None), list) else []
dom = self._domain
if isinstance(self._domain, type(lambda: None)):
dom = self._domain(obj)
extra_domain = dom or []
ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
# If the model has cascade deletion, we delete the rows because it is the intended behavior,
# otherwise we only nullify the reverse foreign key column.
@ -574,7 +580,10 @@ class one2many(_column):
return result
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
return obj.pool.get(self._obj).name_search(cr, uid, value, self._domain, operator, context=context,limit=limit)
dom = self._domain
if isinstance(self._domain, type(lambda: None)):
dom = self._domain(obj)
return obj.pool.get(self._obj).name_search(cr, uid, value, dom, operator, context=context,limit=limit)
@classmethod

View File

@ -666,6 +666,7 @@ class BaseModel(object):
_order = 'id'
_sequence = None
_description = None
_needaction = False
# dict of {field:method}, with method returning the name_get of records
# to include in the _read_group, if grouped on this field
@ -4960,37 +4961,7 @@ class BaseModel(object):
# backwards compatibility
get_xml_id = get_external_id
_get_xml_ids = _get_external_ids
def _get_needaction_info(self, cr, uid, 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]
"""
if hasattr(self, 'needaction_get_record_ids'):
# Arbitrary limit, but still much lower thant infinity, to avoid
# getting too much data.
ids = self.needaction_get_record_ids(cr, uid, user_id, limit=8192, context=context)
if not ids:
return [True, 0]
if domain:
new_domain = eval(domain, locals_dict={'uid': user_id}) + [('id', 'in', ids)]
else:
new_domain = [('id', 'in', ids)]
return [True, self.search(cr, uid, new_domain, limit=limit, order=order, count=True, context=context)]
else:
return [False, 0]
# Transience
def is_transient(self):
""" Return whether the model is transient.