From c02116b41f9c500828c54725c11541ddcd1c5dfd Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 14 Aug 2012 21:44:12 +0200 Subject: [PATCH 1/5] [IMP] cleaning of need action bzr revid: fp@openerp.com-20120814194412-2bcujivyd45buikx --- openerp/addons/base/ir/ir.xml | 24 --- openerp/addons/base/ir/ir_needaction.py | 182 +++--------------- openerp/addons/base/ir/ir_ui_menu.py | 18 +- .../addons/base/security/ir.model.access.csv | 1 - openerp/osv/orm.py | 33 +--- 5 files changed, 36 insertions(+), 222 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index fac2946e204..c554a8896aa 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -556,30 +556,6 @@ - - - ir.needaction_users_rel.tree - ir.needaction_users_rel - 10 - - - - - - - - - - - Need action relationships - ir.needaction_users_rel - form - tree,form - - - - - ir.ui.view diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 0f28c33ded9..95fc6d81b09 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -19,180 +19,48 @@ # ############################################################################## -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 + _description = 'Need Action Mixin' + _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) + return self.search(cr, uid, domain+dom, context=context, count=True) -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 77c0c4d738a..1bb517c570f 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -255,23 +255,23 @@ 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]['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_active: + res[menu.id]['needaction_enabled'] = obj._needaction_active + # 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'), diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index d49aa18db8c..b0218e977bc 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -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 diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7c8085ea8ed..368a37f455a 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -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. From 560e9e7fd259c4da0285d3730ec72c1f462b755a Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 14 Aug 2012 21:58:40 +0200 Subject: [PATCH 2/5] [IMP] put needaction_pending again, new implementation bzr revid: fp@openerp.com-20120814195840-yaha9r3qvj8o13zr --- openerp/addons/base/ir/ir_needaction.py | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 95fc6d81b09..5552b42052e 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -38,12 +38,39 @@ class ir_needaction_mixin(osv.Model): - ``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) + + 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' _needaction = True + def get_needaction_pending(self, cr, uid, ids, name, arg, context=None): + dom = self._needaction_domain_get(cr, uid, context=context) + result = dict.fromkeys(ids, False) + ids2 = self.search(cr, uid, [('id','in',ids)+dom], context=context) + for idna in ids2: + result[idna] = True + return result + + def search_needaction_pending(self, cr, uid, field, field_name, criterion, context=None): + return self._needaction_domain_get(cr, uid, context=context) + + _columns = { + 'needaction_pending': fields.function( + get_needaction_pending, type='boolean', + fnct_search=search_needaction_pending, + string='Need an Action', + help="Do the current user requires to perform an action."), + } + #------------------------------------------------------ # Addon API #------------------------------------------------------ From 1353759beca8ada3d739f9c57117255c9909e25d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 15 Aug 2012 15:37:10 +0200 Subject: [PATCH 3/5] [IMP] dynamic domains on O2M, remove needaction_pending bzr revid: fp@openerp.com-20120815133710-138x0d52qqcd8ex2 --- openerp/addons/base/ir/ir_needaction.py | 30 ++----------------------- openerp/addons/base/ir/ir_ui_menu.py | 5 +++-- openerp/osv/fields.py | 15 ++++++++++--- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 5552b42052e..9112d60c1f4 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -38,39 +38,11 @@ class ir_needaction_mixin(osv.Model): - ``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) - - 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' _needaction = True - def get_needaction_pending(self, cr, uid, ids, name, arg, context=None): - dom = self._needaction_domain_get(cr, uid, context=context) - result = dict.fromkeys(ids, False) - ids2 = self.search(cr, uid, [('id','in',ids)+dom], context=context) - for idna in ids2: - result[idna] = True - return result - - def search_needaction_pending(self, cr, uid, field, field_name, criterion, context=None): - return self._needaction_domain_get(cr, uid, context=context) - - _columns = { - 'needaction_pending': fields.function( - get_needaction_pending, type='boolean', - fnct_search=search_needaction_pending, - string='Need an Action', - help="Do the current user requires to perform an action."), - } - #------------------------------------------------------ # Addon API #------------------------------------------------------ @@ -89,5 +61,7 @@ class ir_needaction_mixin(osv.Model): """Given the current model and a user_id get the number of actions it has to perform""" 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) diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 1bb517c570f..f5e4ee2ba05 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -260,13 +260,14 @@ class ir_ui_menu(osv.osv): 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: obj = self.pool.get(menu.action.res_model) - if obj._needaction_active: - res[menu.id]['needaction_enabled'] = obj._needaction_active + 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) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index d6a5f4f63d8..fd23c246a16 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -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 From b9bdf2c29a048116304f18a95eb2e8c912f3100a Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 16 Aug 2012 18:42:39 +0200 Subject: [PATCH 4/5] [IMP] tools.email_split bzr revid: fp@tinyerp.com-20120816164239-yl1vzp83w6z5xi5i --- openerp/tools/misc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index e90fadf0492..7f2dade30d8 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -1113,6 +1113,12 @@ class unquote(str): def __repr__(self): return self +def emails_split(text): + """Return a list of the email addresses found in ``text``""" + if not text: return [] + return re.findall(r'([^ ,<@]+@[^> ,]+)', text) + + class UnquoteEvalContext(defaultdict): """Defaultdict-based evaluation context that returns an ``unquote`` string for any missing name used during From 07c5ee86d67a573d220803640c73b273456a233d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 17 Aug 2012 16:49:49 +0200 Subject: [PATCH 5/5] fix bzr revid: fp@tinyerp.com-20120817144949-wrqo086h3dx6f3kx --- openerp/addons/base/ir/ir_needaction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 9112d60c1f4..e40a3030eb8 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -63,5 +63,6 @@ class ir_needaction_mixin(osv.Model): 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) + print domain, dom + return self.search(cr, uid, (domain or []) +dom, context=context, count=True)