From c02116b41f9c500828c54725c11541ddcd1c5dfd Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 14 Aug 2012 21:44:12 +0200 Subject: [PATCH 01/37] [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 02/37] [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 03/37] [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 04/37] [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 05/37] 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) From 03f4b992ad57d5a52d4ac8229bab95ba94808969 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Sun, 19 Aug 2012 16:59:28 +0200 Subject: [PATCH 06/37] [IMP] need action & mails bzr revid: fp@openerp.com-20120819145928-5bw5lo6dn4ni4gl7 --- openerp/addons/base/ir/ir.xml | 24 --- openerp/addons/base/ir/ir_needaction.py | 183 +++--------------- openerp/addons/base/ir/ir_ui_menu.py | 19 +- .../addons/base/security/ir.model.access.csv | 1 - openerp/osv/fields.py | 15 +- openerp/osv/orm.py | 33 +--- 6 files changed, 50 insertions(+), 225 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..9112d60c1f4 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -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: diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 77c0c4d738a..f5e4ee2ba05 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -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'), 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/fields.py b/openerp/osv/fields.py index 5a47601f8a5..da025845759 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 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 6021b4473aa0b5cdf436e61320ffcba503de53f3 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 12:52:52 +0200 Subject: [PATCH 07/37] [IMP] message_read on mail.message bzr revid: fp@tinyerp.com-20120820105252-o1eqy5yfqb66hga9 --- openerp/osv/fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index da025845759..496aab5407b 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -517,6 +517,7 @@ class one2many(_column): dom = self._domain if isinstance(self._domain, type(lambda: None)): dom = self._domain(obj) + print self._obj, user, dom + [(self._fields_id, 'in', ids)] 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: From a054299277113f1ae09f47b0cd52f9752ef10a82 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 13:04:55 +0200 Subject: [PATCH 08/37] imp bzr revid: fp@tinyerp.com-20120820110455-jelu0xl7jil36q18 --- openerp/osv/fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 496aab5407b..da025845759 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -517,7 +517,6 @@ class one2many(_column): dom = self._domain if isinstance(self._domain, type(lambda: None)): dom = self._domain(obj) - print self._obj, user, dom + [(self._fields_id, 'in', ids)] 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: From 25233cd5137255f707f6830f720b2ca76f6567aa Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 13:32:27 +0200 Subject: [PATCH 09/37] [IMP] add user_ids on partners bzr revid: fp@tinyerp.com-20120820113227-5k9d7m41vjcof26c --- openerp/addons/base/res/res_partner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 93d12228ebe..d9ae1d8595e 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -215,6 +215,7 @@ class res_partner(osv.osv): "Use this field anywhere a small image is required."), 'company_id': fields.many2one('res.company', 'Company', select=1), 'color': fields.integer('Color Index'), + 'user_ids': fields.one2many('res.users', 'partner_id', 'Users'), 'contact_address': fields.function(_address_display, type='char', string='Complete Address'), } From 0d5a185956cefd92b4b2825d0cb78e27376bd5a2 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 15:17:26 +0200 Subject: [PATCH 10/37] fix bzr revid: fp@tinyerp.com-20120820131726-7iassml4p93z7qsg --- openerp/osv/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index da025845759..4e5d8e97691 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1553,7 +1553,7 @@ def field_to_dict(model, cr, user, field, context=None): # when client is started in debug mode. if isinstance(field, function): res['function'] = field._fnct and field._fnct.func_name or False - res['store'] = field.store + # res['store'] = field.store if isinstance(field.store, dict): res['store'] = str(field.store) res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False From 1bd841d10109ddcaebfde5bbf79a1b24fa6ac82f Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 15:19:21 +0200 Subject: [PATCH 11/37] fix bzr revid: fp@tinyerp.com-20120820131921-7ydvs9x784t4hn4l --- openerp/osv/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 4e5d8e97691..57c84d6a5a8 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1554,8 +1554,8 @@ def field_to_dict(model, cr, user, field, context=None): if isinstance(field, function): res['function'] = field._fnct and field._fnct.func_name or False # res['store'] = field.store - if isinstance(field.store, dict): - res['store'] = str(field.store) + # if isinstance(field.store, dict): + # res['store'] = str(field.store) res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False res['fnct_inv_arg'] = field._fnct_inv_arg or False From 64dc05afc658e318aaa3724827daca0b6a8d2a14 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 15:25:01 +0200 Subject: [PATCH 12/37] fix bzr revid: fp@tinyerp.com-20120820132501-7724jwavhzan3xyp --- openerp/osv/fields.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 57c84d6a5a8..b996d66035f 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1585,6 +1585,9 @@ def field_to_dict(model, cr, user, field, context=None): res['selection'] = field.selection(model, cr, user, context) if res['type'] in ('one2many', 'many2many', 'many2one'): res['relation'] = field._obj + dom = field._domain + if isinstance(field._domain, type(lambda: None)): + dom = field._domain(model) res['domain'] = field._domain res['context'] = field._context From f21f1a68aace85dca8deef197a0d2f46d77d2146 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 20 Aug 2012 15:29:09 +0200 Subject: [PATCH 13/37] fix bzr revid: fp@tinyerp.com-20120820132909-iw8faaon2z4c5e77 --- openerp/osv/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index b996d66035f..27a3f2d8814 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -1588,7 +1588,7 @@ def field_to_dict(model, cr, user, field, context=None): dom = field._domain if isinstance(field._domain, type(lambda: None)): dom = field._domain(model) - res['domain'] = field._domain + res['domain'] = dom res['context'] = field._context if isinstance(field, one2many): From b772da168f7e8e6e6539ac4e867dc1a2e25b7a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 21 Aug 2012 17:14:13 +0200 Subject: [PATCH 14/37] [FIX] tools: emails_split -> email-split. bzr revid: tde@openerp.com-20120821151413-9hxmsyp51q3ny32f --- openerp/tools/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 7f2dade30d8..8ca90e32f07 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -1113,7 +1113,7 @@ class unquote(str): def __repr__(self): return self -def emails_split(text): +def email_split(text): """Return a list of the email addresses found in ``text``""" if not text: return [] return re.findall(r'([^ ,<@]+@[^> ,]+)', text) From d876320e12b80a229bc97b3d2df35858f6812521 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 21 Aug 2012 17:45:55 +0200 Subject: [PATCH 15/37] [FIX] allows using uid in domains of params bzr revid: fp@tinyerp.com-20120821154555-gjsw2pr5ykbl0ed5 --- openerp/addons/base/ir/ir_actions.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index a5d04d7f1bc..28a309f427a 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -860,11 +860,10 @@ class act_client(osv.osv): _order = 'name' def _get_params(self, cr, uid, ids, field_name, arg, context): - return dict([ - ((record.id, ast.literal_eval(record.params_store)) - if record.params_store else (record.id, False)) - for record in self.browse(cr, uid, ids, context=context) - ]) + result = {} + for record in self.browse(cr, uid, ids, context=context): + result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False + return result def _set_params(self, cr, uid, id, field_name, field_value, arg, context): if isinstance(field_value, dict): From e88e21bb569d9ce086bf03285aa4141dc28f0c58 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 21 Aug 2012 19:24:14 +0200 Subject: [PATCH 16/37] fix bzr revid: fp@tinyerp.com-20120821172414-7kz7qcibjile1dfl --- openerp/addons/base/res/res_partner_view.xml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 43a37d1a79c..6e2e8fac178 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -186,13 +186,10 @@

-
- - - - - - + + From 4215827f24fa8909777387b8be55ff90755b5e40 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Tue, 21 Aug 2012 20:04:09 +0200 Subject: [PATCH 17/37] [IMP] better implementation of name_create bzr revid: fp@tinyerp.com-20120821180409-gk2c3odfve53epdp --- openerp/addons/base/res/res_partner.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index d9ae1d8595e..30255d93ec3 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -366,22 +366,14 @@ class res_partner(osv.osv): - ([\w\s.\\-]+)[\<]([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]: Raoul Grosbedon, raoul@grosbedon.fr """ - contact_regex = re.compile('([\w\s.\\-]+)[\<]([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]') - email_regex = re.compile('([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})') - contact_regex_res = contact_regex.findall(name) - email_regex_res = email_regex.findall(name) - if contact_regex_res: - name = contact_regex_res[0][0].rstrip(' ') # remove extra spaces on the right - email = contact_regex_res[0][1] - rec_id = self.create(cr, uid, {self._rec_name: name, 'email': email}, context); - return self.name_get(cr, uid, [rec_id], context)[0] - elif email_regex_res: - email = '%s' % (email_regex_res[0]) - rec_id = self.create(cr, uid, {self._rec_name: email, 'email': email}, context); - return self.name_get(cr, uid, [rec_id], context)[0] - else: - rec_id = super(res_partner, self).create(cr, uid, {self._rec_name: name}, context) - return self.name_get(cr, uid, [rec_id], context)[0] + regex = re.compile('^(.*?)(?: ?[\<]?([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]?)?$') + result = regex.match(name) + if not result or (context.get('force_create',False) and result.group(2) is None): + raise osv.except_osv(_('Warning'), _("Couldn't create contact without email address !")) + name = result.group(1).strip() + email = result.group(2) + rec_id = self.create(cr, uid, {self._rec_name: name or email, 'email': email or False}, context=context) + return self.name_get(cr, uid, [rec_id], context)[0] def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: From e46c8e43ff588ba4a7b0d902527cdf3011efed9d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 22 Aug 2012 12:37:25 +0200 Subject: [PATCH 18/37] [IMP] Removing a bad practice: - what happens if you overwrite check_read and modules calls check_access_rights ? - 5 methods to learn to understand the API insead of one bzr revid: fp@tinyerp.com-20120822103725-vdubhkzlake2jyvo --- openerp/osv/orm.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 368a37f455a..e8f59e50cbd 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2346,7 +2346,7 @@ class BaseModel(object): def read_string(self, cr, uid, id, langs, fields=None, context=None): res = {} res2 = {} - self.pool.get('ir.translation').check_read(cr, uid) + self.pool.get('ir.translation').check_access_rights(cr, uid, 'read') if not fields: fields = self._columns.keys() + self._inherit_fields.keys() #FIXME: collect all calls to _get_source into one SQL call. @@ -2370,7 +2370,7 @@ class BaseModel(object): return res def write_string(self, cr, uid, id, langs, vals, context=None): - self.pool.get('ir.translation').check_write(cr, uid) + self.pool.get('ir.translation').check_access_rights(cr, uid, 'write') #FIXME: try to only call the translation in one SQL for lang in langs: for field in vals: @@ -2518,7 +2518,7 @@ class BaseModel(object): """ context = context or {} - self.check_read(cr, uid) + self.check_access_rights(cr, uid, 'read') if not fields: fields = self._columns.keys() @@ -3399,8 +3399,7 @@ class BaseModel(object): if context is None: context = {} - write_access = self.check_write(cr, user, False) or \ - self.check_create(cr, user, False) + write_access = self.check_access_rights(cr, user, 'write') or self.check_access_rights(cr, user, 'create') res = {} @@ -3464,7 +3463,7 @@ class BaseModel(object): if not context: context = {} - self.check_read(cr, user) + self.check_access_rights(cr, user, 'read') if not fields: fields = list(set(self._columns.keys() + self._inherit_fields.keys())) if isinstance(ids, (int, long)): @@ -3738,18 +3737,6 @@ class BaseModel(object): according to the access rights.""" return self.pool.get('ir.model.access').check(cr, uid, self._name, operation, raise_exception) - def check_create(self, cr, uid, raise_exception=True): - return self.check_access_rights(cr, uid, 'create', raise_exception) - - def check_read(self, cr, uid, raise_exception=True): - return self.check_access_rights(cr, uid, 'read', raise_exception) - - def check_unlink(self, cr, uid, raise_exception=True): - return self.check_access_rights(cr, uid, 'unlink', raise_exception) - - def check_write(self, cr, uid, raise_exception=True): - return self.check_access_rights(cr, uid, 'write', raise_exception) - def check_access_rule(self, cr, uid, ids, operation, context=None): """Verifies that the operation given by ``operation`` is allowed for the user according to ir.rules. @@ -3813,7 +3800,7 @@ class BaseModel(object): self._check_concurrency(cr, ids, context) - self.check_unlink(cr, uid) + self.check_access_rights(cr, uid, 'unlink') ir_property = self.pool.get('ir.property') @@ -3949,7 +3936,7 @@ class BaseModel(object): ids = [ids] self._check_concurrency(cr, ids, context) - self.check_write(cr, user) + self.check_access_rights(cr, user, 'write') result = self._store_get_values(cr, user, ids, vals.keys(), context) or [] @@ -4169,7 +4156,7 @@ class BaseModel(object): if self.is_transient(): self._transient_vacuum(cr, user) - self.check_create(cr, user) + self.check_access_rights(cr, user, 'create') if self._log_access: for f in LOG_ACCESS_COLUMNS: @@ -4661,7 +4648,7 @@ class BaseModel(object): """ if context is None: context = {} - self.check_read(cr, access_rights_uid or user) + self.check_access_rights(cr, access_rights_uid or user, 'read') # For transient models, restrict acces to the current user, except for the super-user if self.is_transient() and self._log_access and user != SUPERUSER_ID: From 9871b2dd370c61052844ed79bc3de20a12c03adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 22 Aug 2012 13:16:13 +0200 Subject: [PATCH 19/37] [FIX] Fixed last commit, about removing check_read/write/unlink/create: those methods were used, propagated the changes :) . bzr revid: tde@openerp.com-20120822111613-305j77rs4fphkjts --- openerp/addons/base/ir/ir_sequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_sequence.py b/openerp/addons/base/ir/ir_sequence.py index e3136b92b6e..8b03cd3f3b3 100644 --- a/openerp/addons/base/ir/ir_sequence.py +++ b/openerp/addons/base/ir/ir_sequence.py @@ -210,7 +210,7 @@ class ir_sequence(openerp.osv.osv.osv): def next_by_id(self, cr, uid, sequence_id, context=None): """ Draw an interpolated string using the specified sequence.""" - self.check_read(cr, uid) + self.check_access_rights(cr, uid, 'read') company_ids = self.pool.get('res.company').search(cr, uid, [], order='company_id', context=context) + [False] ids = self.search(cr, uid, ['&',('id','=', sequence_id),('company_id','in',company_ids)]) return self._next(cr, uid, ids, context) @@ -227,7 +227,7 @@ class ir_sequence(openerp.osv.osv.osv): sequence selection. A matching sequence for that specific company will get higher priority. """ - self.check_read(cr, uid) + self.check_access_rights(cr, uid, 'read') company_ids = self.pool.get('res.company').search(cr, uid, [], order='company_id', context=context) + [False] ids = self.search(cr, uid, ['&',('code','=', sequence_code),('company_id','in',company_ids)]) return self._next(cr, uid, ids, context) From 0f63b4a1fb8a41971d1fd6a9810a75a7af5de759 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 22 Aug 2012 15:34:06 +0200 Subject: [PATCH 20/37] [IMP] need action bzr revid: fp@tinyerp.com-20120822133406-9ayg228mxfeg16dg --- openerp/addons/base/ir/ir_actions.py | 4 +++- openerp/addons/base/ir/ir_ui_menu.py | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 28a309f427a..b70a9d89c06 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -43,7 +43,6 @@ class actions(osv.osv): _table = 'ir_actions' _order = 'name' _columns = { - 'name': fields.char('Action Name', required=True, size=64), 'type': fields.char('Action Type', required=True, size=32,readonly=True), 'usage': fields.char('Action Usage', size=32), } @@ -872,10 +871,13 @@ class act_client(osv.osv): self.write(cr, uid, id, {'params_store': field_value}, context=context) _columns = { + 'name': fields.char('Action Name', required=True, size=64, translate=True), 'tag': fields.char('Client action tag', size=64, required=True, help="An arbitrary string, interpreted by the client" " according to its own needs and wishes. There " "is no central tag repository across clients."), + 'res_model': fields.char('Destination Model', size=64, + help="Optional model, mostly used for needactions."), 'params': fields.function(_get_params, fnct_inv=_set_params, type='binary', string="Supplementary arguments", diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index f5e4ee2ba05..bc743d34fc0 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -264,12 +264,10 @@ class ir_ui_menu(osv.osv): 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: + if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model: 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 From 9d6ee746eb4e29ea7dca76ef8c836d71374d6186 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 22 Aug 2012 15:41:09 +0200 Subject: [PATCH 21/37] fix bzr revid: fp@tinyerp.com-20120822134109-97lbhcllylbe15ce --- openerp/addons/base/ir/ir_actions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index b70a9d89c06..cf67e6fcf88 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -43,6 +43,7 @@ class actions(osv.osv): _table = 'ir_actions' _order = 'name' _columns = { + 'name': fields.char('Name', size=64, required=True), 'type': fields.char('Action Type', required=True, size=32,readonly=True), 'usage': fields.char('Action Usage', size=32), } From 552705494c11c53d81671766e608f830e773df96 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 22 Aug 2012 18:14:07 +0200 Subject: [PATCH 22/37] [IMP] needaction bzr revid: fp@tinyerp.com-20120822161407-aucl1m2jsa3pe6sv --- openerp/addons/base/ir/ir_needaction.py | 3 ++- openerp/addons/base/ir/ir_ui_menu.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index caf099c18cd..1e59a6fbb8e 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 or []) +dom, context=context, count=True) + result = self.search(cr, uid, (domain or []) +dom, context=context, count=True) + return result diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index bc743d34fc0..eefa8ea4a50 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -24,6 +24,8 @@ import base64 import re import threading +from tools.safe_eval import safe_eval as eval + import tools import openerp.modules from osv import fields, osv @@ -268,7 +270,7 @@ class ir_ui_menu(osv.osv): obj = self.pool.get(menu.action.res_model) if obj._needaction: res[menu.id]['needaction_enabled'] = obj._needaction - res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, menu.action.domain, context=context) + res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, menu.action.domain and eval(menu.action.domain), context=context) return res _columns = { From 84625969c0a724e0374f21f527ce9a04339ebbee Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Thu, 23 Aug 2012 16:35:25 +0200 Subject: [PATCH 23/37] [FIX] needaction on mail.groups bzr revid: fp@tinyerp.com-20120823143525-28mnbgd015jd8yyd --- openerp/addons/base/ir/ir_actions.py | 3 +++ openerp/addons/base/ir/ir_ui_menu.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index cf67e6fcf88..6f906f5c416 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -879,6 +879,8 @@ class act_client(osv.osv): "is no central tag repository across clients."), 'res_model': fields.char('Destination Model', size=64, help="Optional model, mostly used for needactions."), + 'context': fields.char('Context Value', size=250, required=True, + help="Context dictionary as Python expression, empty by default (Default: {})"), 'params': fields.function(_get_params, fnct_inv=_set_params, type='binary', string="Supplementary arguments", @@ -888,6 +890,7 @@ class act_client(osv.osv): } _defaults = { 'type': 'ir.actions.client', + 'context': '{}', } act_client() diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index eefa8ea4a50..05523faa550 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -269,8 +269,13 @@ class ir_ui_menu(osv.osv): if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model: obj = self.pool.get(menu.action.res_model) if obj._needaction: + dom = [] + if menu.action.type=='ir.actions.act_window': + dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or [] + else: + dom = eval(menu.action.params_store or '{}', {'uid': uid}).get('domain') res[menu.id]['needaction_enabled'] = obj._needaction - res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, menu.action.domain and eval(menu.action.domain), context=context) + res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, dom, context=context) return res _columns = { From f386e2de33edd9571755275208ab640c55e097e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 27 Aug 2012 12:18:07 +0200 Subject: [PATCH 24/37] [IMP] base demo data: added a partner for demo user. bzr revid: tde@openerp.com-20120827101807-wop2m7fdfgkyh0e7 --- openerp/addons/base/base_demo.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/base_demo.xml b/openerp/addons/base/base_demo.xml index 34feb3af990..064b1f49e69 100644 --- a/openerp/addons/base/base_demo.xml +++ b/openerp/addons/base/base_demo.xml @@ -1,10 +1,15 @@ + + Demo User + + + + demo demo - Demo User Mr Demo From bc5da1a02b30f4a89edb988de0d2edb48d183b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 28 Aug 2012 09:34:14 +0200 Subject: [PATCH 25/37] [REVIEW] tools: moved email_split function in emails section, instead of 'somwhere in the file'. bzr revid: tde@openerp.com-20120828073414-lt4bpubxtv3235b6 --- openerp/tools/misc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 024d75fcae0..f609981342e 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -395,6 +395,11 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non cr.close() return res +def email_split(text): + """ Return a list of the email addresses found in ``text`` """ + if not text: return [] + return re.findall(r'([^ ,<@]+@[^> ,]+)', text) + #---------------------------------------------------------- # SMS #---------------------------------------------------------- From 2a916edbade8505201aba8b520981e9ac26873c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 28 Aug 2012 09:53:21 +0200 Subject: [PATCH 26/37] [REVIEW] ir_ui_menu: cleaned _get_needaction code. bzr revid: tde@openerp.com-20120828075321-7emtlqvh5oj6o31r --- openerp/addons/base/ir/ir_ui_menu.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 05523faa550..77e923adf48 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -23,9 +23,7 @@ import base64 import re import threading - from tools.safe_eval import safe_eval as eval - import tools import openerp.modules from osv import fields, osv @@ -258,18 +256,15 @@ class ir_ui_menu(osv.osv): return res 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] = {} + res[menu.id] = { + 'needaction_enabled': False, + 'needaction_counter': False, + } if menu.action and menu.action.type in ('ir.actions.act_window','ir.actions.client') and menu.action.res_model: obj = self.pool.get(menu.action.res_model) if obj._needaction: - dom = [] if menu.action.type=='ir.actions.act_window': dom = menu.action.domain and eval(menu.action.domain, {'uid': uid}) or [] else: From df47c52bab4ed691519f7173f32ecf11751d19d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 28 Aug 2012 09:53:55 +0200 Subject: [PATCH 27/37] [REVIEW] ir_needaction: cleaned comments, removed unused import. bzr revid: tde@openerp.com-20120828075355-iiblaa6gqio78dcj --- openerp/addons/base/ir/ir_needaction.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 1e59a6fbb8e..3bb405fcbfa 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -19,37 +19,35 @@ # ############################################################################## -from osv import osv, fields +from osv import osv 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 + Need action feature can be used by models 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 list of users asked to perform an action. - Objects using the 'need_action' feature should override the - ``needaction_domain_get`` method. This methods returns a + Models using the 'need_action' feature should override the + ``_needaction_domain_get`` method. This method 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 - search with count=True) + - ``_needaction_count``: returns the number of actions uid has to perform ''' _name = 'ir.needaction_mixin' _needaction = True #------------------------------------------------------ - # Addon API + # Addons API #------------------------------------------------------ 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: domain or False is no action """ return False @@ -58,8 +56,7 @@ class ir_needaction_mixin(osv.Model): #------------------------------------------------------ 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""" + """ Get the number of actions uid has to perform. """ dom = self._needaction_domain_get(cr, uid, context=context) if dom is False: return 0 From ec85db8e83b01111db0d3e76fc6a815d37702695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 28 Aug 2012 10:09:39 +0200 Subject: [PATCH 28/37] [REVIEW] tools: removed duplicate email_split in tools. bzr revid: tde@openerp.com-20120828080939-5bt3klbep49gjw47 --- openerp/tools/misc.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index f609981342e..e86f189e9ca 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -1118,12 +1118,6 @@ class unquote(str): def __repr__(self): return self -def email_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 de18f4a401bb8d2d2be2758fdaba3a9dd80184e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 28 Aug 2012 10:50:18 +0200 Subject: [PATCH 29/37] [CLEAN] ir_needaction: cleaned needaction_count. bzr revid: tde@openerp.com-20120828085018-z6uywvk2oeqoog06 --- openerp/addons/base/ir/ir_needaction.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 3bb405fcbfa..ba3a5c4a998 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -58,8 +58,6 @@ class ir_needaction_mixin(osv.Model): def _needaction_count(self, cr, uid, domain=[], context=None): """ Get the number of actions uid has to perform. """ dom = self._needaction_domain_get(cr, uid, context=context) - if dom is False: + if not dom: return 0 - result = self.search(cr, uid, (domain or []) +dom, context=context, count=True) - return result - + return self.search(cr, uid, (domain or []) +dom, context=context, count=True) From d37745559e951bed047bd293e293ce3087cf8f40 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 31 Aug 2012 15:23:37 +0200 Subject: [PATCH 30/37] [ADD] tools.misc: new function append_to_html, can be used to inject blocks (html or plaintext) to existing HTML snippets bzr revid: odo@openerp.com-20120831132337-of2p1wlh5l4lpvez --- openerp/tests/__init__.py | 3 ++- openerp/tests/test_misc.py | 21 +++++++++++++++++++++ openerp/tools/misc.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 openerp/tests/test_misc.py diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index d3ab62ebc1c..aa3055f471c 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -9,7 +9,7 @@ See the :ref:`test-framework` section in the :ref:`features` list. """ from . import test_expression, test_html_sanitize, test_ir_sequence, test_orm,\ - test_view_validation, test_uninstall + test_view_validation, test_uninstall, test_misc fast_suite = [ test_ir_sequence, @@ -20,6 +20,7 @@ checks = [ test_html_sanitize, test_orm, test_view_validation, + test_misc, ] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/test_misc.py b/openerp/tests/test_misc.py new file mode 100644 index 00000000000..7661f253b17 --- /dev/null +++ b/openerp/tests/test_misc.py @@ -0,0 +1,21 @@ +# This test can be run stand-alone with something like: +# > PYTHONPATH=. python2 openerp/tests/test_misc.py + +import unittest2 + +class test_misc(unittest2.TestCase): + """ Test some of our generic utility functions """ + + def test_append_to_html(self): + from openerp.tools import append_content_to_html + test_samples = [ + ('some content', '--\nYours truly', True, + 'some content\n
--\nYours truly
\n'), + ('some content', '\n\n

--

\n

Yours truly

\n\n', False, + 'some content\n\n\n

--

\n

Yours truly

\n\n\n'), + ] + for html, content, flag, expected in test_samples: + self.assertEqual(append_content_to_html(html,content,flag), expected, 'append_content_to_html is broken') + +if __name__ == '__main__': + unittest2.main() \ No newline at end of file diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index e86f189e9ca..39e86b1ec4a 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -400,6 +400,37 @@ def email_split(text): if not text: return [] return re.findall(r'([^ ,<@]+@[^> ,]+)', text) +def append_content_to_html(html, content, plaintext=True): + """Append extra content at the end of an HTML snippet, trying + to locate the end of the HTML document (, , or + EOF), and wrapping the provided content in a
 block
+       unless ``plaintext`` is False. A side-effect of this
+       method is to coerce all HTML tags to lowercase in ``html``,
+       and strips enclosing  or  tags in content if
+       ``plaintext`` is False.
+       
+       :param str html: html tagsoup (doesn't have to be XHTML)
+       :param str content: extra content to append
+       :param bool plaintext: whether content is plaintext and should
+           be wrapped in a 
 tag.
+    """
+    html = ustr(html)
+    if plaintext:
+        content = u'\n
%s
\n' % ustr(content) + else: + content = re.sub(r'(?i)(||)', '', content) + content = u'\n%s\n'% ustr(content) + # Force all tags to lowercase + html = re.sub(r'(])', + lambda m: '%s%s%s' % (m.group(1),m.group(2).lower(),m.group(3)), html) + insert_location = html.find('') + if insert_location == -1: + insert_location = html.find('') + if insert_location == -1: + return '%s%s' % (html, content) + return '%s%s%s' % (html[:insert_location], content, html[insert_location:]) + + #---------------------------------------------------------- # SMS #---------------------------------------------------------- From 0de195b893ffca38a8bbe8be015e15147377bfa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 31 Aug 2012 19:12:47 +0200 Subject: [PATCH 31/37] [IMP] res_partner: moved regex of name_create of res.partner into a dedicated method, allowing ot use it outside of the name_create. Cleaned a bit the code, updated comments to the method. bzr revid: tde@openerp.com-20120831171247-uc0babnzfk4logoa --- openerp/addons/base/res/res_partner.py | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 30255d93ec3..42ddd1d40cd 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -351,27 +351,27 @@ class res_partner(osv.osv): res.append((record.id, name)) return res + def parse_partner_name(self, cr, uid, name, context=None): + """ Supported syntax: + - 'Raoul ', available with ' ', '<>', '{}', + '()', '[]': will find name and email address + - otherwise: default, everything is set as the name """ + regex = re.compile('^(.*?)(?: ?[\<{(\[]?([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>})\]]?)?$') + result = regex.match(name) + return (result.group(1).strip(), result.group(2)) + def name_create(self, cr, uid, name, context=None): """ Override of orm's name_create method for partners. The purpose is to handle some basic formats to create partners using the name_create. - Supported syntax: - - 'raoul@grosbedon.fr': create a partner with name raoul@grosbedon.fr - and sets its email to raoul@grosbedon.fr - - 'Raoul Grosbedon ': create a partner with name - Raoul Grosbedon, and set its email to raoul@grosbedon.fr - - anything else: fall back on the default name_create - Regex : - - ([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8}): raoul@grosbedon.fr - - ([\w\s.\\-]+)[\<]([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]: - Raoul Grosbedon, raoul@grosbedon.fr - """ - regex = re.compile('^(.*?)(?: ?[\<]?([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>]?)?$') - result = regex.match(name) - if not result or (context.get('force_create',False) and result.group(2) is None): + If only an email address is received and that the regex cannot find + a name, the name will have the email value. + If 'force_email' key in context: must find the email address. """ + name, email = self.parse_partner_name(cr, uid, name, context=context) + if context.get('force_email') and not email: raise osv.except_osv(_('Warning'), _("Couldn't create contact without email address !")) - name = result.group(1).strip() - email = result.group(2) + if not name and email: + name = email rec_id = self.create(cr, uid, {self._rec_name: name or email, 'email': email or False}, context=context) return self.name_get(cr, uid, [rec_id], context)[0] From 68038627119f4a590f16caa983a0e821198d9361 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Sat, 1 Sep 2012 00:52:05 +0200 Subject: [PATCH 32/37] [IMP] res.partner: simplified+corrected parsing of partner name and email in name_create, added tests, made this internal method private bzr revid: odo@openerp.com-20120831225205-04l4udn95cz3n72o --- openerp/addons/base/res/res_partner.py | 17 ++++++++++------- openerp/addons/base/tests/__init__.py | 4 ++-- openerp/addons/base/tests/test_base.py | 26 ++++++++++++++++++++++++++ openerp/tools/misc.py | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 openerp/addons/base/tests/test_base.py diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 42ddd1d40cd..931497004ec 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -351,14 +351,17 @@ class res_partner(osv.osv): res.append((record.id, name)) return res - def parse_partner_name(self, cr, uid, name, context=None): + def _parse_partner_name(self, text, context=None): """ Supported syntax: - - 'Raoul ', available with ' ', '<>', '{}', - '()', '[]': will find name and email address + - 'Raoul ': will find name and email address - otherwise: default, everything is set as the name """ - regex = re.compile('^(.*?)(?: ?[\<{(\[]?([a-zA-Z0-9._%-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9._]{1,8})[\>})\]]?)?$') - result = regex.match(name) - return (result.group(1).strip(), result.group(2)) + match = re.search(r'([^\s,<@]+@[^>\s,]+)', text) + if match: + email = match.group(1) + name = text[:text.index(email)].replace('"','').replace('<','').strip() + else: + name, email = text, '' + return name, email def name_create(self, cr, uid, name, context=None): """ Override of orm's name_create method for partners. The purpose is @@ -367,7 +370,7 @@ class res_partner(osv.osv): If only an email address is received and that the regex cannot find a name, the name will have the email value. If 'force_email' key in context: must find the email address. """ - name, email = self.parse_partner_name(cr, uid, name, context=context) + name, email = self._parse_partner_name(name, context=context) if context.get('force_email') and not email: raise osv.except_osv(_('Warning'), _("Couldn't create contact without email address !")) if not name and email: diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index 5ea7654c008..42fef2eb39d 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -1,5 +1,5 @@ -import test_ir_values +import test_ir_values, test_base checks = [ - test_ir_values, + test_ir_values, test_base ] diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py new file mode 100644 index 00000000000..1ff200a8b68 --- /dev/null +++ b/openerp/addons/base/tests/test_base.py @@ -0,0 +1,26 @@ +import unittest2 + +import openerp.tests.common as common + +class test_base(common.TransactionCase): + + def setUp(self): + super(test_base,self).setUp() + self.res_partner = self.registry('res.partner') + + def test_00_res_partner_name_parse(self): + parse = self.res_partner._parse_partner_name + # samples use effective TLDs from the Mozilla public suffix + # list at http://publicsuffix.org + test_samples = [ + ('"Raoul Grosbedon" ', 'Raoul Grosbedon', 'raoul@chirurgiens-dentistes.fr'), + ('ryu+giga-Sushi@aizubange.fukushima.jp', '', 'ryu+giga-Sushi@aizubange.fukushima.jp'), + ('Raoul chirurgiens-dentistes.fr', 'Raoul chirurgiens-dentistes.fr', ''), + (" Raoul O'hara ", "Raoul O'hara", '!@historicalsociety.museum') + ] + for text, name, mail in test_samples: + self.assertEqual((name,mail), parse(text), 'Partner name parsing failed') + + +if __name__ == '__main__': + unittest2.main() \ No newline at end of file diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 39e86b1ec4a..875fa7a9c09 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -406,7 +406,7 @@ def append_content_to_html(html, content, plaintext=True): EOF), and wrapping the provided content in a
 block
        unless ``plaintext`` is False. A side-effect of this
        method is to coerce all HTML tags to lowercase in ``html``,
-       and strips enclosing  or  tags in content if
+       and strip enclosing  or  tags in content if
        ``plaintext`` is False.
        
        :param str html: html tagsoup (doesn't have to be XHTML)

From 1bcad78c9958b3dee7c63e45a09cdfa3a9ebf64b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= 
Date: Mon, 3 Sep 2012 19:48:29 +0200
Subject: [PATCH 33/37] [FIX] res_partner: not always have a context.

bzr revid: tde@openerp.com-20120903174829-epc4nyiyavdppsbu
---
 openerp/addons/base/res/res_partner.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py
index fbc9e7ee7b6..242526b3d35 100644
--- a/openerp/addons/base/res/res_partner.py
+++ b/openerp/addons/base/res/res_partner.py
@@ -380,6 +380,8 @@ class res_partner(osv.osv):
             If only an email address is received and that the regex cannot find
             a name, the name will have the email value.
             If 'force_email' key in context: must find the email address. """
+        if context is None:
+            context = {}
         name, email = self._parse_partner_name(name, context=context)
         if context.get('force_email') and not email:
             raise osv.except_osv(_('Warning'), _("Couldn't create contact without email address !"))

From 95837af09c769370fa7c7aa1bc89bbee0868d50f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= 
Date: Tue, 4 Sep 2012 12:32:42 +0200
Subject: [PATCH 34/37] [IMP] ir.needaction: made the model Abstract. Fixed a
 bug when inheriting from AbstractModels: _auto (to create a database for the
 model) was not correctly set.

bzr revid: tde@openerp.com-20120904103242-o6ky20u9gvhu0t3u
---
 openerp/addons/base/ir/ir_needaction.py | 2 +-
 openerp/osv/orm.py                      | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py
index ba3a5c4a998..db133ad9904 100644
--- a/openerp/addons/base/ir/ir_needaction.py
+++ b/openerp/addons/base/ir/ir_needaction.py
@@ -21,7 +21,7 @@
 
 from osv import osv
 
-class ir_needaction_mixin(osv.Model):
+class ir_needaction_mixin(osv.AbstractModel):
     '''Mixin class for objects using the need action feature.
 
     Need action feature can be used by models that have to be able to
diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py
index 650a9ac78c5..df12062f922 100644
--- a/openerp/osv/orm.py
+++ b/openerp/osv/orm.py
@@ -654,6 +654,7 @@ class BaseModel(object):
     may be set to False.
     """
     __metaclass__ = MetaModel
+    _auto = True # create database backend
     _register = False # Set to false if the model shouldn't be automatically discovered.
     _name = None
     _columns = {}
@@ -5076,6 +5077,7 @@ class Model(BaseModel):
     The system will later instantiate the class once per database (on
     which the class' module is installed).
     """
+    _auto = True
     _register = False # not visible in ORM registry, meant to be python-inherited only
     _transient = False # True in a TransientModel
 
@@ -5088,6 +5090,7 @@ class TransientModel(BaseModel):
        records they created. The super-user has unrestricted access
        to all TransientModel records.
     """
+    _auto = True
     _register = False # not visible in ORM registry, meant to be python-inherited only
     _transient = True
 

From 43eb05242f0026f7b8742136dcfd2801f0125187 Mon Sep 17 00:00:00 2001
From: Olivier Dony 
Date: Tue, 4 Sep 2012 17:20:02 +0200
Subject: [PATCH 35/37] [IMP] res.partner: add find_or_create util method for
 on-the-fly partner creation + update/improve tests

Changed parsing test to be higher level and test name_create as well

bzr revid: odo@openerp.com-20120904152002-8rra325huf5p3xek
---
 openerp/addons/base/res/res_partner.py | 15 +++++++++++++++
 openerp/addons/base/tests/test_base.py | 25 +++++++++++++++++++++----
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py
index 39ff6775cb3..1609fa2a16f 100644
--- a/openerp/addons/base/res/res_partner.py
+++ b/openerp/addons/base/res/res_partner.py
@@ -409,6 +409,21 @@ class res_partner(osv.osv):
                 return self.name_get(cr, uid, ids, context)
         return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit)
 
+    def find_or_create(self, cr, uid, email, context=None):
+        """ Find a partner with the given ``email`` or use :py:method:`~.name_create`
+            to create one
+            
+            :param str email: email-like string, which should contain at least one email,
+                e.g. ``"Raoul Grosbedon "``"""
+        assert email, 'an email is required for find_or_create to work'
+        emails = tools.email_split(email)
+        if emails:
+            email = emails[0]
+        ids = self.search(cr, uid, [('email','ilike',email)], context=context)
+        if not ids:
+            return self.name_create(cr, uid, email, context=context)[0]
+        return ids[0]
+
     def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None):
         partners = self.browse(cr, uid, ids)
         for partner in partners:
diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py
index 1ff200a8b68..285af8fbf6e 100644
--- a/openerp/addons/base/tests/test_base.py
+++ b/openerp/addons/base/tests/test_base.py
@@ -8,18 +8,35 @@ class test_base(common.TransactionCase):
         super(test_base,self).setUp()
         self.res_partner = self.registry('res.partner')
 
-    def test_00_res_partner_name_parse(self):
-        parse = self.res_partner._parse_partner_name
         # samples use effective TLDs from the Mozilla public suffix
         # list at http://publicsuffix.org
-        test_samples = [
+        self.samples = [
             ('"Raoul Grosbedon"  ', 'Raoul Grosbedon', 'raoul@chirurgiens-dentistes.fr'),
             ('ryu+giga-Sushi@aizubange.fukushima.jp', '', 'ryu+giga-Sushi@aizubange.fukushima.jp'),
             ('Raoul chirurgiens-dentistes.fr', 'Raoul chirurgiens-dentistes.fr', ''),
             (" Raoul O'hara  ", "Raoul O'hara", '!@historicalsociety.museum')
         ]
-        for text, name, mail in test_samples:
+
+    def test_00_res_partner_name_create(self):
+        cr, uid = self.cr, self.uid
+        parse = self.res_partner._parse_partner_name
+        for text, name, mail in self.samples:
             self.assertEqual((name,mail), parse(text), 'Partner name parsing failed')
+            partner_id, dummy = self.res_partner.name_create(cr, uid, text)
+            partner = self.res_partner.browse(cr, uid, partner_id)
+            self.assertEqual(name or mail, partner.name, 'Partner name incorrect')
+            self.assertEqual(mail or False, partner.email, 'Partner email incorrect')
+
+    def test_10_res_partner_find_or_create(self):
+        cr,uid = self.cr, self.uid
+        email = self.samples[0][0]
+        partner_id, dummy = self.res_partner.name_create(cr, uid, email)
+        found_id = self.res_partner.find_or_create(cr, uid, email)
+        self.assertEqual(partner_id, found_id, 'find_or_create failed')
+        new_id = self.res_partner.find_or_create(cr, uid, self.samples[1][0])
+        self.assertTrue(new_id > partner_id, 'find_or_create failed - should have created new one')
+        new_id2 = self.res_partner.find_or_create(cr, uid, self.samples[2][0])
+        self.assertTrue(new_id2 > new_id, 'find_or_create failed - should have created new one again')
 
 
 if __name__ == '__main__':

From efa0f9b26367289a67ad1fc3524bcf74dfd6f925 Mon Sep 17 00:00:00 2001
From: Olivier Dony 
Date: Thu, 6 Sep 2012 00:57:46 +0200
Subject: [PATCH 36/37] [IMP] html_sanitize: attempt to make slightly more
 pythonic/readable + re-enable test. Proper review + test still needed.

bzr revid: odo@openerp.com-20120905225746-npjjzy1w00k05vtc
---
 openerp/tests/__init__.py      |   1 +
 openerp/tools/html_sanitize.py | 111 ++++++++++++++++++++-------------
 2 files changed, 69 insertions(+), 43 deletions(-)

diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py
index 908df676fc0..aa3055f471c 100644
--- a/openerp/tests/__init__.py
+++ b/openerp/tests/__init__.py
@@ -17,6 +17,7 @@ fast_suite = [
 
 checks = [
     test_expression,
+    test_html_sanitize,
     test_orm,
     test_view_validation,
     test_misc,
diff --git a/openerp/tools/html_sanitize.py b/openerp/tools/html_sanitize.py
index 65f17be074f..6ea7b90e2ba 100644
--- a/openerp/tools/html_sanitize.py
+++ b/openerp/tools/html_sanitize.py
@@ -1,67 +1,92 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (C) 2012 OpenERP S.A. ().
+#
+#    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 .
+#
+##############################################################################
 
 import lxml.html
+import operator
 import re
 
-def html_sanitize(x):
-    if not x:
-        return x
-    if type(x) == str:
-        x = unicode(x, "utf8", "replace")
-    root = lxml.html.fromstring("
%s
" % x) - result = handle_element(root) - res = "" - for el in children(result[0]): - if type(el) == str or type(el) == unicode: - res += el - else: - el.tail = "" - res += lxml.html.tostring(el) - return res +from openerp.loglevels import ustr +def html_sanitize(src): + if not src: + return src + src = ustr(src, errors='replace') + root = lxml.html.fromstring(u"
%s
" % src) + result = handle_element(root) + res = [] + for element in children(result[0]): + if isinstance(element, basestring): + res.append(element) + else: + element.tail = "" + res.append(lxml.html.tostring(element)) + return ''.join(res) + +# FIXME: shouldn't this be a whitelist rather than a blacklist?! to_remove = set(["script", "head", "meta", "title", "link", "img"]) to_unwrap = set(["html", "body"]) -javascript_regex = re.compile("""^\s*javascript\s*\:.*$""") +javascript_regex = re.compile(r"^\s*javascript\s*:.*$", re.IGNORECASE) + def handle_a(el, new): href = el.get("href", "#") if javascript_regex.search(href): href = "#" new.set("href", href) + special = { "a": handle_a, } -def handle_element(el): - if type(el) == str or type(el) == unicode: - return [el] - if el.tag in to_remove: +def handle_element(element): + if isinstance(element, basestring): + return [element] + if element.tag in to_remove: return [] - if el.tag in to_unwrap: - return reduce(lambda x,y: x+y, [handle_element(x) for x in children(el)]) - new = lxml.html.fromstring("<%s />" % el.tag) - for i in children(el): - append_to(handle_element(i), new) - if el.tag in special: - special[el.tag](el, new) - return [new] - -def children(el): + if element.tag in to_unwrap: + return reduce(operator.add, [handle_element(x) for x in children(element)]) + result = lxml.html.fromstring("<%s />" % element.tag) + for c in children(element): + append_to(handle_element(c), result) + if element.tag in special: + special[element.tag](element, result) + return [result] + +def children(node): res = [] - if el.text is not None: - res.append(el.text) - for i in el.getchildren(): - res.append(i) - if i.tail is not None: - res.append(i.tail) + if node.text is not None: + res.append(node.text) + for child_node in node.getchildren(): + res.append(child_node) + if child_node.tail is not None: + res.append(child_node.tail) return res -def append_to(new_ones, el): - for i in new_ones: - if type(i) == str or type(i) == unicode: - children = el.getchildren() +def append_to(elements, dest_node): + for element in elements: + if isinstance(element, basestring): + children = dest_node.getchildren() if len(children) == 0: - el.text = i + dest_node.text = element else: - children[-1].tail = i + children[-1].tail = element else: - el.append(i) + dest_node.append(element) From a4f30bf11fbfed0978f09eba8c5b29a44f20c14f Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 6 Sep 2012 01:12:32 +0200 Subject: [PATCH 37/37] [IMP] fields: review/simplify patch for callable domains on fields.one2many bzr revid: odo@openerp.com-20120905231232-u6dx3ak9b0y95zvl --- openerp/osv/fields.py | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index d4b519764a8..037d2a90986 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -515,10 +515,8 @@ class one2many(_column): for id in ids: res[id] = [] - 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) + domain = self._domain(obj) if callable(self._domain) else self._domain + ids2 = obj.pool.get(self._obj).search(cr, user, domain + [(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']) @@ -560,10 +558,8 @@ 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 - dom = self._domain - if isinstance(self._domain, type(lambda: None)): - dom = self._domain(obj) - extra_domain = dom or [] + domain = self._domain(obj) if callable(self._domain) else self._domain + extra_domain = domain 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. @@ -581,10 +577,8 @@ class one2many(_column): return result def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None): - 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) + domain = self._domain(obj) if callable(self._domain) else self._domain + return obj.pool.get(self._obj).name_search(cr, uid, value, domain, operator, context=context,limit=limit) @classmethod @@ -1549,14 +1543,12 @@ def field_to_dict(model, cr, user, field, context=None): """ res = {'type': field._type} - # This additional attributes for M2M and function field is added - # because we need to display tooltip with this additional information - # when client is started in debug mode. + # some attributes for m2m/function field are added as debug info only if isinstance(field, function): res['function'] = field._fnct and field._fnct.func_name or False - # res['store'] = field.store - # if isinstance(field.store, dict): - # res['store'] = str(field.store) + res['store'] = field.store + if isinstance(field.store, dict): + res['store'] = str(field.store) res['fnct_search'] = field._fnct_search and field._fnct_search.func_name or False res['fnct_inv'] = field._fnct_inv and field._fnct_inv.func_name or False res['fnct_inv_arg'] = field._fnct_inv_arg or False @@ -1586,10 +1578,7 @@ def field_to_dict(model, cr, user, field, context=None): res['selection'] = field.selection(model, cr, user, context) if res['type'] in ('one2many', 'many2many', 'many2one'): res['relation'] = field._obj - dom = field._domain - if isinstance(field._domain, type(lambda: None)): - dom = field._domain(model) - res['domain'] = dom + res['domain'] = field._domain(model) if callable(field._domain) else field._domain res['context'] = field._context if isinstance(field, one2many):