[IMP] need action & mails
bzr revid: fp@openerp.com-20120819145928-5bw5lo6dn4ni4gl7
This commit is contained in:
parent
e1cd328909
commit
03f4b992ad
|
@ -556,30 +556,6 @@
|
||||||
|
|
||||||
<menuitem action="ir_action_wizard" id="menu_ir_action_wizard" parent="base.next_id_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 -->
|
<!-- View -->
|
||||||
<record id="view_view_form" model="ir.ui.view">
|
<record id="view_view_form" model="ir.ui.view">
|
||||||
<field name="name">ir.ui.view</field>
|
<field name="name">ir.ui.view</field>
|
||||||
|
|
|
@ -19,180 +19,49 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import openerp.pooler as pooler
|
|
||||||
from operator import itemgetter
|
|
||||||
from osv import osv, fields
|
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):
|
class ir_needaction_mixin(osv.Model):
|
||||||
'''Mixin class for objects using the need action feature.
|
'''Mixin class for objects using the need action feature.
|
||||||
|
|
||||||
Need action feature can be used by objects having to be able to
|
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
|
signal that an action is required on a particular record. If in
|
||||||
the business logic an action must be performed by somebody, for
|
the business logic an action must be performed by somebody, for
|
||||||
instance validation by a manager, this mechanism allows to set a
|
instance validation by a manager, this mechanism allows to set a
|
||||||
list of users asked to perform an action.
|
list of users asked to perform an action.
|
||||||
|
|
||||||
This class wraps a class (ir.ir_needaction_users_rel) that
|
Objects using the 'need_action' feature should override the
|
||||||
behaves like a many2many field. This class handles the low-level
|
``needaction_domain_get`` method. This methods returns a
|
||||||
considerations of updating relationships. Every change made on
|
domain to filter records requiring an action for a specific user.
|
||||||
the record calls a method that updates the relationships.
|
|
||||||
|
This class also offers several global services:
|
||||||
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)
|
|
||||||
- ``needaction_get_action_count``: as ``needaction_get_record_ids``
|
- ``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)
|
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'
|
_name = 'ir.needaction_mixin'
|
||||||
_description = 'Need action mixin'
|
_needaction = True
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
# Addon API
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def get_needaction_user_ids(self, cr, uid, ids, context=None):
|
def _needaction_domain_get(self, cr, uid, context=None):
|
||||||
""" Returns the user_ids that have to perform an action
|
""" Returns the domain to filter records that require an action
|
||||||
:return: dict { record_id: [user_ids], }
|
:return: domain or False is no need action
|
||||||
"""
|
"""
|
||||||
return dict((id,list()) for id in ids)
|
return False
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
# "Need action" API
|
# "Need action" API
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def needaction_get_record_ids(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
|
|
||||||
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):
|
|
||||||
"""Given the current model and a user_id
|
"""Given the current model and a user_id
|
||||||
get the number of actions it has to perform"""
|
get the number of actions it has to perform"""
|
||||||
rel_obj = self.pool.get('ir.needaction_users_rel')
|
dom = self._needaction_domain_get(cr, uid, context=context)
|
||||||
return rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, 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:
|
|
||||||
|
|
|
@ -255,23 +255,24 @@ class ir_ui_menu(osv.osv):
|
||||||
|
|
||||||
return res
|
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):
|
def _get_needaction(self, cr, uid, ids, field_names, args, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
res = {}
|
res = {}
|
||||||
for menu in self.browse(cr, uid, ids, context=context):
|
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] = {}
|
res[menu.id] = {}
|
||||||
if menu.action and menu.action.type == 'ir.actions.act_window' and menu.action.res_model:
|
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)
|
obj = self.pool.get(menu.action.res_model)
|
||||||
else:
|
if obj._needaction:
|
||||||
menu_needaction_res = [False, 0]
|
res[menu.id]['needaction_enabled'] = obj._needaction
|
||||||
res[menu.id]['needaction_enabled'] = menu_needaction_res[0]
|
# check domain and context: should we evaluate the domain ?
|
||||||
res[menu.id]['needaction_counter'] = menu_needaction_res[1]
|
# and add context of the action ?
|
||||||
|
res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, menu.action.domain, context=context)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char('Menu', size=64, required=True, translate=True),
|
'name': fields.char('Menu', size=64, required=True, translate=True),
|
||||||
'sequence': fields.integer('Sequence'),
|
'sequence': fields.integer('Sequence'),
|
||||||
|
|
|
@ -118,6 +118,5 @@
|
||||||
"access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0
|
"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_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_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
|
"access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1
|
||||||
|
|
||||||
|
|
|
|
@ -514,7 +514,10 @@ class one2many(_column):
|
||||||
for id in ids:
|
for id in ids:
|
||||||
res[id] = []
|
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'):
|
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:
|
if r[self._fields_id] in res:
|
||||||
res[r[self._fields_id]].append(r['id'])
|
res[r[self._fields_id]].append(r['id'])
|
||||||
|
@ -556,7 +559,10 @@ class one2many(_column):
|
||||||
reverse_rel = obj._all_columns.get(self._fields_id)
|
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'
|
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
|
# 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)
|
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,
|
# 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.
|
# otherwise we only nullify the reverse foreign key column.
|
||||||
|
@ -574,7 +580,10 @@ class one2many(_column):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):
|
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
|
@classmethod
|
||||||
|
|
|
@ -666,6 +666,7 @@ class BaseModel(object):
|
||||||
_order = 'id'
|
_order = 'id'
|
||||||
_sequence = None
|
_sequence = None
|
||||||
_description = None
|
_description = None
|
||||||
|
_needaction = False
|
||||||
|
|
||||||
# dict of {field:method}, with method returning the name_get of records
|
# dict of {field:method}, with method returning the name_get of records
|
||||||
# to include in the _read_group, if grouped on this field
|
# to include in the _read_group, if grouped on this field
|
||||||
|
@ -4960,37 +4961,7 @@ class BaseModel(object):
|
||||||
# backwards compatibility
|
# backwards compatibility
|
||||||
get_xml_id = get_external_id
|
get_xml_id = get_external_id
|
||||||
_get_xml_ids = _get_external_ids
|
_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
|
# Transience
|
||||||
def is_transient(self):
|
def is_transient(self):
|
||||||
""" Return whether the model is transient.
|
""" Return whether the model is transient.
|
||||||
|
|
Loading…
Reference in New Issue