From e9f4fc98cc98dab4ca1fdab2f6d9697485934beb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Apr 2012 16:15:53 +0200 Subject: [PATCH 01/49] [ADD] display unhelpful message when a list view search yields no result bzr revid: xmo@openerp.com-20120403141553-hrv006vdkv70zyfu --- addons/web/static/src/css/base.css | 9 +++++++++ addons/web/static/src/css/base.sass | 14 ++++++++++++++ addons/web/static/src/img/empty-list-arrow.png | Bin 0 -> 2308 bytes addons/web/static/src/js/view_list.js | 15 +++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 addons/web/static/src/img/empty-list-arrow.png diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index b0a2b7945b9..d4a1d743d35 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -344,3 +344,12 @@ -webkit-box-shadow: none; -box-shadow: none; } +.openerp2 .oe-listview .oe_listview_nocontent > img { + float: left; + margin-right: 1.5em; +} +.openerp2 .oe-listview .oe_listview_nocontent > div { + overflow: hidden; + font-size: 150%; + width: 60ex; +} diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c31f501c499..ecc45f5f538 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -337,6 +337,20 @@ $colour4: #8a89ba @include box-shadow(none) // }}} + + // ListView {{{ + .oe-listview + .oe_listview_nocontent + > img + float: left + margin-right: 1.5em + > div + // don't encroach on my arrow + overflow: hidden + font-size: 150% + width: 60ex + // }}} + .openerp // Transitional overrides for old styles {{{ // }}} diff --git a/addons/web/static/src/img/empty-list-arrow.png b/addons/web/static/src/img/empty-list-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b1372adfd3fc393ecc1dac3934c423822970cd GIT binary patch literal 2308 zcmV+f3H$bmP)X)YT08QE{zMm=*yzjy#>qO{b%( zI}c{Mx03c9H98A){3#yqKb--N032OnY{IHxe`j=U0dQ71X*fr_-rMdfhO?Be=W<_8 zj{qD+a-Q}nrt_NceY#zQ=DCovmL8(x;bg|Xh=WW7;Q#!dE6lRP7`PA`cgO>*0&zVl zW>;>57TRm70J;%?W6+jWT|J)*-v@DPq@u$gCQSQwHuVTV9}uaueWRY;EWlDthqccq zRtZs=&cEQ#VimEr2atn@VpxUD3W~h!`$m-_`A0=}o?^e*)&Md9s%|~v(U#IU;EAZL zX9cbK)i4Im3^T0>O!E>6djCen5f$R7~r| zu;s~((H7HQoPiGm0Mg*G8K^Fz{hq2?ms8*% z%^*ilIV?CJj@#Tc{%#aN;(JUPmYaT(pSG^!dl39mAj+ zAHlTx(Q*3(n%EEd80VWP8sl0cAnt>FPbnRKXBP^yBiOJ4zH_629t%h1&;d9RC$4GM zdBg{~8Y9C2sTIKPVw}`5{5+5}l7*dnGCed$7C>?f=-yFDYc6XUgGT4A&IJQ$V=gxa zE&~`xBsH*%sRiRSt%+bfTuE+Y7ES8wP@^LOD%{2&qG@l3Jp?zkm(G~ z4z>b-Y+dY5;=P4aA1u@VaLXjcPfkd_^Lg!lAy6#wj$jU&!>t_PGZeN3SZKfv< zxWLjR0IdZSO$8Y8JrUBvJm1U|6%t}}54F)>(e3N8rXGlJFN$ejS<4cF9W3wLb27AO zuVhBTQW9=Jcb=<)*mrVsG|d&7=GSs=*8;dFsFX%U4zeh32#T6$Hv|u0tri+~mza~$ zrTic-)TM~7r0jY0ozxo%!Zb0HEqq#Sg@*_{Qpx+V=J)aYzf;4^OA5GcTc-6aH>_Km z4`9l^4;mz^fh;As0y5=4*lg&=Y2g!1G5s1gT4s|<4V?>D^Dx%Ain1V@?;+p-6m!|4 z&lQP*P^{%{zSpLW`1T@NRSOGS0s#>u`!GGQ1GJ#3Hpa63|i!0A-$!bCx2rXs~bW2oOmls+(bVccccdm}nF1 z9c}PpH8Zr3m;p8FA^T`Uur6Vq9_cTQ1{#a^|4aA`k^CHgzbuWJR0E1TYitJcVwLGT z;8(>BBi&n#K=qiCw&!W-!J9NQe;fXO02S@$DIdqbUx&|q3pot46o65R>CI%5TPW56 zjRoL3;`2O)>`5R*x_%SNv_FJ-@Pge&g*Sx2U=2c)6y^t@s5?jzQ(Ob(z_q*pU(p$` z$+#1Tp6#RWCcpQB->*ufa&oybk>UURh*Jq_0Ezq1zGqM>Y--%6_DoaeH!B1lHlEIZ zLz`i~@1A1v#RU1xWTntTrZlZBMKamypu9DtiCD#Dj*7%;`zFRa5b;^r_d&oe>c63#sisT!WhY_tYNHW)WAYO5cw(<3g|5%FjIxduIUVGc-3yU9m*M zo;{XZ@Kvj0ZT0#)3Fj^}i~yq}Z9w$?z%jsNVUy$*XY>Nb=mm_CL;ywrMgT?tMiK!S e0T=;z9LPV+S$p2eoZCSF0000') + .append($('', { + src: '/web/static/src/img/empty-list-arrow.png', + alt: _t("This list is empty")})) + .append($('
').html(help))); } }); openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.ListView.List# */{ @@ -1344,6 +1356,9 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L self.records.add(records, {silent: true}); list.render(); d.resolve(list); + if (_.isEmpty(records)) { + view.no_result(); + } });}); return d.promise(); }, From da864266c0c374abecad3295f74a57b0d7a394b8 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Apr 2012 16:17:26 +0200 Subject: [PATCH 02/49] [FIX] o2m/m2m lists: only display help when there's help on the action... or an action at all bzr revid: xmo@openerp.com-20120403141726-a6o4sfvifzzjy9r4 --- addons/web/static/src/js/view_list.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index c9a65ac6c4b..85943732c68 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -749,8 +749,9 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# } }, no_result: function () { - var help = this.options.action.help; - if (this.groups.group_by || !help) { + if (this.groups.group_by + || !this.options.action + || !this.options.action.help) { return; } this.$element.children('table').replaceWith( @@ -758,7 +759,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# .append($('', { src: '/web/static/src/img/empty-list-arrow.png', alt: _t("This list is empty")})) - .append($('
').html(help))); + .append($('
').html(this.options.action.help))); } }); openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.ListView.List# */{ From 522de0125cd5764220957637ee8f647dc910ef8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 10:47:37 +0200 Subject: [PATCH 03/49] [REVIEW] needaction: remove unnecessary bzr revid: tde@openerp.com-20120418084737-jxvoe5jf02xalq8r --- openerp/addons/base/ir/ir_needaction.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 3fcd56affae..6dd0b73e82c 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -49,15 +49,11 @@ class ir_needaction_users(osv.osv): def _get_users(self, cr, uid, res_ids, res_model, context=None): """Given res_ids of res_model, get user_ids present in table""" - if context is None: - context = {} needact_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context) return map(itemgetter('res_id'), self.read(cr, uid, needact_ids, 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""" - if context is None: - context = {} 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) @@ -65,8 +61,6 @@ class ir_needaction_users(osv.osv): 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""" - if context is None: - context = {} 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) @@ -131,8 +125,6 @@ class ir_needaction_mixin(osv.osv): return dict.fromkeys(ids, []) def create(self, cr, uid, values, context=None): - if context is None: - context = {} needact_table_obj = self.pool.get('ir.needaction_users') # perform create obj_id = super(ir_needaction_mixin, self).create(cr, uid, values, context=context) @@ -142,8 +134,6 @@ class ir_needaction_mixin(osv.osv): return obj_id def write(self, cr, uid, ids, values, context=None): - if context is None: - context = {} needact_table_obj = self.pool.get('ir.needaction_users') # perform write write_res = super(ir_needaction_mixin, self).write(cr, uid, ids, values, context=context) @@ -154,8 +144,6 @@ class ir_needaction_mixin(osv.osv): return write_res def unlink(self, cr, uid, ids, context=None): - if context is None: - context = {} # unlink user_ids needact_table_obj = self.pool.get('ir.needaction_users') needact_table_obj.unlink_users(cr, uid, ids, self._name, context=context) @@ -169,8 +157,6 @@ class ir_needaction_mixin(osv.osv): def needaction_get_record_ids(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - if context is None: - context = {} needact_table_obj = self.pool.get('ir.needaction_users') needact_table_ids = needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context) return map(itemgetter('res_id'), needact_table_obj.read(cr, uid, needact_table_ids, context=context)) @@ -178,8 +164,6 @@ class ir_needaction_mixin(osv.osv): def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - if context is None: - context = {} needact_table_obj = self.pool.get('ir.needaction_users') return needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context) @@ -188,8 +172,6 @@ class ir_needaction_mixin(osv.osv): perform an action. Records are given as references, a list of tuples (model_name, record_id). This method is trans-model.""" - if context is None: - context = {} needact_table_obj = self.pool.get('ir.needaction_users') needact_table_ids = needact_table_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) needact_records = needact_table_obj.read(cr, uid, needact_table_ids, context=context) From 3930d41621dddd023f62ad0695528367a9773127 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 18 Apr 2012 10:47:42 +0200 Subject: [PATCH 04/49] [IMP] hr.config.settings: small fix in view bzr revid: rco@openerp.com-20120418084742-ruboxpqeggpzu2sh --- addons/hr/res_config_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr/res_config_view.xml b/addons/hr/res_config_view.xml index 3377ad20c37..a34783772c9 100644 --- a/addons/hr/res_config_view.xml +++ b/addons/hr/res_config_view.xml @@ -42,7 +42,7 @@ - Configure Human Resources Application + Configure Human Resources hr.config.settings form From 7ef99096b0b84b0eb338ba55fcce14b3f9b5142c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 10:59:45 +0200 Subject: [PATCH 05/49] [RFEVIEW] Cleaned code, based on ODO/VMT review. bzr revid: tde@openerp.com-20120418085945-jlr7hbzrpju3yv2m --- openerp/addons/base/ir/ir_needaction.py | 52 +++++++++---------- .../addons/base/security/ir.model.access.csv | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 6dd0b73e82c..7f246dc282a 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -24,17 +24,18 @@ from operator import itemgetter from osv import osv, fields from tools.translate import _ -class ir_needaction_users(osv.osv): +class ir_needaction_users_rel(osv.osv): ''' - ir_needaction_users holds data related to the needaction - mechanism inside OpenERP. A needaction is characterized by: + 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' + _name = 'ir.ir_needaction_users_rel' _description = 'Needaction relationship table' _rec_name = 'id' _order = 'id desc' @@ -49,8 +50,8 @@ class ir_needaction_users(osv.osv): def _get_users(self, cr, uid, res_ids, res_model, context=None): """Given res_ids of res_model, get user_ids present in table""" - needact_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context) - return map(itemgetter('res_id'), self.read(cr, uid, needact_ids, context=context)) + rel_ids = self.search(cr, uid, [('res_model', '=', res_model), ('res_id', 'in', res_ids)], context=context) + return map(itemgetter('res_id'), self.read(cr, uid, rel_ids, 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""" @@ -88,11 +89,11 @@ class ir_needaction_mixin(osv.osv): This class wraps a class (ir.needaction_users) that behaves like a many2many field. However, no field is added to the model - inheriting from base.needaction. The mixin class manages the low-level + inheriting from this mixin class. 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 + 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, @@ -112,7 +113,7 @@ class ir_needaction_mixin(osv.osv): are given as references, a list of tuples (model_name, record_id) ''' _name = 'ir.needaction_mixin' - _description = 'Need action of users on records API' + _description = '"Need action" mixin' #------------------------------------------------------ # Addon API @@ -125,56 +126,55 @@ class ir_needaction_mixin(osv.osv): return dict.fromkeys(ids, []) def create(self, cr, uid, values, context=None): - needact_table_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.needaction_users') # 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) - needact_table_obj.create_users(cr, uid, [obj_id], self._name, needaction_user_ids[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): - needact_table_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.needaction_users') # 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: - needact_table_obj.update_users(cr, uid, [id], self._name, needaction_user_ids[id], context=context) + 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 - needact_table_obj = self.pool.get('ir.needaction_users') - needact_table_obj.unlink_users(cr, uid, ids, self._name, context=context) + rel_obj = self.pool.get('ir.needaction_users') + rel_obj.unlink_users(cr, uid, ids, self._name, context=context) # perform unlink return super(ir_needaction_mixin, self).unlink(cr, uid, ids, context=context) #------------------------------------------------------ - # Need action API + # "Need action" API #------------------------------------------------------ def needaction_get_record_ids(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - needact_table_obj = self.pool.get('ir.needaction_users') - needact_table_ids = needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context) - return map(itemgetter('res_id'), needact_table_obj.read(cr, uid, needact_table_ids, context=context)) + rel_obj = self.pool.get('ir.needaction_users') + needact_table_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, needact_table_ids, context=context)) def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - needact_table_obj = self.pool.get('ir.needaction_users') - return needact_table_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context) + rel_obj = self.pool.get('ir.needaction_users') + return rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context) def needaction_get_record_references(self, cr, uid, user_id, offset=None, limit=None, order=None, context=None): """For a given user_id, get all the records that asks this user to perform an action. Records are given as references, a list of tuples (model_name, record_id). This method is trans-model.""" - needact_table_obj = self.pool.get('ir.needaction_users') - needact_table_ids = needact_table_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) - needact_records = needact_table_obj.read(cr, uid, needact_table_ids, context=context) - return map(itemgetter('res_model', 'id'), needact_records) + rel_obj = self.pool.get('ir.needaction_users') + needact_table_ids = rel_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) + return map(itemgetter('res_model', 'id'), rel_obj.read(cr, uid, needact_table_ids, context=context)) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index ac05b726341..8b906ba48a5 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -121,6 +121,6 @@ "access_ir_mail_server_all","ir_mail_server","model_ir_mail_server",,1,0,0,0 "access_ir_actions_todo_category","ir_actions_todo_category","model_ir_actions_todo_category","group_system",1,1,1,1 "access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0 -"access_ir_needaction_users","ir_needaction_users","model_ir_needaction_users",,1,1,1,1 +"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 From 94210b26a27cb32c0a00bcb578b44bca138eeec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:01:41 +0200 Subject: [PATCH 06/49] [REVIEW] [DOC] Updated documentation. bzr revid: tde@openerp.com-20120418090141-ho1205rt0omyxo4k --- doc/api/need_action_specs.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/need_action_specs.rst b/doc/api/need_action_specs.rst index c9f32dd60c4..88787ae4c44 100644 --- a/doc/api/need_action_specs.rst +++ b/doc/api/need_action_specs.rst @@ -1,14 +1,14 @@ Need action mechanism ===================== -ir.needaction mixin class -++++++++++++++++++++++++++ +ir.needaction_mixin mixin class ++++++++++++++++++++++++++++++++ This revision adds a mixin class for objects using the need action feature. Need action feature can be used by objects willing to be able to signal that an action is required on a particular record. If in the business logic an action must be performed by somebody, for instance validation by a manager, this mechanism allows to set a list of users asked to perform an action. -This class wraps a class (ir.needaction_users) that behaves like a many2many field. However, no field is added to the model inheriting from base.needaction. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships. +This class wraps a class (ir.needaction_users) that behaves like a many2many field. However, no field is added to the model inheriting from ir.needaction_mixin. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships. Objects using the need_action feature should override the ``get_needaction_user_ids`` method. This methods returns a dictionary whose keys are record ids, and values a list of user ids, like in a many2many relationship. Therefore by defining only one method, you can specify if an action is required by defining the users that have to do it, in every possible situation. @@ -30,12 +30,12 @@ Those fields are functional, because they depend on the user and must therefore Addon implementation example ++++++++++++++++++++++++++++ -In your ``foo`` module, you want to specify that when it is in state ``confirmed``, it has to be validated by a manager, given by the field ``manager_id``. After making ``foo`` inheriting from ``base.needaction``, you override the ``get_needaction_user_ids`` method: +In your ``foo`` module, you want to specify that when it is in state ``confirmed``, it has to be validated by a manager, given by the field ``manager_id``. After making ``foo`` inheriting from ``ir.needaction_mixin``, you override the ``get_needaction_user_ids`` method: :: [...] - _inherit = [base.needaction] + _inherit = [`ir.needaction_mixin] [...] def get_needaction_user_ids(self, cr, uid, ids, context=None): result = dict.fromkeys(ids) From c1a4f1f8db6e3161ae61a8358e023c281dc3fed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:07:35 +0200 Subject: [PATCH 07/49] [IMP] Updated code (see odo/vmt review). Also removed record_ids storage in menus, to avoid passing too much data to the client. bzr revid: tde@openerp.com-20120418090735-e5ewk3wcf7bp0bvu --- openerp/addons/base/ir/ir_needaction.py | 15 ++++++++------- openerp/addons/base/ir/ir_ui_menu.py | 8 +++----- openerp/osv/orm.py | 10 ++++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 7f246dc282a..c408c189939 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -51,7 +51,7 @@ class ir_needaction_users_rel(osv.osv): 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 map(itemgetter('res_id'), self.read(cr, uid, rel_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""" @@ -69,7 +69,7 @@ class ir_needaction_users_rel(osv.osv): """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]): + 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) @@ -157,10 +157,11 @@ class ir_needaction_mixin(osv.osv): def needaction_get_record_ids(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id - get the number of actions it has to perform""" + return the record ids that require the user to perform an + action""" rel_obj = self.pool.get('ir.needaction_users') - needact_table_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, needact_table_ids, context=context)) + rel_ids = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context) + return map(itemgetter('res_id'), rel_obj.read(cr, uid, rel_ids, ['res_id'], context=context)) def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id @@ -174,7 +175,7 @@ class ir_needaction_mixin(osv.osv): tuples (model_name, record_id). This method is trans-model.""" rel_obj = self.pool.get('ir.needaction_users') - needact_table_ids = rel_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) - return map(itemgetter('res_model', 'id'), rel_obj.read(cr, uid, needact_table_ids, context=context)) + rel_ids = rel_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) + return map(itemgetter('res_model', 'res_id'), rel_obj.read(cr, uid, rel_ids, ['res_model', 'res_id'], context=context)) # 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 1f3f2e0f181..0de4ff74c11 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -258,19 +258,17 @@ class ir_ui_menu(osv.osv): def _get_needaction(self, cr, uid, ids, field_names, args, context=None): if context is None: context = {} - res = dict.fromkeys(ids) + res = {} for menu in self.browse(cr, uid, ids, context=context): res[menu.id] = {} if menu.action and menu.action.type == 'ir.actions.act_window' and menu.action.res_model: - menu_needaction_res = self.pool.get(menu.action.res_model).get_needaction_info(cr, uid, uid, domain=menu.action.domain, context=context) + menu_needaction_res = self.pool.get(menu.action.res_model)._get_needaction_info(cr, uid, uid, domain=menu.action.domain, context=context) # TODO: find the addon that causes a bug on runbot, not on local if not isinstance(menu_needaction_res[1], (int, long)): menu_needaction_res[1] = 0 else: - menu_needaction_res = [False, 0, ()] + menu_needaction_res = [False, 0] res[menu.id]['needaction_enabled'] = menu_needaction_res[0] res[menu.id]['needaction_counter'] = menu_needaction_res[1] - # not used currently, therefore set to a void list - res[menu.id]['needaction_record_ids'] = [] return res _columns = { diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 313c96a9fb4..bdf78fd3567 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4866,7 +4866,7 @@ class BaseModel(object): 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): + 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 @@ -4883,16 +4883,18 @@ class BaseModel(object): :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, []] + 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), ids] + return [True, self.search(cr, uid, new_domain, limit=limit, order=order, count=True, context=context)] else: - return [False, 0, []] + return [False, 0] # Transience def is_transient(self): From aca2b4874639b5adbe13e6471b8d9a0d5f59651d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:12:59 +0200 Subject: [PATCH 08/49] [FIX] Propagated name change from ir_needaction_users to ir_needaction_users_rel. bzr revid: tde@openerp.com-20120418091259-cnhae6rsc8dqlmkw --- openerp/addons/base/ir/ir_needaction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index c408c189939..2ea81870c68 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -87,7 +87,7 @@ class ir_needaction_mixin(osv.osv): validation by a manager, this mechanism allows to set a list of users asked to perform an action. - This class wraps a class (ir.needaction_users) that behaves + This class wraps a class (ir.ir_needaction_users_rel) that behaves like a many2many field. However, no field is added to the model inheriting from this mixin class. This class handles the low-level considerations of updating relationships. Every change made on the @@ -126,7 +126,7 @@ class ir_needaction_mixin(osv.osv): return dict.fromkeys(ids, []) def create(self, cr, uid, values, context=None): - rel_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.ir_needaction_users_rel') # perform create obj_id = super(ir_needaction_mixin, self).create(cr, uid, values, context=context) # link user_ids @@ -135,7 +135,7 @@ class ir_needaction_mixin(osv.osv): return obj_id def write(self, cr, uid, ids, values, context=None): - rel_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.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 @@ -146,7 +146,7 @@ class ir_needaction_mixin(osv.osv): def unlink(self, cr, uid, ids, context=None): # unlink user_ids - rel_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.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) @@ -159,14 +159,14 @@ class ir_needaction_mixin(osv.osv): """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_obj = self.pool.get('ir.ir_needaction_users_rel') rel_ids = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context) return map(itemgetter('res_id'), rel_obj.read(cr, uid, rel_ids, ['res_id'], context=context)) def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - rel_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.ir_needaction_users_rel') return rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, count=True, context=context) def needaction_get_record_references(self, cr, uid, user_id, offset=None, limit=None, order=None, context=None): @@ -174,7 +174,7 @@ class ir_needaction_mixin(osv.osv): perform an action. Records are given as references, a list of tuples (model_name, record_id). This method is trans-model.""" - rel_obj = self.pool.get('ir.needaction_users') + rel_obj = self.pool.get('ir.ir_needaction_users_rel') rel_ids = rel_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) return map(itemgetter('res_model', 'res_id'), rel_obj.read(cr, uid, rel_ids, ['res_model', 'res_id'], context=context)) From 7b073bd3861d83fee01e987b151eeef88c8dd314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:13:57 +0200 Subject: [PATCH 09/49] [FIX] Propagated name change from ir_needaction_users to ir_needaction_users_rel. bzr revid: tde@openerp.com-20120418091357-w3364qw5e0fyfjkj --- openerp/addons/base/ir/ir.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index ccec381f1d1..e66182bae39 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -662,9 +662,9 @@ - - ir.needaction_users.tree - ir.needaction_users + + ir.needaction_users_rel.tree + ir.needaction_users_rel tree 10 @@ -676,14 +676,14 @@ - + Need action relationships - ir.needaction_users + ir.needaction_users_rel form tree,form - + Date: Wed, 18 Apr 2012 11:16:13 +0200 Subject: [PATCH 10/49] [imp] removed validate & invalid in form view bzr revid: nicolas.vanhoren@openerp.com-20120418091613-c0gpu6eo3yqndq6l --- addons/web/static/src/js/view_form.js | 65 +++++++------------ addons/web_calendar/static/src/js/calendar.js | 1 - 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 3f9ef8301fd..2ca19687cec 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -250,9 +250,6 @@ instance.web.FormView = instance.web.View.extend({ field.reset(); var result = field.set_value(self.datarecord[f] || false); set_values.push(result); - $.when(result).then(function() { - field.validate(); - }); }); return $.when.apply(null, set_values).pipe(function() { if (!record.id) { @@ -285,9 +282,6 @@ instance.web.FormView = instance.web.View.extend({ on_form_changed: function() { this.trigger("view_content_has_changed"); _.each(this.get_widgets(), function(w) { - if (w.field) { - w.validate(); - } w.update_dom(); }); }, @@ -1265,7 +1259,7 @@ instance.web.form.compute_domain = function(expr, fields) { _t("Unknown field %s in domain %s"), ex[0], JSON.stringify(expr))); } - var field_value = field.get_value(); + var field_value = field.get_value ? field.get_value() : field.value; var op = ex[1]; var val = ex[2]; @@ -1655,7 +1649,7 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins this.set({'value': false}); this.field = this.field_manager.get_field(this.name); this.set({required: this.modifiers['required'] === true}); - this.invalid = this.dirty = false; + this.dirty = false; // some events to make the property "effective_readonly" sync automatically with "readonly" and // "force_readonly" @@ -1695,7 +1689,6 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins this._inhibit_on_change = true; this.set({'value': value_}); this._inhibit_on_change = false; - this.invalid = false; this.update_dom(); }, on_translate: function() { @@ -1705,7 +1698,7 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins return this.get('value'); }, is_valid: function() { - return !this.invalid; + return true; }, is_dirty: function() { return this.dirty && !this.get("effective_readonly"); @@ -1723,7 +1716,6 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins }, _on_ui_change: function() { this.dirty = true; - this.validate(); if (this.is_valid()) { this.view.do_onchange(this); this.view.on_form_changed(true); @@ -1732,9 +1724,6 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins this.update_dom(true); } }, - validate: function() { - this.invalid = false; - }, focus: function($element) { if ($element) { setTimeout(function() { @@ -1821,16 +1810,16 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({} this.$element.text(show_value); } }, - validate: function() { - this.invalid = false; + is_valid: function() { if (!this.get("effective_readonly")) { try { var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); - this.invalid = this.get("required") && value_ === ''; + return (! this.get("required")) || value_ !== ''; } catch(e) { - this.invalid = true; + return false; } } + return true; }, focus: function($element) { this._super($element || this.$element.find('input:first')); @@ -2031,11 +2020,11 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.exten this.$element.text(instance.web.format_value(this.get('value'), this, '')); } }, - validate: function() { - this.invalid = false; + is_valid: function() { if (!this.get("effective_readonly")) { - this.invalid = !this.datewidget.is_valid() || (this.get("required") && !this.datewidget.get_value()); + return this.datewidget.is_valid() && (!this.get("required") || this.datewidget.get_value()); } + return true; }, focus: function($element) { this._super($element || (this.datewidget && this.datewidget.$input)); @@ -2076,16 +2065,16 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({} this.$element.text(show_value); } }, - validate: function() { - this.invalid = false; + is_valid: function() { if (!this.get("effective_readonly")) { try { var value_ = instance.web.parse_value(this.$textarea.val(), this, ''); - this.invalid = this.get("required") && value_ === ''; + return !this.get("required") || value_ !== ''; } catch(e) { - this.invalid = true; + return false; } } + return true; }, focus: function($element) { this._super($element || this.$textarea); @@ -2220,13 +2209,13 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte this.$element.text(option ? option[1] : this.values[0][1]); } }, - validate: function() { + is_valid: function() { if (this.get("effective_readonly")) { - this.invalid = false; - return; + return true; } var value_ = this.values[this.$element.find('select')[0].selectedIndex]; - this.invalid = !(value_ && !(this.get("required") && value_[0] === false)); + var invalid_ = !(value_ && !(this.get("required") && value_[0] === false)); + return ! invalid_; }, focus: function($element) { this._super($element || this.$element.find('select:first')); @@ -2570,8 +2559,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten this._super(value_); this.inhibit_on_change = false; }, - validate: function() { - this.invalid = this.get("required") && ! this.get("value"); + is_valid: function() { + return !this.get("required") || !! this.get("value"); }, focus: function ($element) { this._super($element || this.$input); @@ -2876,23 +2865,18 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }, this)); }, is_valid: function() { - this.validate(); - return this._super(); - }, - validate: function() { - this.invalid = false; if (!this.viewmanager.views[this.viewmanager.active_view]) - return; + return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; if (this.viewmanager.active_view === "form") { for (var f in view.fields) { f = view.fields[f]; if (!f.is_valid()) { - this.invalid = true; - return; + return false; } } } + return true; }, is_dirty: function() { this.save_any_view(); @@ -3066,9 +3050,6 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ self.reload_content(); this.is_setted.resolve(); }, - validate: function() { - this.invalid = false; - }, load_view: function() { var self = this; this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, { diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index fe261ba6fec..d92aac4304e 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -291,7 +291,6 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ var ffield = form.fields[field_name]; ffield.reset(); $.when(ffield.set_value(data[field_name])).then(function() { - ffield.validate(); ffield.dirty = true; form.do_onchange(ffield); }); From 79a4e461a7f666cf71b597d3a733c85d03e8647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:23:15 +0200 Subject: [PATCH 11/49] [FIX] Fixed wrong model name. bzr revid: tde@openerp.com-20120418092315-c35p0dtazgkgd9ae --- openerp/addons/base/ir/ir_needaction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 2ea81870c68..6dd9cdfdc54 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -35,7 +35,7 @@ class ir_needaction_users_rel(osv.osv): 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.ir_needaction_users_rel' + _name = 'ir.needaction_users_rel' _description = 'Needaction relationship table' _rec_name = 'id' _order = 'id desc' @@ -126,7 +126,7 @@ class ir_needaction_mixin(osv.osv): return dict.fromkeys(ids, []) def create(self, cr, uid, values, context=None): - rel_obj = self.pool.get('ir.ir_needaction_users_rel') + 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 @@ -135,7 +135,7 @@ class ir_needaction_mixin(osv.osv): return obj_id def write(self, cr, uid, ids, values, context=None): - rel_obj = self.pool.get('ir.ir_needaction_users_rel') + 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 @@ -146,7 +146,7 @@ class ir_needaction_mixin(osv.osv): def unlink(self, cr, uid, ids, context=None): # unlink user_ids - rel_obj = self.pool.get('ir.ir_needaction_users_rel') + 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) @@ -159,14 +159,14 @@ class ir_needaction_mixin(osv.osv): """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.ir_needaction_users_rel') + rel_obj = self.pool.get('ir.needaction_users_rel') rel_ids = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('user_id', '=', user_id)], limit=limit, context=context) return map(itemgetter('res_id'), rel_obj.read(cr, uid, rel_ids, ['res_id'], context=context)) def needaction_get_action_count(self, cr, uid, user_id, limit=80, context=None): """Given the current model and a user_id get the number of actions it has to perform""" - rel_obj = self.pool.get('ir.ir_needaction_users_rel') + 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) def needaction_get_record_references(self, cr, uid, user_id, offset=None, limit=None, order=None, context=None): @@ -174,7 +174,7 @@ class ir_needaction_mixin(osv.osv): perform an action. Records are given as references, a list of tuples (model_name, record_id). This method is trans-model.""" - rel_obj = self.pool.get('ir.ir_needaction_users_rel') + rel_obj = self.pool.get('ir.needaction_users_rel') rel_ids = rel_obj.search(cr, uid, [('user_id', '=', user_id)], offset=offset, limit=limit, order=order, context=context) return map(itemgetter('res_model', 'res_id'), rel_obj.read(cr, uid, rel_ids, ['res_model', 'res_id'], context=context)) From 22adea444c6614243ce280a795931ce91460ddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:41:01 +0200 Subject: [PATCH 12/49] [ADD] Added a 'needaction_pending' field: this states whether the record has an action pending. bzr revid: tde@openerp.com-20120418094101-o0a9lcpux8b6d75u --- openerp/addons/base/ir/ir_needaction.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 6dd9cdfdc54..7270eb2ce54 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -115,6 +115,21 @@ class ir_needaction_mixin(osv.osv): _name = 'ir.needaction_mixin' _description = '"Need action" mixin' + def get_needaction_pending(self, cr, uid, ids, name, arg, context=None): + res = {} + rel_obj = self.pool.get('ir.needaction_users_rel') + for id in ids: + res[id] = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', id), ('user_id', '=', uid)], limit=1, count=True, context=context) > 0 + return res + + _columns = { + 'needaction_pending': fields.function(get_needaction_pending, type='boolean', + string='Need action pending', + help='If True, this field states that users have to perform an action. \ + This field comes from the needaction mechanism. Please refer \ + to the ir.needaction_mixin class.'), + } + #------------------------------------------------------ # Addon API #------------------------------------------------------ From 9012e82d774ce96d0fafaa41b56e39dd3b4ab6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:45:54 +0200 Subject: [PATCH 13/49] [IMP] 'needaction_pending' field: not necessary to read a table as the information is already available in object. bzr revid: tde@openerp.com-20120418094554-n5p2wf599tbmhizi --- openerp/addons/base/ir/ir_needaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 7270eb2ce54..7ed9762f8db 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -117,9 +117,9 @@ class ir_needaction_mixin(osv.osv): def get_needaction_pending(self, cr, uid, ids, name, arg, context=None): res = {} - rel_obj = self.pool.get('ir.needaction_users_rel') + needaction_user_ids = self.get_needaction_user_ids(cr, uid, ids, context=context) for id in ids: - res[id] = rel_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', id), ('user_id', '=', uid)], limit=1, count=True, context=context) > 0 + res[id] = uid in needaction_user_ids[id] return res _columns = { From 84dadd0e27e1c6f7686ac97e3cfbdd53d74ef757 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 18 Apr 2012 11:50:43 +0200 Subject: [PATCH 14/49] [imp] separated syntax validity from required validity in form view bzr revid: nicolas.vanhoren@openerp.com-20120418095043-6b3pgd89d984f6o0 --- addons/web/static/src/js/view_form.js | 50 +++++++++++++++++---------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 2ca19687cec..976c844c3db 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1698,8 +1698,14 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins return this.get('value'); }, is_valid: function() { + return this.is_syntax_valid() && (! this.get('required') || ! this.is_false()); + }, + is_syntax_valid: function() { return true; }, + is_false: function() { + return this.get('value') === false; + }, is_dirty: function() { return this.dirty && !this.get("effective_readonly"); }, @@ -1716,7 +1722,7 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins }, _on_ui_change: function() { this.dirty = true; - if (this.is_valid()) { + if (this.is_syntax_valid()) { this.view.do_onchange(this); this.view.on_form_changed(true); this.view.do_notify_change(); @@ -1810,17 +1816,20 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({} this.$element.text(show_value); } }, - is_valid: function() { + is_syntax_valid: function() { if (!this.get("effective_readonly")) { try { var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); - return (! this.get("required")) || value_ !== ''; + return true; } catch(e) { return false; } } return true; }, + is_false: function() { + return this.get('value') === ''; + }, focus: function($element) { this._super($element || this.$element.find('input:first')); } @@ -1846,7 +1855,7 @@ instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ } }, on_button_clicked: function() { - if (!this.get('value') || !this.is_valid()) { + if (!this.get('value') || !this.is_syntax_valid()) { this.do_warn("E-mail error", "Can't send email to invalid e-mail address"); } else { location.href = 'mailto:' + this.get('value'); @@ -1958,7 +1967,7 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ this.$input.prop('readonly', this.readonly); this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, - is_valid: function() { + is_valid_: function() { var value_ = this.$input.val(); if (value_ === "") { return true; @@ -1978,7 +1987,7 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ return instance.web.format_value(v, {"widget": this.type_of_date}); }, on_change: function() { - if (this.is_valid()) { + if (this.is_valid_()) { this.set_value_from_ui_(); } } @@ -2020,12 +2029,15 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.exten this.$element.text(instance.web.format_value(this.get('value'), this, '')); } }, - is_valid: function() { + is_syntax_valid: function() { if (!this.get("effective_readonly")) { - return this.datewidget.is_valid() && (!this.get("required") || this.datewidget.get_value()); + return this.datewidget.is_valid_(); } return true; }, + is_false: function() { + return this.get('value') === ''; + }, focus: function($element) { this._super($element || (this.datewidget && this.datewidget.$input)); } @@ -2065,17 +2077,20 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({} this.$element.text(show_value); } }, - is_valid: function() { + is_syntax_valid: function() { if (!this.get("effective_readonly")) { try { var value_ = instance.web.parse_value(this.$textarea.val(), this, ''); - return !this.get("required") || value_ !== ''; + return true; } catch(e) { return false; } } return true; }, + is_false: function() { + return this.get('value') === ''; + }, focus: function($element) { this._super($element || this.$textarea); }, @@ -2209,13 +2224,12 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte this.$element.text(option ? option[1] : this.values[0][1]); } }, - is_valid: function() { + is_syntax_valid: function() { if (this.get("effective_readonly")) { return true; } var value_ = this.values[this.$element.find('select')[0].selectedIndex]; - var invalid_ = !(value_ && !(this.get("required") && value_[0] === false)); - return ! invalid_; + return !! value_; }, focus: function($element) { this._super($element || this.$element.find('select:first')); @@ -2559,8 +2573,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten this._super(value_); this.inhibit_on_change = false; }, - is_valid: function() { - return !this.get("required") || !! this.get("value"); + is_false: function() { + return ! this.get("value"); }, focus: function ($element) { this._super($element || this.$input); @@ -2864,7 +2878,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ return false; }, this)); }, - is_valid: function() { + is_syntax_valid: function() { if (!this.viewmanager.views[this.viewmanager.active_view]) return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; @@ -3510,8 +3524,8 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.exte this.m2o.renderElement(); this.m2o.start(); }, - is_valid: function() { - return this.get("required") === false || typeof(this.get_value()) === 'string'; + is_false: function() { + return typeof(this.get_value()) !== 'string'; }, is_dirty: function() { return this.selection.is_dirty() || this.m2o.is_dirty(); From e4063efdf27324d91ad2d1a33f042e0c2b192af8 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 18 Apr 2012 11:57:58 +0200 Subject: [PATCH 15/49] [imp] changed the way on_changes are received by the form view bzr revid: nicolas.vanhoren@openerp.com-20120418095758-x7du906934jbkp90 --- addons/web/static/src/js/view_form.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 976c844c3db..3cda7bf1b4c 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -853,6 +853,11 @@ instance.web.FormView = instance.web.View.extend({ if (this.get_field(name).translate) { this.translatable_fields.push(field); } + field.on('changed_value', this, function() { + this.do_onchange(field); + this.on_form_changed(true); + this.do_notify_change(); + }); }, get_field: function(field_name) { return this.fields_view.fields[field_name]; @@ -1574,7 +1579,7 @@ instance.web.form.FieldManagerInterface = { * - force_readonly: boolean, When it is true, the field should always appear * in read only mode, no matter what the value of the "readonly" property can be. * Events: - * - ... + * - changed_value: called to trigger a on_change in the view * */ instance.web.form.FieldInterface = { @@ -1662,14 +1667,12 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins _.bind(test_effective_readonly, this)(); this.on("change:value", this, function() { - if (this._field_is_started && ! this._inhibit_on_change) + if (! this._inhibit_on_change) this._on_ui_change(); }); }, start: function() { this._super.apply(this, arguments); - // quick hack, will change later - this._field_is_started = true; if (this.field.translate) { this.$element.addClass('oe_form_field_translatable'); this.$element.find('.oe_field_translate').click(this.on_translate); @@ -1723,9 +1726,7 @@ instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends ins _on_ui_change: function() { this.dirty = true; if (this.is_syntax_valid()) { - this.view.do_onchange(this); - this.view.on_form_changed(true); - this.view.do_notify_change(); + this.trigger('changed_value'); } else { this.update_dom(true); } From 49f785c74ec6e6aa5df27439e8bf8860e23df58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 11:58:54 +0200 Subject: [PATCH 16/49] [DOC] Improved doc, adding reference to the needaction_pending field. bzr revid: tde@openerp.com-20120418095854-29o6qupaoi741imr --- doc/api/need_action_specs.rst | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/api/need_action_specs.rst b/doc/api/need_action_specs.rst index 88787ae4c44..de1b75a5fb1 100644 --- a/doc/api/need_action_specs.rst +++ b/doc/api/need_action_specs.rst @@ -1,6 +1,8 @@ Need action mechanism ===================== +.. versionadded:: openobject-server.4124 + ir.needaction_mixin mixin class +++++++++++++++++++++++++++++++ @@ -15,15 +17,37 @@ Objects using the need_action feature should override the ``get_needaction_user_ This class also offers several global services,: - ``needaction_get_record_ids``: for the current model and uid, get all record ids that ask this user to perform an action. This mechanism is used for instance to display the number of pending actions in menus, such as Leads (12) - ``needaction_get_action_count``: as ``needaction_get_record_ids`` but returns only the number of action, not the ids (performs a search with count=True) - - ``needaction_get_user_record_references``: for a given uid, get all the records that ask this user to perform an action. Records are given as references, a list of tuples (model_name, record_id) + - ``needaction_get_user_record_references``: for a given uid, get all the records that ask this user to perform an action. Records are given as references, a list of tuples (model_name, record_id). + +.. versionadded:: openobject-server.XXXX + +This revision of the needaction_mixin mechanism slighty modifies the class behavior. The ``ir_needaction_mixin`` class now adds a function field on model inheriting from the class. This 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. This makes the class not a pure abstract class, but allows to easily use the action information. The field definition is:: + + + 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 + + _columns = { + 'needaction_pending': fields.function(get_needaction_pending, type='boolean', + string='Need action pending', + help='If True, this field states that users have to perform an action. \ + This field comes from the needaction mechanism. Please refer \ + to the ir.needaction_mixin class.'), + } Menu modification +++++++++++++++++ +.. versionchanged:: openobject-server.XXXX + This revision adds three functional fields to ``ir.ui.menu`` model : - ``uses_needaction``: boolean field. If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false. - ``needaction_uid_ctr``: integer field. If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform. - - ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action. + - ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action. **This field has been removed on version XXXX**. Those fields are functional, because they depend on the user and must therefore be computed at every refresh, each time menus are displayed. The use of the need action mechanism is done by taking into account the action domain in order to display accurate results. When computing the value of the functional fields, the ids of records asking the user to perform an action is concatenated to the action domain. A counting search is then performed on the model, giving back the number of action the users has to perform, limited to the domain of the action. From a787430ba943af3a7e7e71b3ddcb0c76d00d5d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 13:10:16 +0200 Subject: [PATCH 17/49] [DOC] Small doc update. bzr revid: tde@openerp.com-20120418111016-fgmvnq85esyhvw87 --- doc/api/need_action_specs.rst | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/doc/api/need_action_specs.rst b/doc/api/need_action_specs.rst index de1b75a5fb1..111c156ac79 100644 --- a/doc/api/need_action_specs.rst +++ b/doc/api/need_action_specs.rst @@ -1,16 +1,18 @@ Need action mechanism ===================== -.. versionadded:: openobject-server.4124 +.. versionadded:: 7.0 -ir.needaction_mixin mixin class -+++++++++++++++++++++++++++++++ +ir.needaction_mixin class ++++++++++++++++++++++++++ + +.. versionadded:: openobject-server.4124 This revision adds a mixin class for objects using the need action feature. Need action feature can be used by objects willing to be able to signal that an action is required on a particular record. If in the business logic an action must be performed by somebody, for instance validation by a manager, this mechanism allows to set a list of users asked to perform an action. -This class wraps a class (ir.needaction_users) that behaves like a many2many field. However, no field is added to the model inheriting from ir.needaction_mixin. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships. +This class wraps a class (ir.ir_needaction_users_rel) that behaves like a many2many field. However, no field is added to the model inheriting from ir.needaction_mixin. The mixin class manages the low-level considerations of updating relationships. Every change made on the record calls a method that updates the relationships. Objects using the need_action feature should override the ``get_needaction_user_ids`` method. This methods returns a dictionary whose keys are record ids, and values a list of user ids, like in a many2many relationship. Therefore by defining only one method, you can specify if an action is required by defining the users that have to do it, in every possible situation. @@ -39,6 +41,23 @@ This revision of the needaction_mixin mechanism slighty modifies the class behav to the ir.needaction_mixin class.'), } +ir.needaction_users_rel class ++++++++++++++++++++++++++++++ + +.. versionadded:: openobject-server.4124 + +This class essentially wraps a database table that behaves like a many2many. +It 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) + Menu modification +++++++++++++++++ From 8d858cf0b02e4e9a053ac3c4dd1438afe1c6278a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 13:12:12 +0200 Subject: [PATCH 18/49] [TMP] Uncommit temp fix. bzr revid: tde@openerp.com-20120418111212-j035lajdu2g045kf --- openerp/addons/base/ir/ir_ui_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index 0de4ff74c11..e861f8231ca 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -264,7 +264,7 @@ class ir_ui_menu(osv.osv): 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) # TODO: find the addon that causes a bug on runbot, not on local - if not isinstance(menu_needaction_res[1], (int, long)): menu_needaction_res[1] = 0 + #if not isinstance(menu_needaction_res[1], (int, long)): menu_needaction_res[1] = 0 else: menu_needaction_res = [False, 0] res[menu.id]['needaction_enabled'] = menu_needaction_res[0] From 78883771bdb16b9d2c4c926162fc1d5dfab0e068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 13:25:43 +0200 Subject: [PATCH 19/49] [DOC] Added comments in code. bzr revid: tde@openerp.com-20120418112543-g5jbcn2h5mgrbxtc --- openerp/addons/base/ir/ir_needaction.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 7ed9762f8db..cb6d29d1715 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -111,7 +111,15 @@ class ir_needaction_mixin(osv.osv): - ``needaction_get_user_record_references``: for a given uid, get all the records that ask this user to perform an action. Records are given as references, a list of tuples (model_name, record_id) - ''' + + The ``ir_needaction_mixin`` class adds a function field on model inheriting + from the class. This 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. This makes the class not a pure abstract class, + but allows to easily use the action information.''' + _name = 'ir.needaction_mixin' _description = '"Need action" mixin' From 3da67c851fa3333215dfa9afb8faa2be032dac11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 13:37:06 +0200 Subject: [PATCH 20/49] [REM] Removed unused field in menus. BTW it solves a GTk client crash. bzr revid: tde@openerp.com-20120418113706-raesdy1tak5dwkwo --- openerp/addons/base/ir/ir_ui_menu.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_ui_menu.py b/openerp/addons/base/ir/ir_ui_menu.py index e861f8231ca..f73496cb230 100644 --- a/openerp/addons/base/ir/ir_ui_menu.py +++ b/openerp/addons/base/ir/ir_ui_menu.py @@ -289,7 +289,6 @@ class ir_ui_menu(osv.osv): 'web_icon_hover_data':fields.function(_get_image_icon, string='Web Icon Image (hover)', type='binary', readonly=True, store=True, multi='icon'), 'needaction_enabled': fields.function(_get_needaction, string='Target model uses the need action mechanism', type='boolean', help='If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false.', multi='_get_needaction'), 'needaction_counter': fields.function(_get_needaction, string='Number of actions the user has to perform', type='integer', help='If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform.', multi='_get_needaction'), - 'needaction_record_ids': fields.function(_get_needaction, string='Ids of records requesting an action from the user', type='many2many', help='If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action.', multi='_get_needaction'), 'action': fields.function(_action, fnct_inv=_action_inv, type='reference', string='Action', selection=[ From d029e2734c65624083eeb593a9684e35d31e7b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 18 Apr 2012 13:37:48 +0200 Subject: [PATCH 21/49] [DOC] Small doc update. bzr revid: tde@openerp.com-20120418113748-vsdtsype2l94wbl5 --- doc/api/need_action_specs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/need_action_specs.rst b/doc/api/need_action_specs.rst index 111c156ac79..a3a8ba04bf0 100644 --- a/doc/api/need_action_specs.rst +++ b/doc/api/need_action_specs.rst @@ -66,7 +66,7 @@ Menu modification This revision adds three functional fields to ``ir.ui.menu`` model : - ``uses_needaction``: boolean field. If the menu entry action is an act_window action, and if this action is related to a model that uses the need_action mechanism, this field is set to true. Otherwise, it is false. - ``needaction_uid_ctr``: integer field. If the target model uses the need action mechanism, this field gives the number of actions the current user has to perform. - - ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action. **This field has been removed on version XXXX**. + - **REMOVED** ``needaction_record_ids``: many2many field. If the target model uses the need action mechanism, this field holds the ids of the record requesting the user to perform an action. **This field has been removed on version XXXX**. Those fields are functional, because they depend on the user and must therefore be computed at every refresh, each time menus are displayed. The use of the need action mechanism is done by taking into account the action domain in order to display accurate results. When computing the value of the functional fields, the ids of records asking the user to perform an action is concatenated to the action domain. A counting search is then performed on the model, giving back the number of action the users has to perform, limited to the domain of the action. From 29d48867ab89e6067fb34b97522095c334040766 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 18 Apr 2012 13:50:04 +0200 Subject: [PATCH 22/49] [imp] changed the way validity is tested bzr revid: nicolas.vanhoren@openerp.com-20120418115004-otz3xocucky9u5gl --- addons/web/static/src/js/view_form.js | 47 ++++++++++----------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 3cda7bf1b4c..fedc602b4b1 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -247,7 +247,7 @@ instance.web.FormView = instance.web.View.extend({ } _(this.fields).each(function (field, f) { - field.reset(); + field._dirty_flag = false; var result = field.set_value(self.datarecord[f] || false); set_values.push(result); }); @@ -258,7 +258,7 @@ instance.web.FormView = instance.web.View.extend({ _.each(self.fields_order, function(field_name) { if (record[field_name] !== undefined) { var field = self.fields[field_name]; - field.dirty = true; + field._dirty_flag = true; self.do_onchange(field); } }); @@ -469,7 +469,7 @@ instance.web.FormView = instance.web.View.extend({ var value_ = result.value[f]; if (field.get_value() != value_) { field.set_value(value_); - field.dirty = true; + field._dirty_flag = true; if (!_.contains(processed, field.name)) { this.do_onchange(field, processed); } @@ -615,7 +615,7 @@ instance.web.FormView = instance.web.View.extend({ if (!first_invalid_field) { first_invalid_field = f; } - } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f.is_dirty())) { + } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) { // Special case 'id' field, do not save this field // on 'create' : save all non readonly fields // on 'edit' : save non readonly modified fields @@ -754,7 +754,7 @@ instance.web.FormView = instance.web.View.extend({ }, is_dirty: function() { return _.any(this.fields, function (value_) { - return value_.is_dirty(); + return value_._dirty_flag; }); }, is_interactible_record: function() { @@ -854,9 +854,11 @@ instance.web.FormView = instance.web.View.extend({ this.translatable_fields.push(field); } field.on('changed_value', this, function() { - this.do_onchange(field); - this.on_form_changed(true); - this.do_notify_change(); + if (field.is_syntax_valid()) { + this.do_onchange(field); + this.on_form_changed(true); + this.do_notify_change(); + } }); }, get_field: function(field_name) { @@ -1628,7 +1630,11 @@ instance.web.form.FieldInterface = { * Inform the current object of the id it should use to match a html