From 5ce0720b7526c335bd277eb71c7cdb4d369e7fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 9 Aug 2012 16:42:03 +0200 Subject: [PATCH 001/108] [IMP] fields: many2many: extracted query generation, to ease overrides. bzr revid: tde@openerp.com-20120809144203-sm93gsl2db608z85 --- openerp/osv/fields.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 4ff23956050..c173a8ba8c4 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -648,6 +648,20 @@ class many2many(_column): col2 = '%s_id' % dest_model._table return (tbl, col1, col2) + def _get_query_and_where_params(self, cr, model, ids, values, where_params): + """ Extracted from ``get`` to facilitate fine-tuning of the generated + query. """ + query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \ + FROM %(rel)s, %(from_c)s \ + WHERE %(rel)s.%(id1)s IN %%s \ + AND %(rel)s.%(id2)s = %(tbl)s.id \ + %(where_c)s \ + %(order_by)s \ + %(limit)s \ + OFFSET %(offset)d' \ + % values + return (query, where_params) + def get(self, cr, model, ids, name, user=None, offset=0, context=None, values=None): if not context: context = {} @@ -685,15 +699,7 @@ class many2many(_column): if self._limit is not None: limit_str = ' LIMIT %d' % self._limit - query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \ - FROM %(rel)s, %(from_c)s \ - WHERE %(rel)s.%(id1)s IN %%s \ - AND %(rel)s.%(id2)s = %(tbl)s.id \ - %(where_c)s \ - %(order_by)s \ - %(limit)s \ - OFFSET %(offset)d' \ - % {'rel': rel, + query, where_params = self._get_query_and_where_params(cr, model, ids, {'rel': rel, 'from_c': from_c, 'tbl': obj._table, 'id1': id1, @@ -702,7 +708,8 @@ class many2many(_column): 'limit': limit_str, 'order_by': order_by, 'offset': offset, - } + }, where_params) + cr.execute(query, [tuple(ids),] + where_params) for r in cr.fetchall(): res[r[1]].append(r[0]) From c510b34778a93c53de069a31256d1015591d5dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 9 Aug 2012 16:43:45 +0200 Subject: [PATCH 002/108] [REF] mail: mail has now a mesage_subscriber_ids field, many2many modified field, to avoid doing asynchronous queries to have them. Added temp debug field on mail_group. bzr revid: tde@openerp.com-20120809144345-y03iqvhxv8jxo59v --- addons/mail/__init__.py | 2 +- addons/mail/mail_group_view.xml | 1 + addons/mail/mail_subscription.py | 5 +++-- addons/mail/mail_thread.py | 23 +++++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/addons/mail/__init__.py b/addons/mail/__init__.py index cbd477f55e6..cef9fe126aa 100644 --- a/addons/mail/__init__.py +++ b/addons/mail/__init__.py @@ -21,9 +21,9 @@ import mail_alias import mail_message +import mail_subscription import mail_thread import mail_group -import mail_subscription import ir_needaction import res_users import res_partner diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index 5c81d426d6a..fd40a3ed739 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -78,6 +78,7 @@ + diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 7ef74641d77..cf33e64dd79 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -965,7 +965,8 @@ class mail_thread(osv.Model): uid instead """ to_subscribe_uids = [uid] if user_ids is None else user_ids - return self.write(cr, uid, ids, {'message_subscriber_ids': [(4, id) for id in to_subscribe_uids]}, context=context) + write_res = self.write(cr, uid, ids, {'message_subscriber_ids': [(4, id) for id in to_subscribe_uids]}, context=context) + return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None): """ Unsubscribe the user (or user_ids) from the current document. @@ -974,7 +975,8 @@ class mail_thread(osv.Model): uid instead """ to_unsubscribe_uids = [uid] if user_ids is None else user_ids - return self.write(cr, uid, ids, {'message_subscriber_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) + write_res = self.write(cr, uid, ids, {'message_subscriber_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) + return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] #------------------------------------------------------ # Notification API diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index b4b8a6e9797..30d83667165 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -22,25 +22,24 @@ openerp_mail_followers = function(session, mail) { init: function() { this._super.apply(this, arguments); - this.params = this.get_definition_options(); - this.params.see_subscribers = true; - this.params.see_subscribers_options = this.params.see_subscribers_options || false; - this.ds = new session.web.DataSetSearch(this, this.view.model); - this.ds_users = new session.web.DataSetSearch(this, 'res.users'); - + debugger + this.params = {}; + this.params.image = this.node.attrs.image || 'image_small'; + this.params.display_followers = true; + this.params.display_control = this.node.attrs.display_control || false; + this.params.display_actions = this.node.attrs.display_actions || false; + this.ds_model = new session.web.DataSetSearch(this, this.view.model); + this.ds_follow = new session.web.DataSetSearch(this, this.field.relation); }, start: function() { var self = this; - // NB: all the widget should be modified to check the actual_mode property on view, not use // any other method to know if the view is in create mode anymore this.view.on("change:actual_mode", this, this._check_visibility); this._check_visibility(); - - // session.mail.ChatterUtils.bind_events(this); this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); }); - if (! this.params.see_subscribers_options) { + if (! this.params.display_control) { this.$element.find('button.oe_mail_button_followers').hide(); } this.$element.find('button.oe_mail_button_follow').click(function () { self.do_follow(); }) .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) @@ -60,16 +59,15 @@ openerp_mail_followers = function(session, mail) { }, reinit: function() { - this.params.see_subscribers = true; - this.params.see_subscribers_options = this.params.see_subscribers_options || false; + this.params.display_followers = true; + this.params.display_control = this.node.attrs.display_control || false; + this.params.display_actions = this.node.attrs.display_actions || false; this.$element.find('button.oe_mail_button_followers').html('Hide followers') this.$element.find('button.oe_mail_button_follow').hide(); this.$element.find('button.oe_mail_button_unfollow').hide(); }, set_value: function(value_) { - console.log(value_); - // debugger this.reinit(); if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { @@ -80,7 +78,7 @@ openerp_mail_followers = function(session, mail) { }, fetch_subscribers: function (value_) { - return this.ds_users.call('read', [value_ || this.get_value(), ['name', 'image_small']]).then(this.proxy('display_subscribers')); + return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers')); }, display_subscribers: function (records) { @@ -102,17 +100,12 @@ openerp_mail_followers = function(session, mail) { }, do_follow: function () { - return this.ds.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('fetch_subscribers')); + return this.ds_model.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); }, do_unfollow: function () { var self = this; - return this.ds.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(function (record) { - // debugger - var new_value = self.view.datarecord.message_subscriber_ids; - // return [new_value.splice(_.indexOf(new_value, self.session.uid, true), 1);] - return [2] - }).pipe(this.proxy('set_value')); + return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); }, do_toggle_followers: function () { From 98b17f6e96d202997605f3f6964a50121cfc6612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 20:09:41 +0200 Subject: [PATCH 023/108] [IMP] mail_thread: many2many_reference: now overrides the 6 command. bzr revid: tde@openerp.com-20120813180941-n0u1xq0dx2cf8rsf --- addons/mail/mail_thread.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index cf33e64dd79..47f74e0ab29 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -62,14 +62,23 @@ class many2many_reference(fields.many2many): continue if act[0] == 0: idnew = obj.create(cr, user, act[2], context=context) - cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+') VALUES (%s,%s,res_model)', (id, idnew, model._name)) + cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, idnew, model._name)) elif act[0] == 3: cr.execute('DELETE FROM "'+rel+'" WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name)) elif act[0] == 4: # following queries are in the same transaction - so should be relatively safe cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name)) if not cr.fetchone(): - cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) values (%s,%s,%s)', (id, act[1], model._name)) + cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act[1], model._name)) + elif act[0] == 6: + d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context) + if d1: + d1 = ' and ' + ' and '.join(d1) + else: + d1 = '' + cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND res_model=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2) + for act_nbr in act[2]: + cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act_nbr, model._name)) else: print act return super(many2many_reference, self).set(cr, model, id, name, values, user, context) From 59d8cb124c28904ef7cd9b75bf314c71e12c2c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 20:33:12 +0200 Subject: [PATCH 024/108] [IMP] mail_thread: do not subscribe followers already subscribed. bzr revid: tde@openerp.com-20120813183312-qpuksy9ku7z5zemx --- addons/mail/mail_thread.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 47f74e0ab29..afe3cf2e381 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -973,7 +973,10 @@ class mail_thread(osv.Model): :param user_ids: a list of user_ids; if not set, subscribe uid instead """ + follower_ids = [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] to_subscribe_uids = [uid] if user_ids is None else user_ids + if all(follower_id in follower_ids for follower_id in to_subscribe_uids): + return follower_ids write_res = self.write(cr, uid, ids, {'message_subscriber_ids': [(4, id) for id in to_subscribe_uids]}, context=context) return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] From ff0a280a7ede102c79d0d3ca047429d196993759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:10:06 +0200 Subject: [PATCH 025/108] [REF] mail.subscription: renamed to mail.followers. Also renamed files accordingly. Views and menu entries have been updated. Also removed an ununsed read field on mail.notification. bzr revid: tde@openerp.com-20120813191006-jf7vygdyb93k7nxf --- ...mail_subscription.py => mail_followers.py} | 21 +++++------- ...ption_view.xml => mail_followers_view.xml} | 32 ++++++++----------- 2 files changed, 21 insertions(+), 32 deletions(-) rename addons/mail/{mail_subscription.py => mail_followers.py} (82%) rename addons/mail/{mail_subscription_view.xml => mail_followers_view.xml} (64%) diff --git a/addons/mail/mail_subscription.py b/addons/mail/mail_followers.py similarity index 82% rename from addons/mail/mail_subscription.py rename to addons/mail/mail_followers.py index eb6f066a2ef..fcdc78d5f0a 100644 --- a/addons/mail/mail_subscription.py +++ b/addons/mail/mail_followers.py @@ -22,19 +22,21 @@ from osv import osv from osv import fields -class mail_subscription(osv.Model): - """ - mail_subscription holds the data related to the follow mechanism inside OpenERP. +class mail_followers(osv.Model): + """ mail_followers holds the data related to the follow mechanism inside + OpenERP. Users can choose to follow documents (records) of any kind that + inherits from mail.thread. Following documents allow to receive + notifications for new messages. A subscription is characterized by: :param: res_model: model of the followed objects :param: res_id: ID of resource (may be 0 for every objects) :param: user_id: user_id of the follower """ - _name = 'mail.subscription' + _name = 'mail.followers' _rec_name = 'id' _log_access = False _order = 'res_model asc' - _description = 'Mail subscription' + _description = 'Mail Document Followers' _columns = { 'res_model': fields.char('Related Document Model', size=128, required=True, select=1, @@ -46,9 +48,7 @@ class mail_subscription(osv.Model): } class mail_notification(osv.Model): - """ - mail_notification is a relational table modeling messages pushed to users. - :param: read: not used currently + """ mail_notification is a relational table modeling messages pushed to users. """ _name = 'mail.notification' _rec_name = 'id' @@ -60,9 +60,4 @@ class mail_notification(osv.Model): ondelete='cascade', required=True, select=1), 'message_id': fields.many2one('mail.message', string='Message', ondelete='cascade', required=True, select=1), - 'read': fields.boolean('Read', help="Not used currently",), - # TODO: add a timestamp ? or use message date ? - } - _defaults = { - 'read': False, } diff --git a/addons/mail/mail_subscription_view.xml b/addons/mail/mail_followers_view.xml similarity index 64% rename from addons/mail/mail_subscription_view.xml rename to addons/mail/mail_followers_view.xml index c3d79d48308..ebb7104c5b3 100644 --- a/addons/mail/mail_subscription_view.xml +++ b/addons/mail/mail_followers_view.xml @@ -2,17 +2,14 @@ - - - - mail.subscription.tree - mail.subscription + + + mail.followers.tree + mail.followers tree 10 - + @@ -20,17 +17,14 @@ - - + mail.notification.tree mail.notification tree 10 - + @@ -38,9 +32,9 @@ - - Subscriptions - mail.subscription + + Followers + mail.followers form tree,form @@ -52,9 +46,9 @@ tree,form - - --> + + --> Date: Mon, 13 Aug 2012 21:13:46 +0200 Subject: [PATCH 026/108] [REF] mail_thread: refactoring of followers continuing. Updated reference to subscription to followers. Also removed message_read_subscribers and message_is_subscriber, not used anymore. bzr revid: tde@openerp.com-20120813191346-cpn09u4xqiesvpwb --- addons/mail/mail_thread.py | 75 +++++++++++--------------------------- 1 file changed, 21 insertions(+), 54 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index afe3cf2e381..d82158ec3b3 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -38,7 +38,7 @@ class many2many_reference(fields.many2many): def _get_query_and_where_params(self, cr, model, ids, values, where_params): """ Add in where: - - mail_subscription.res_model = 'crm.lead' + - mail_followers.res_model = 'crm.lead' """ query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \ FROM %(rel)s, %(from_c)s \ @@ -104,21 +104,20 @@ class mail_thread(osv.Model): default implementation will work for any model. However it is common to override at least the ``message_new`` and ``message_update`` methods (calling ``super``) to add model-specific behavior at - creation and update of a thread; and ``message_get_subscribers`` - to manage more precisely the social aspect of the thread through - the followers. + creation and update of a thread. + + TODO: UPDATE WITIH SUBTYPE / NEW FOLLOW MECHANISM ''' _name = 'mail.thread' _description = 'Email Thread' def _get_message_ids(self, cr, uid, ids, name, args, context=None): res = {} - for id in ids: - message_ids = self.message_search(cr, uid, [id], context=context) - subscriber_ids = self.message_get_subscribers(cr, uid, [id], context=context) + for thread in self.browse(cr, uid, ids, context=context): + message_ids = self.message_search(cr, uid, [thread.id], context=context) res[id] = { 'message_ids': message_ids, - 'message_summary': "9 %d + %d" % (len(message_ids), len(subscriber_ids)), + 'message_summary': "9 %d + %d" % (len(message_ids), len(thread.message_follower_ids)), } return res @@ -133,8 +132,8 @@ class mail_thread(osv.Model): type='one2many', obj='mail.message', _fields_id = 'res_id', string='Temp messages', multi="_get_message_ids", help="Functional field holding messages related to the current document."), - 'message_subscriber_ids': many2many_reference('res.users', - rel='mail_subscription', id1='res_id', id2='user_id', string="Followers", + 'message_follower_ids': many2many_reference('res.users', + rel='mail_followers', id1='res_id', id2='user_id', string="Followers", help="Followers of the document. The followers have full access to " \ "the document details, as well as the conversation."), 'message_state': fields.boolean('Read', @@ -155,7 +154,7 @@ class mail_thread(osv.Model): #------------------------------------------------------ def create(self, cr, uid, vals, context=None): - """Automatically subscribe the creator """ + """ Automatically subscribe the creator """ thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) if thread_id: self.message_subscribe(cr, uid, [thread_id], [uid], context=context) @@ -163,31 +162,24 @@ class mail_thread(osv.Model): def write(self, cr, uid, ids, vals, context=None): """ Override of write to subscribe the writer, except if he has changed - the subscribers (to avoid unsubscribe-->subscribe). """ + the followers (to avoid unfollow-->follow). """ if isinstance(ids, (int, long)): ids = [ids] write_res = super(mail_thread, self).write(cr, uid, ids, vals, context=context) - if write_res and not vals.get('message_subscriber_ids'): + if write_res and not vals.get('message_follower_ids'): self.message_subscribe(cr, uid, ids, [uid], context=context) return write_res; def unlink(self, cr, uid, ids, context=None): - """Override unlink, to automatically delete - - subscriptions - - messages + """Override unlink, to automatically delete messages that are linked with res_model and res_id, not through a foreign key with a 'cascade' ondelete attribute. Notifications will be deleted with messages """ - subscr_obj = self.pool.get('mail.subscription') msg_obj = self.pool.get('mail.message') - # delete subscriptions - subscr_to_del_ids = subscr_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) - subscr_obj.unlink(cr, uid, subscr_to_del_ids, context=context) # delete messages and notifications msg_to_del_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context) msg_obj.unlink(cr, uid, msg_to_del_ids, context=context) - return super(mail_thread, self).unlink(cr, uid, ids, context=context) #------------------------------------------------------ @@ -198,12 +190,11 @@ class mail_thread(osv.Model): """ OpenChatter: wrapper of mail.message create method - creates the mail.message - automatically subscribe the message writer - - push the message to subscribed users + - push the message to followers """ if context is None: context = {} - - message_obj = self.pool.get('mail.message') + notification_obj = self.pool.get('mail.notification') body = vals.get('body_html', '') if vals.get('content_subtype') == 'html' else vals.get('body_text', '') @@ -232,7 +223,6 @@ class mail_thread(osv.Model): return msg_id def message_get_user_ids_to_notify(self, cr, uid, thread_ids, new_msg_vals, context=None): - subscription_obj = self.pool.get('mail.subscription') # get body body = new_msg_vals.get('body_html', '') if new_msg_vals.get('content_subtype') == 'html' else new_msg_vals.get('body_text', '') @@ -939,46 +929,23 @@ class mail_thread(osv.Model): """ Returns the current document followers. Basically this method checks in mail.subscription for entries with matching res_model, res_id. - This method can be overriden to add implicit subscribers, such - as project managers, by adding their user_id to the list of - ids returned by this method. """ - subscr_obj = self.pool.get('mail.subscription') + subscr_obj = self.pool.get('mail.followers') subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) return [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)] - def message_read_subscribers(self, cr, uid, ids, fields=['id', 'name', 'image_small'], context=None): - """ Returns the current document followers as a read result. Used - mainly for Chatter having only one method to call to have - details about users. - """ - user_ids = self.message_get_subscribers(cr, uid, ids, context=context) - return self.pool.get('res.users').read(cr, uid, user_ids, fields=fields, context=context) - - def message_is_subscriber(self, cr, uid, ids, user_id = None, context=None): - """ Check if uid or user_id (if set) is a subscriber to the current - document. - - :param user_id: if set, check is done on user_id; if not set - check is done on uid - """ - sub_user_id = uid if user_id is None else user_id - if sub_user_id in self.message_get_subscribers(cr, uid, ids, context=context): - return True - return False - def message_subscribe(self, cr, uid, ids, user_ids = None, context=None): """ Subscribe the user (or user_ids) to the current document. :param user_ids: a list of user_ids; if not set, subscribe uid instead """ - follower_ids = [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] + follower_ids = [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] to_subscribe_uids = [uid] if user_ids is None else user_ids if all(follower_id in follower_ids for follower_id in to_subscribe_uids): return follower_ids - write_res = self.write(cr, uid, ids, {'message_subscriber_ids': [(4, id) for id in to_subscribe_uids]}, context=context) - return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] + write_res = self.write(cr, uid, ids, {'message_follower_ids': [(4, id) for id in to_subscribe_uids]}, context=context) + return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None): """ Unsubscribe the user (or user_ids) from the current document. @@ -987,8 +954,8 @@ class mail_thread(osv.Model): uid instead """ to_unsubscribe_uids = [uid] if user_ids is None else user_ids - write_res = self.write(cr, uid, ids, {'message_subscriber_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) - return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_subscriber_ids] + write_res = self.write(cr, uid, ids, {'message_follower_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) + return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] #------------------------------------------------------ # Notification API From 9a3680e7f556ca20b6149cdff4555e141321c32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:15:01 +0200 Subject: [PATCH 027/108] [REF] mail: init and openerp files updated with followers instead of subscription. bzr revid: tde@openerp.com-20120813191501-tapi42znpz9bckiy --- addons/mail/__init__.py | 2 +- addons/mail/__openerp__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mail/__init__.py b/addons/mail/__init__.py index cef9fe126aa..6e3baad57a8 100644 --- a/addons/mail/__init__.py +++ b/addons/mail/__init__.py @@ -21,7 +21,7 @@ import mail_alias import mail_message -import mail_subscription +import mail_followers import mail_thread import mail_group import ir_needaction diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py index 5c9e4fdf1f8..0e1caabcf65 100644 --- a/addons/mail/__openerp__.py +++ b/addons/mail/__openerp__.py @@ -64,7 +64,7 @@ The main features of the module are: 'wizard/mail_compose_message_view.xml', 'res_config_view.xml', 'mail_message_view.xml', - 'mail_subscription_view.xml', + 'mail_followers_view.xml', 'mail_thread_view.xml', 'mail_group_view.xml', 'res_partner_view.xml', From c14a28fd934eec186c51220574e3e30e5409aa0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:24:27 +0200 Subject: [PATCH 028/108] [MISC] mail_thread: updated one comment. bzr revid: tde@openerp.com-20120813192427-vw2id9wtd1hil9as --- addons/mail/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index d82158ec3b3..f6303fca841 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -927,7 +927,7 @@ class mail_thread(osv.Model): def message_get_subscribers(self, cr, uid, ids, context=None): """ Returns the current document followers. Basically this method - checks in mail.subscription for entries with matching res_model, + checks in mail.followers for entries with matching res_model, res_id. """ subscr_obj = self.pool.get('mail.followers') From a52b57acf4369a1b5652d31f7b0756c8e6701a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:38:41 +0200 Subject: [PATCH 029/108] [REF] Still refactoring of names: propagated to security files. bzr revid: tde@openerp.com-20120813193841-yavky2odabj5li70 --- addons/mail/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 3677d424c14..1979e2fec05 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -2,7 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0 access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1 access_mail_thread,mail.thread,model_mail_thread,base.group_user,1,1,1,0 -access_mail_subscription_all,mail.subscription.all,model_mail_subscription,,1,1,1,1 +access_mail_followers_all,mail.followers.all,model_mail_followers,,1,1,1,1 access_mail_notification_all,mail.notification.all,model_mail_notification,,1,1,1,1 access_mail_group,mail.group,model_mail_group,base.group_user,1,1,1,1 access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0 From f6155e6fa071e85084d8db2f8c29238b9c73d092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:39:10 +0200 Subject: [PATCH 030/108] [FIX] mail_followers_view: deleted read field from view, because was deleted from modele. bzr revid: tde@openerp.com-20120813193910-5rnsgm0wf3hikkss --- addons/mail/mail_followers_view.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/mail/mail_followers_view.xml b/addons/mail/mail_followers_view.xml index ebb7104c5b3..b324cfb1ae9 100644 --- a/addons/mail/mail_followers_view.xml +++ b/addons/mail/mail_followers_view.xml @@ -27,7 +27,6 @@ - From 882eff5556cfeca8a0e8611e1a87989e1bbe9ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:40:37 +0200 Subject: [PATCH 031/108] [REF] mail.group: continuing the refactoring. member_ids field deleted, because this is now a standard field on mail.thread. Updated view and methods accordingly. bzr revid: tde@openerp.com-20120813194037-qm2xamhgnos3gqpt --- addons/mail/mail_group.py | 30 ++++++++++-------------------- addons/mail/mail_group_view.xml | 2 +- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index b850f1c29f7..fb7c0b53b29 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -57,25 +57,17 @@ class mail_group(osv.osv): def _set_image(self, cr, uid, id, name, value, args, context=None): return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) - def get_member_ids(self, cr, uid, ids, field_names, args, context=None): + def get_followers_data(self, cr, uid, ids, field_names, args, context=None): if context is None: context = {} result = dict.fromkeys(ids) - for id in ids: - result[id] = {} - result[id]['member_ids'] = self.message_get_subscribers(cr, uid, [id], context=context) - result[id]['member_count'] = len(result[id]['member_ids']) - result[id]['is_subscriber'] = uid in result[id]['member_ids'] + for thread in self.browse(cr, uid, ids, context=context): + result[thread.id] = { + 'member_count': len(thread.message_follower_ids), + 'is_subscriber': uid in [follower.id for follower in thread.message_follower_ids], + } return result - def search_member_ids(self, cr, uid, obj, name, args, context=None): - if context is None: - context = {} - sub_obj = self.pool.get('mail.subscription') - sub_ids = sub_obj.search(cr, uid, ['&', ('res_model', '=', obj._name), ('user_id', '=', args[0][2])], context=context) - subs = sub_obj.read(cr, uid, sub_ids, context=context) - return [('id', 'in', map(itemgetter('res_id'), subs))] - def get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None): result = {} message_obj = self.pool.get('mail.message') @@ -121,12 +113,10 @@ class mail_group(osv.osv): help="Small-sized photo of the group. It is automatically "\ "resized as a 50x50px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required."), - 'member_ids': fields.function(get_member_ids, fnct_search=search_member_ids, - type='many2many', relation='res.users', string='Group members', multi='get_member_ids'), - 'member_count': fields.function(get_member_ids, type='integer', - string='Member count', multi='get_member_ids'), - 'is_subscriber': fields.function(get_member_ids, type='boolean', - string='Joined', multi='get_member_ids'), + 'member_count': fields.function(get_followers_data, type='integer', + string='Member count', multi='get_followers_data'), + 'is_subscriber': fields.function(get_followers_data, type='boolean', + string='Joined', multi='get_followers_data'), 'last_month_msg_nbr': fields.function(get_last_month_msg_nbr, type='integer', string='Messages count for last month'), 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index 9facf51febe..92fdd795359 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -82,7 +82,7 @@
-
From 2ae73a76154969a03656f5565a0044a01343946c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:41:15 +0200 Subject: [PATCH 032/108] [REF] mail_thread: updated message_ids and message_summary computation due to changes in followers. bzr revid: tde@openerp.com-20120813194115-j125ubffz7alghzv --- addons/mail/mail_thread.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index f6303fca841..1e91b73cae5 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -112,13 +112,11 @@ class mail_thread(osv.Model): _description = 'Email Thread' def _get_message_ids(self, cr, uid, ids, name, args, context=None): - res = {} + res = dict.fromkeys(ids) + for id in ids: + res[id] = {'message_ids': self.message_search(cr, uid, [id], context=context)} for thread in self.browse(cr, uid, ids, context=context): - message_ids = self.message_search(cr, uid, [thread.id], context=context) - res[id] = { - 'message_ids': message_ids, - 'message_summary': "9 %d + %d" % (len(message_ids), len(thread.message_follower_ids)), - } + res[thread.id]['message_summary'] = "9 %d + %d" % (len(res[thread.id]['message_ids']), len(thread.message_follower_ids)) return res def _search_message_ids(self, cr, uid, obj, name, args, context=None): @@ -203,7 +201,7 @@ class mail_thread(osv.Model): self.message_subscribe(cr, uid, [thread_id], [vals['user_id']], context=context) # create message - msg_id = message_obj.create(cr, uid, vals, context=context) + msg_id = self.pool.get('mail.message').create(cr, uid, vals, context=context) # Set as unread if writer is not the document responsible self.message_create_set_unread(cr, uid, [thread_id], context=context) @@ -218,7 +216,7 @@ class mail_thread(osv.Model): notification_obj.create(cr, uid, {'user_id': id, 'message_id': msg_id}, context=context) # create the email to send - email_id = self.message_create_notify_by_email(cr, uid, vals, user_to_push_ids, context=context) + self.message_create_notify_by_email(cr, uid, vals, user_to_push_ids, context=context) return msg_id From f397e44b2f2c23cb4ffb696c82e97f4a16f9b2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 13 Aug 2012 21:54:19 +0200 Subject: [PATCH 033/108] [FIX] portal: mail_subscription -> mail_followers. bzr revid: tde@openerp.com-20120813195419-c1j3r95wyj7fb0hj --- addons/portal/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/portal/security/ir.model.access.csv b/addons/portal/security/ir.model.access.csv index 37302891333..56b3ae5f269 100644 --- a/addons/portal/security/ir.model.access.csv +++ b/addons/portal/security/ir.model.access.csv @@ -6,7 +6,7 @@ access_widget_manager,access.portal.widget.manager,model_res_portal_widget,group access_mail_message,mail.message,mail.model_mail_message,group_portal_member,1,0,1,1 access_mail_message_all,mail.message.all,mail.model_mail_message,group_portal_member,1,0,0,0 access_mail_thread,mail.thread,mail.model_mail_thread,group_portal_member,1,0,0,0 -access_mail_subscription,mail.subscription,mail.model_mail_subscription,group_portal_member,1,0,1,1 +access_mail_followers,mail.followers,mail.model_mail_followers,group_portal_member,1,0,1,1 access_mail_notification,mail.notification,mail.model_mail_notification,group_portal_member,1,0,1,0 access_mail_group,mail.group,mail.model_mail_group,group_portal_member,1,0,0,0 access_mail_alias,mail.alias,mail.model_mail_alias,group_portal_member,1,0,0,0 From b1e5c9e1ae1b997a44d2c273768cee4a41909b93 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 14 Aug 2012 12:40:52 +0200 Subject: [PATCH 034/108] [FIX] auth_openid: use set_cookie_and_redirect + handle errors correctly bzr revid: chs@openerp.com-20120814104052-tw5zgqu5nb12zj5c --- addons/auth_openid/controllers/main.py | 12 +++++------- addons/auth_openid/static/src/js/auth_openid.js | 16 +++++++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/addons/auth_openid/controllers/main.py b/addons/auth_openid/controllers/main.py index d77ef666aa4..eba711dae7a 100644 --- a/addons/auth_openid/controllers/main.py +++ b/addons/auth_openid/controllers/main.py @@ -28,6 +28,7 @@ import werkzeug.urls import werkzeug.exceptions from openerp.modules.registry import RegistryManager +from openerp.addons.web.controllers.main import login_and_redirect, set_cookie_and_redirect try: import openerp.addons.web.common.http as openerpweb except ImportError: @@ -159,7 +160,7 @@ class OpenIDController(openerpweb.Controller): def process(self, req, **kw): session = getattr(req.session, 'openid_session', None) if not session: - return werkzeug.utils.redirect('/') + return set_cookie_and_redirect(req, '/') oidconsumer = consumer.Consumer(session, self._store, consumer_class=GoogleAppsAwareConsumer) @@ -168,7 +169,6 @@ class OpenIDController(openerpweb.Controller): display_identifier = info.getDisplayIdentifier() session['status'] = info.status - user_id = None if info.status == consumer.SUCCESS: dbname = session['dbname'] @@ -206,10 +206,9 @@ class OpenIDController(openerpweb.Controller): # TODO fill empty fields with the ones from sreg/ax cr.commit() - req.session.authenticate(dbname, login, key, {}) + return login_and_redirect(req, dbname, login, key) - if not user_id: - session['message'] = 'This OpenID identifier is not associated to any active users' + session['message'] = 'This OpenID identifier is not associated to any active users' elif info.status == consumer.SETUP_NEEDED: session['message'] = info.setup_url @@ -223,8 +222,7 @@ class OpenIDController(openerpweb.Controller): # information in a log. session['message'] = 'Verification failed.' - fragment = '#loginerror' if not user_id else '' - return werkzeug.utils.redirect('/' + fragment) + return set_cookie_and_redirect(req, '/#action=login&loginerror=1') @openerpweb.jsonrequest def status(self, req): diff --git a/addons/auth_openid/static/src/js/auth_openid.js b/addons/auth_openid/static/src/js/auth_openid.js index fd6ec01cc0f..1417bc471ce 100644 --- a/addons/auth_openid/static/src/js/auth_openid.js +++ b/addons/auth_openid/static/src/js/auth_openid.js @@ -8,7 +8,7 @@ instance.web.Login = instance.web.Login.extend({ this._super.apply(this, arguments); var self = this; - this._default_error_message = this.$element.find('.login_error_message').text(); + this._default_error_message = this.$element.find('.oe_login_error_message').text(); this.$openid_selected_button = $(); this.$openid_selected_input = $(); @@ -40,8 +40,7 @@ instance.web.Login = instance.web.Login.extend({ } }); - this._check_fragment(); - + this._check_error(); }, @@ -67,11 +66,9 @@ instance.web.Login = instance.web.Login.extend({ }, - _check_fragment: function() { + _check_error: function() { var self = this; - var fragment = jQuery.deparam.fragment(); - console.log(fragment); - if (fragment.loginerror !== undefined) { + if (this.params.loginerror !== undefined) { this.rpc('/auth_openid/login/status', {}, function(result) { if (_.contains(['success', 'failure'], result.status) && result.message) { self.do_warn('Invalid OpenID Login', result.message); @@ -132,11 +129,12 @@ instance.web.Login = instance.web.Login.extend({ do_warn: function(title, msg) { //console.warn(title, msg); - this.$element.find('.login_error_message').text(msg).show(); + this.$element.find('.oe_login_error_message').text(msg).show(); + this._super(title, msg); }, reset_error_message: function() { - this.$element.find('.login_error_message').text(this._default_error_message); + this.$element.find('.oe_login_error_message').text(this._default_error_message); } }); From 70f8659e7876a8b2a1ec64fac1d2f22a1d21f9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Aug 2012 13:39:37 +0200 Subject: [PATCH 035/108] [IMP] mail_thread: method to calculate mail values is now _get_message_data; added message_is_follower field, with its computation; cleaned a bit the code. bzr revid: tde@openerp.com-20120814113937-a7279ud2rj5fixl9 --- addons/mail/mail_thread.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index fbbac88639b..efba082fee4 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -106,17 +106,22 @@ class mail_thread(osv.Model): methods (calling ``super``) to add model-specific behavior at creation and update of a thread. - TODO: UPDATE WITIH SUBTYPE / NEW FOLLOW MECHANISM + #TODO: UPDATE WITH SUBTYPE / NEW FOLLOW MECHANISM ''' _name = 'mail.thread' _description = 'Email Thread' - def _get_message_ids(self, cr, uid, ids, name, args, context=None): + def _get_message_data(self, cr, uid, ids, field_names, args, context=None): res = dict.fromkeys(ids) for id in ids: res[id] = {'message_ids': self.message_search(cr, uid, [id], context=context)} for thread in self.browse(cr, uid, ids, context=context): - res[thread.id]['message_summary'] = "9 %d + %d" % (len(res[thread.id]['message_ids']), len(thread.message_follower_ids)) + message_follower_ids = [follower.id for follower in thread.message_follower_ids] + res[thread.id].update({ + 'message_is_follower': uid in message_follower_ids, + 'message_summary': "9 %d + %d" % + (len(res[thread.id]['message_ids']), len(thread.message_follower_ids)) + }) return res def _search_message_ids(self, cr, uid, obj, name, args, context=None): @@ -125,19 +130,22 @@ class mail_thread(osv.Model): return [('id', 'in', msg_ids)] _columns = { - 'message_ids': fields.function(_get_message_ids, + 'message_ids': fields.function(_get_message_data, fnct_search=_search_message_ids, type='one2many', obj='mail.message', _fields_id = 'res_id', - string='Temp messages', multi="_get_message_ids", + string='Temp messages', multi="_get_message_data", help="Functional field holding messages related to the current document."), 'message_follower_ids': many2many_reference('res.users', rel='mail_followers', id1='res_id', id2='user_id', string="Followers", help="Followers of the document. The followers have full access to " \ "the document details, as well as the conversation."), + 'message_is_follower': fields.function(_get_message_data, method=True, + type='boolean', string='I am Follower', multi='_get_message_data', + help='True if the current user is following the current document.'), 'message_state': fields.boolean('Read', help="When checked, new messages require your attention."), - 'message_summary': fields.function(_get_message_ids, method=True, - type='text', string='Summary', multi="_get_message_ids", + 'message_summary': fields.function(_get_message_data, method=True, + type='text', string='Summary', multi='_get_message_data', help="Holds the Chatter summary (number of messages, ...). "\ "This summary is directly in html format in order to "\ "be inserted in kanban views."), From 4dd85980ec251de04343e2330997b1a4b14e5895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Aug 2012 13:40:21 +0200 Subject: [PATCH 036/108] [IMP] mail_group: removed fields now delegated to mail_thread; updated doc and kanban view accordingly. bzr revid: tde@openerp.com-20120814114021-vwiz24yu2mo75jt7 --- addons/mail/mail_group.py | 27 +++------------------------ addons/mail/mail_group_view.xml | 14 +++++++++----- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index fb7c0b53b29..82dcb4bc50b 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -29,18 +29,12 @@ import tools from tools.translate import _ from lxml import etree -class mail_group(osv.osv): +class mail_group(osv.Model): """ A mail_group is a collection of users sharing messages in a discussion group. Group users are users that follow the mail group, using the subscription/follow mechanism of OpenSocial. A mail group has nothing in common with res.users.group. - Additional information on fields: - - ``member_ids``: user member of the groups are calculated with - ``message_get_subscribers`` method from mail.thread - - ``member_count``: calculated with member_ids - - ``is_subscriber``: calculated with member_ids - """ _description = 'Discussion group' @@ -57,18 +51,7 @@ class mail_group(osv.osv): def _set_image(self, cr, uid, id, name, value, args, context=None): return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) - def get_followers_data(self, cr, uid, ids, field_names, args, context=None): - if context is None: - context = {} - result = dict.fromkeys(ids) - for thread in self.browse(cr, uid, ids, context=context): - result[thread.id] = { - 'member_count': len(thread.message_follower_ids), - 'is_subscriber': uid in [follower.id for follower in thread.message_follower_ids], - } - return result - - def get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None): + def _get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None): result = {} message_obj = self.pool.get('mail.message') for id in ids: @@ -113,11 +96,7 @@ class mail_group(osv.osv): help="Small-sized photo of the group. It is automatically "\ "resized as a 50x50px image, with aspect ratio preserved. "\ "Use this field anywhere a small image is required."), - 'member_count': fields.function(get_followers_data, type='integer', - string='Member count', multi='get_followers_data'), - 'is_subscriber': fields.function(get_followers_data, type='boolean', - string='Joined', multi='get_followers_data'), - 'last_month_msg_nbr': fields.function(get_last_month_msg_nbr, type='integer', + 'last_month_msg_nbr': fields.function(_get_last_month_msg_nbr, type='integer', string='Messages count for last month'), 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, help="The email address associated with this group. New emails received will automatically " diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index 92fdd795359..4f25ba300be 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -10,6 +10,8 @@ + +
@@ -17,17 +19,19 @@
-
+

-
From 19abc35ce640fae67dc13126c829913e93f4158a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Aug 2012 13:52:22 +0200 Subject: [PATCH 037/108] [CLEAN] Cleaned mail_group and mail.js due to the merge of trunk. bzr revid: tde@openerp.com-20120814115222-gxunwfuttejrock0 --- addons/mail/mail_group.py | 12 ++++++------ addons/mail/static/src/js/mail.js | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index d9519caf6d3..84008548d96 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -64,21 +64,21 @@ class mail_group(osv.Model): return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64')) _columns = { - #'name': fields.char('Group Name', size=64, required=True), 'description': fields.text('Description'), 'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"), 'responsible_id': fields.many2one('res.users', string='Responsible', ondelete='set null', required=True, select=1, help="Responsible of the group that has all rights on the record."), - 'public': fields.selection([('public','Public'),('private','Private'),('groups','Selected Group Only')], 'Privacy', required=True, - help='This group is visible by non members. \ - Invisible groups can add members through the invite button.'), + 'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')], + string='Privacy', required=True, + help='This group is visible by non members. '\ + 'Invisible groups can add members through the invite button.'), 'group_public_id': fields.many2one('res.groups', string='Authorized Group'), 'group_ids': fields.many2many('res.groups', rel='mail_group_res_group_rel', id1='mail_group_id', id2='groups_id', string='Auto Subscription', help="Members of those groups will automatically added as followers. "\ - "Note that they will be able to manage their subscription manually "\ - "if necessary."), + "Note that they will be able to manage their subscription manually "\ + "if necessary."), 'image': fields.binary("Photo", help="This field holds the image used as photo for the "\ "user. The image is base64 encoded, and PIL-supported. "\ diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 500d0b9c138..16404b28594 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -911,7 +911,6 @@ openerp.mail = function(session) { this.params.res_id = params.res_id || false; this.params.search_view_id = params.search_view_id || false; this.params.thread_level = params.thread_level || 1; - this.params.title = params.title || false; this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}}; this.display_show_more = true; this.thread_list = []; @@ -956,7 +955,6 @@ openerp.mail = function(session) { if (this.compose_message_widget) { this.compose_message_widget.destroy(); } - debugger; this.compose_message_widget = new mail.ComposeMessage(this, { 'extended_mode': false, 'uid': this.session.uid, 'res_model': this.params.res_model, 'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id }); From ebc010fd39507ff60d950e9866325d16c8e7fca2 Mon Sep 17 00:00:00 2001 From: "vta vta@openerp.com" <> Date: Tue, 14 Aug 2012 13:55:55 +0200 Subject: [PATCH 038/108] [FIX] Fixed auth_oauth method bzr revid: vta@openerp.com-20120814115555-qyveeb9fibs598rh --- addons/auth_oauth/controllers/main.py | 20 ++++---- addons/auth_oauth/res_users.py | 69 ++++++++++++++------------- addons/auth_oauth/res_users.xml | 23 +++++++++ addons/auth_signup/res_users.py | 51 ++++++++------------ 4 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 addons/auth_oauth/res_users.xml diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index e0284638291..381602dff46 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -1,5 +1,4 @@ import logging -import urllib2 import werkzeug.urls import werkzeug.utils @@ -13,25 +12,30 @@ _logger = logging.getLogger(__name__) class OAuthController(openerpweb.Controller): _cp_path = '/auth_oauth' + def list_providers(self, req, dbname): + #dbname = kw.get("state") + #registry = openerp.modules.registry.RegistryManager.get(dbname) + #with registry.cursor() as cr: + # dsfasdf + pass + @openerpweb.httprequest def signin(self, req, **kw): dbname = kw.get("state") registry = openerp.modules.registry.RegistryManager.get(dbname) - cr = registry.db.cursor() - try: + with registry.cursor() as cr: try: u = registry.get('res.users') - r = u.auth_oauth(cr, 1, kw) + credentials = u.auth_oauth(cr, 1, kw) cr.commit() - return openerp.addons.web.controllers.main.login_and_redirect(req, *r) + return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) except AttributeError: # auth_signup is not installed url = "/#action=auth_signup&error=1" except Exception,e: # signup error url = "/#action=auth_signup&error=2" - finally: - cr.close() - return werkzeug.utils.redirect("https://localhost") + return werkzeug.utils.redirect("http://localhost:8069") + # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 5349cbf529e..98bd020a8c0 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -17,19 +17,19 @@ class res_users(osv.Model): readonly=True), } - def auth_oauth_rpc(self, cr, uid, endpoint, params, context=None): - url = endpoint + params.get('access_token') + def auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None): + url = endpoint + access_token f = urllib2.urlopen(url) - validation = f.read() - return simplejson.loads(validation) + response = f.read() + return simplejson.loads(response) - def auth_oauth_fetch_user_validation(self, cr, uid, params, context=None): + def auth_oauth_fetch_user_validation(self, cr, uid, access_token, context=None): endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' - return self.auth_oauth_rpc(cr, uid, endpoint, params) + return self.auth_oauth_rpc(cr, uid, endpoint, access_token) - def auth_oauth_fetch_user_data(self, cr, uid, params): + def auth_oauth_fetch_user_data(self, cr, uid, access_token, context=None): endpoint = 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' - return self.auth_oauth_rpc(cr, uid, endpoint, params) + return self.auth_oauth_rpc(cr, uid, endpoint, access_token) def auth_oauth(self, cr, uid, params, context=None): # Advice by Google (to avoid Confused Deputy Problem) @@ -37,33 +37,36 @@ class res_users(osv.Model): # abort() # else: # continue with the process - validation = self.auth_oauth_fetch_user_validation(cr, uid, params) + + access_token = params.get('access_token') + + validation = self.auth_oauth_fetch_user_validation(cr, uid, access_token, context=context) + if validation.get("error"): + raise openerp.exceptions.AccessDenied + login = validation['email'] oauth_uid = validation['user_id'] name = self.auth_oauth_fetch_user_data(cr, uid, params)['name'] - r = (cr.dbname, login, oauth_uid) - try: - # check for existing user - if not self.auth_signup_check(cr, uid, login, oauth_uid): - # new user - new_user = { - 'name': name, - 'login': login, - 'user_email': login, - 'password': oauth_uid, - 'oauth_provider': 'Google', - 'oauth_uid': oauth_uid, - 'oauth_access_token': params.get('access_token'), - 'active': True, - } - self.auth_signup_create(cr, uid, new_user) - return r - else: - # already existing with same password - return r - except openerp.exceptions.AccessDenied: - # already existing with diffrent password - raise + + r = (cr.dbname, login, access_token) + + res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) + if res: + self.write(cr, uid, res[0]['id'], {'oauth_access_token':access_token}) + else: + # New user + new_user = { + 'name': name, + 'login': login, + 'user_email': login, + 'oauth_provider': 'Google', + 'oauth_uid': oauth_uid, + 'oauth_access_token': access_token, + 'active': True, + } + self.auth_signup_create(cr, uid, new_user) + return r + def check(self, db, uid, passwd): try: @@ -77,7 +80,7 @@ class res_users(osv.Model): cr.execute('''SELECT COUNT(1) FROM res_users WHERE id=%s - AND oauth_key=%s + AND oauth_access_token=%s AND active=%s''', (int(uid), passwd, True)) if not cr.fetchone()[0]: diff --git a/addons/auth_oauth/res_users.xml b/addons/auth_oauth/res_users.xml new file mode 100644 index 00000000000..995dbfbd52a --- /dev/null +++ b/addons/auth_oauth/res_users.xml @@ -0,0 +1,23 @@ + + + + + res.users.form + res.users + form + + + + + + + + + + + + + + + + diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index 0076787cc6d..1d77a3f9473 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -19,37 +19,28 @@ class res_users(osv.Model): else: self.pool.get('res.users').create(cr, 1, new_user, context=context) - def auth_signup_check(self, cr, uid, login, key, context=None): - res = self.search(cr, uid, [("login", "=", login)]) - if res: - user_id = res[0] - self.check(cr.dbname, user_id, key) - return user_id - return False - def auth_signup(self, cr, uid, name, login, password, context=None): r = (cr.dbname, login, password) - try: - # check for existing user - if not self.auth_signup_check(cr, uid, login, password): - print "NEW USER" - # new user - new_user = { - 'name': name, - 'login': login, - 'user_email': login, - 'password': password, - 'active': True, - } - self.auth_signup_create(cr, uid, new_user) - return r - else: - print "Existing same" - # already existing with same password - return r - except openerp.exceptions.AccessDenied: - print "Existing different" - # already existing with diffrent password - raise + res = self.search(cr, uid, [("login", "=", login)]) + if res: + # Existing user + user_id = res[0] + try: + self.check(cr.dbname, user_id, password) + # Same password + except openerp.exceptions.AccessDenied: + # Different password + raise + else: + # New user + new_user = { + 'name': name, + 'login': login, + 'user_email': login, + 'password': password, + 'active': True, + } + self.auth_signup_create(cr, uid, new_user) + return r # From 89e404301bdaa57dc2bbe351bfefac993ade9b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Aug 2012 14:12:02 +0200 Subject: [PATCH 039/108] [CLEAN] mail_group_menu: cleaned file, added some doc. bzr revid: tde@openerp.com-20120814121202-zuhu88g95p78ggdc --- addons/mail/mail_group_menu.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/addons/mail/mail_group_menu.py b/addons/mail/mail_group_menu.py index 38756f69928..8625285d640 100644 --- a/addons/mail/mail_group_menu.py +++ b/addons/mail/mail_group_menu.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2010-today OpenERP SA () +# Copyright (C) 2012-today OpenERP SA () # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -24,21 +24,29 @@ from osv import fields from tools.translate import _ class ir_ui_menu(osv.osv): + """ Override of ir.ui.menu class. When adding mail_thread module, each + new mail.group will create a menu entry. This overrides checks that + the current user is in the mail.group followers. If not, the menu + entry is taken off the list of menu ids. This way the user will see + menu entries for the mail.group he is following. + """ _inherit = 'ir.ui.menu' + _columns = { 'mail_group_id': fields.many2one('mail.group', 'Mail Group') } - def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): - ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False) - subs = self.pool.get('mail.subscription') + def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): + """ Override to take off menu entries (mail.group) the user is not + following. """ + ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False) + follower_obj = self.pool.get('mail.followers') for menu in self.browse(cr, uid, ids, context=context): if menu.mail_group_id: - sub_ids = subs.search(cr, uid, [ - ('user_id','=',uid),('res_model','=','mail.group'), - ('res_id','=',menu.mail_group_id.id) + sub_ids = follower_obj.search(cr, uid, [ + ('user_id', '=', uid), ('res_model', '=', 'mail.group'), + ('res_id', '=', menu.mail_group_id.id) ], context=context) if not sub_ids: ids.remove(menu.id) return ids - From c070b68d311ec89be506532021210a3f5e6783f4 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 14 Aug 2012 14:15:12 +0200 Subject: [PATCH 040/108] [IMP] portal: customize events menu tooltips bzr revid: abo@openerp.com-20120814121512-0kmgpw5p31bi1mnw --- addons/portal_event/portal_event_view.xml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) mode change 100644 => 100755 addons/portal_event/portal_event_view.xml diff --git a/addons/portal_event/portal_event_view.xml b/addons/portal_event/portal_event_view.xml old mode 100644 new mode 100755 index 8b1f21bbef0..1c6d80fb807 --- a/addons/portal_event/portal_event_view.xml +++ b/addons/portal_event/portal_event_view.xml @@ -2,8 +2,23 @@ + + + Events + ir.actions.act_window + event.event + form + kanban,calendar,tree,form,graph + {"search_default_upcoming":1} + + This company has no public event so far. + + + action="action_event_view" sequence="40"/> From ba5168192a63eba549ce0172b9127c2ba8468671 Mon Sep 17 00:00:00 2001 From: "vta vta@openerp.com" <> Date: Tue, 14 Aug 2012 14:46:36 +0200 Subject: [PATCH 041/108] [FIX] main.py bzr revid: vta@openerp.com-20120814124636-nhv4iurcc9phe3cm --- addons/auth_oauth/controllers/main.py | 4 +++- addons/auth_oauth/res_users.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 381602dff46..664d510e65f 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -31,11 +31,13 @@ class OAuthController(openerpweb.Controller): return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) except AttributeError: # auth_signup is not installed + _logger.exception("attribute error") url = "/#action=auth_signup&error=1" except Exception,e: # signup error + _logger.exception('oops') url = "/#action=auth_signup&error=2" - return werkzeug.utils.redirect("http://localhost:8069") + return openerp.addons.web.controllers.main.set_cookie_and_redirect(req, "/") # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 98bd020a8c0..4cae3bd4880 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -1,3 +1,5 @@ +import logging + import urllib2 import simplejson @@ -5,6 +7,8 @@ import openerp from openerp.osv import osv, fields +_logger = logging.getLogger(__name__) + class res_users(osv.Model): _inherit = 'res.users' @@ -46,13 +50,14 @@ class res_users(osv.Model): login = validation['email'] oauth_uid = validation['user_id'] - name = self.auth_oauth_fetch_user_data(cr, uid, params)['name'] + name = self.auth_oauth_fetch_user_data(cr, uid, access_token)['name'] r = (cr.dbname, login, access_token) - + res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) + _logger.exception(res) if res: - self.write(cr, uid, res[0]['id'], {'oauth_access_token':access_token}) + self.write(cr, uid, res[0], {'oauth_access_token':access_token}) else: # New user new_user = { From 0dd635d60376949cfc750f3dff531aca2a1cec64 Mon Sep 17 00:00:00 2001 From: "vta vta@openerp.com" <> Date: Tue, 14 Aug 2012 15:13:22 +0200 Subject: [PATCH 042/108] [FIX] Fixed logged user redirection bzr revid: vta@openerp.com-20120814131322-sww9yuik9jn2tn3p --- addons/auth_oauth/res_users.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 4cae3bd4880..20771ea4ac6 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -52,7 +52,7 @@ class res_users(osv.Model): oauth_uid = validation['user_id'] name = self.auth_oauth_fetch_user_data(cr, uid, access_token)['name'] - r = (cr.dbname, login, access_token) + r = (cr.dbname, login, oauth_uid) res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) _logger.exception(res) @@ -63,6 +63,7 @@ class res_users(osv.Model): new_user = { 'name': name, 'login': login, + # 'password': oauth_uid, 'user_email': login, 'oauth_provider': 'Google', 'oauth_uid': oauth_uid, @@ -94,5 +95,5 @@ class res_users(osv.Model): finally: cr.close() - +res_users() # From 397c67348e6672e81a28021caa6e97162da2ca51 Mon Sep 17 00:00:00 2001 From: "vta vta@openerp.com" <> Date: Tue, 14 Aug 2012 16:29:04 +0200 Subject: [PATCH 043/108] [ADD] Added the oauth providers table and views. bzr revid: vta@openerp.com-20120814142904-7pjt91gl9ld2vxmf --- addons/auth_oauth/__init__.py | 1 + addons/auth_oauth/__openerp__.py | 3 ++ addons/auth_oauth/oauth_providers.py | 26 ++++++++++++++ addons/auth_oauth/oauth_providers.xml | 49 +++++++++++++++++++++++++++ addons/auth_oauth/res_users.py | 2 -- 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 addons/auth_oauth/oauth_providers.py create mode 100644 addons/auth_oauth/oauth_providers.xml diff --git a/addons/auth_oauth/__init__.py b/addons/auth_oauth/__init__.py index 68bb6c79406..62ec900cba2 100644 --- a/addons/auth_oauth/__init__.py +++ b/addons/auth_oauth/__init__.py @@ -1,2 +1,3 @@ import controllers import res_users +import oauth_providers diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py index 68f4d2ccd87..3557bec951b 100644 --- a/addons/auth_oauth/__openerp__.py +++ b/addons/auth_oauth/__openerp__.py @@ -31,6 +31,9 @@ 'depends': ['base', 'web'], 'data': [ ], + 'update_xml': [ + 'oauth_providers.xml' + ], 'js': [ 'static/src/js/auth_oauth.js', ], diff --git a/addons/auth_oauth/oauth_providers.py b/addons/auth_oauth/oauth_providers.py new file mode 100644 index 00000000000..5b5c32841d9 --- /dev/null +++ b/addons/auth_oauth/oauth_providers.py @@ -0,0 +1,26 @@ +from openerp.osv import osv, fields + +class oauth_providers(osv.osv): + + """Class defining the configuration values of an OAuth2 provider""" + + _name = 'oauth.providers' + _description = 'OAuth2 provider' + _order = 'name' + + _columns = { + 'name' : fields.char('Provider name', required=True), # Name of the OAuth2 entity, Google, LinkedIn, etc + 'client_id' : fields.char('Client ID', required=True), # Our identifier + 'auth_endpoint' : fields.char('Authentication URL', required=True), # OAuth provider URL to authenticate users + 'scope' : fields.char('Accessed user data'), # OAUth user data desired to access + 'validation_endpoint' : fields.char('Validation URL'), # OAuth provider URL to validate tokens + 'icon_url' : fields.char('Icon'), # URL of the icon's provider + 'active' : fields.boolean('Active'), + 'sequence' : fields.integer(), + } + + _sql_constraints = [ + ('name', 'unique(name)', 'The name of the OAuth provider must be unique') + ] + +oauth_providers() \ No newline at end of file diff --git a/addons/auth_oauth/oauth_providers.xml b/addons/auth_oauth/oauth_providers.xml new file mode 100644 index 00000000000..384a3052789 --- /dev/null +++ b/addons/auth_oauth/oauth_providers.xml @@ -0,0 +1,49 @@ + + + + + + oauth.provider.form + oauth.providers + form + +
+
+ +
+ + + + + + + + + + + + +
+
+
+ + oauth.provider.list + oauth.providers + tree + + + + + + + + + + Providers + oauth.providers + form + tree,form + + +
+
\ No newline at end of file diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 20771ea4ac6..229eba2f354 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -55,7 +55,6 @@ class res_users(osv.Model): r = (cr.dbname, login, oauth_uid) res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) - _logger.exception(res) if res: self.write(cr, uid, res[0], {'oauth_access_token':access_token}) else: @@ -63,7 +62,6 @@ class res_users(osv.Model): new_user = { 'name': name, 'login': login, - # 'password': oauth_uid, 'user_email': login, 'oauth_provider': 'Google', 'oauth_uid': oauth_uid, From 83c957b87b04650cac73b9411d5a3f5ed5cd6c74 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 14 Aug 2012 16:40:24 +0200 Subject: [PATCH 044/108] [IMP] remove useless view_type bzr revid: abo@openerp.com-20120814144024-ql2v70rnbenl6038 --- addons/portal_event/portal_event_view.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/portal_event/portal_event_view.xml b/addons/portal_event/portal_event_view.xml index 1c6d80fb807..662ff0349c9 100755 --- a/addons/portal_event/portal_event_view.xml +++ b/addons/portal_event/portal_event_view.xml @@ -3,14 +3,13 @@ Events ir.actions.act_window event.event - form kanban,calendar,tree,form,graph {"search_default_upcoming":1} From 4678ef3524b28f9618cd8e7e8cbd396edee4afac Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Tue, 14 Aug 2012 16:56:46 +0200 Subject: [PATCH 045/108] [WIP] portal: customize sale menu tooltips bzr revid: abo@openerp.com-20120814145646-mfa6ja7ujav1aud2 --- addons/portal_sale/portal_sale_view.xml | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index 2db0a7a4d96..0e1ef055242 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -2,16 +2,39 @@ + + + Quotations + ir.actions.act_window + sale.order + tree,form,calendar,graph + {"search_default_draft":1} + + No quotations so far. + + + + Sales Orders + ir.actions.act_window + sale.order + tree,form,calendar,graph + + {"search_default_sales":1} + No sale orders so far. + + Delivery Orders stock.picking.out ir.actions.act_window - form tree,form [('type','=','out')] {'default_type': 'out', 'contact_display': 'partner_address'} - This is the list of all delivery orders that have to be prepared, according to your different orders. + No delivery orders so far. @@ -22,12 +45,13 @@ kanban,tree,form + No products so far. + action="action_order_tree5" sequence="10"/> + action="action_order_form" sequence="20"/> Date: Tue, 14 Aug 2012 17:26:46 +0200 Subject: [PATCH 046/108] [WIP] portal: customize accounting menu tooltips bzr revid: abo@openerp.com-20120814152646-izicd2mqb77pwow4 --- addons/portal_sale/portal_sale_view.xml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index 0e1ef055242..517c68c9ba8 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -48,6 +48,26 @@ No products so far. + + Customer Invoices + account.invoice + tree,form,calendar,graph + [('type','=','out_invoice')] + {'default_type':'out_invoice', 'type':'out_invoice', 'journal_type': 'sale'} + + No invoices so far. + + + + Customer Payment + account.voucher + [('journal_id.type', 'in', ['bank', 'cash']), ('type','=','receipt')] + {'type':'receipt'} + + current + No purchase or payments so far. + + + action="action_invoice_tree1" sequence="10"/> + action="action_vendor_receipt" sequence="20"/> From eb50d914614eed9e046ba713e9e988419e0f8422 Mon Sep 17 00:00:00 2001 From: "vta vta@openerp.com" <> Date: Tue, 14 Aug 2012 17:45:29 +0200 Subject: [PATCH 047/108] [ADD] Added initial data to providers. bzr revid: vta@openerp.com-20120814154529-58c60kynpwkckwmc --- addons/auth_oauth/__openerp__.py | 1 + addons/auth_oauth/oauth_providers.py | 4 +++- addons/auth_oauth/oauth_providers.xml | 2 ++ addons/auth_oauth/oauth_providers_data.xml | 25 ++++++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 addons/auth_oauth/oauth_providers_data.xml diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py index 3557bec951b..e7b47a1397d 100644 --- a/addons/auth_oauth/__openerp__.py +++ b/addons/auth_oauth/__openerp__.py @@ -30,6 +30,7 @@ 'website': 'http://www.openerp.com', 'depends': ['base', 'web'], 'data': [ + 'oauth_providers_data.xml' ], 'update_xml': [ 'oauth_providers.xml' diff --git a/addons/auth_oauth/oauth_providers.py b/addons/auth_oauth/oauth_providers.py index 5b5c32841d9..2b42c821684 100644 --- a/addons/auth_oauth/oauth_providers.py +++ b/addons/auth_oauth/oauth_providers.py @@ -12,8 +12,10 @@ class oauth_providers(osv.osv): 'name' : fields.char('Provider name', required=True), # Name of the OAuth2 entity, Google, LinkedIn, etc 'client_id' : fields.char('Client ID', required=True), # Our identifier 'auth_endpoint' : fields.char('Authentication URL', required=True), # OAuth provider URL to authenticate users - 'scope' : fields.char('Accessed user data'), # OAUth user data desired to access + 'scope' : fields.char('Scope'), # OAUth user data desired to access 'validation_endpoint' : fields.char('Validation URL'), # OAuth provider URL to validate tokens + 'data_endpoint' : fields.char('Data URL'), + 'redirect_uris' : fields.char('Redirect URIs'), 'icon_url' : fields.char('Icon'), # URL of the icon's provider 'active' : fields.boolean('Active'), 'sequence' : fields.integer(), diff --git a/addons/auth_oauth/oauth_providers.xml b/addons/auth_oauth/oauth_providers.xml index 384a3052789..273ba1c6a10 100644 --- a/addons/auth_oauth/oauth_providers.xml +++ b/addons/auth_oauth/oauth_providers.xml @@ -21,6 +21,8 @@ + + diff --git a/addons/auth_oauth/oauth_providers_data.xml b/addons/auth_oauth/oauth_providers_data.xml new file mode 100644 index 00000000000..b66e6a877b9 --- /dev/null +++ b/addons/auth_oauth/oauth_providers_data.xml @@ -0,0 +1,25 @@ + + + + + Google OAuth2 + 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.googleusercontent.com + https://accounts.google.com/o/oauth2/auth + https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile + https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= + https://www.googleapis.com/oauth2/v1/userinfo?access_token= + https://localhost/ + True + + + Twitter OAuth2 + 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.twitterusercontent.com + https://api.twitter.com/oauth/request_token + + https://api.twitter.com/oauth/authorize?oauth_token= + + https://localhost/ + True + + + \ No newline at end of file From a6482fe07c57805976529eebd48225b060a9a49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 14 Aug 2012 19:06:12 +0200 Subject: [PATCH 048/108] [WIP] Followers rewrite: in create/write, the purpose is to add the field in the values to write, and to simplify automatic subscription. WIP, lack of time to finish. Will be continued. bzr revid: tde@openerp.com-20120814170612-ax8xhcmmttrcphnl --- addons/crm/crm_lead.py | 11 ++-- addons/hr_holidays/hr_holidays.py | 13 ++-- addons/hr_recruitment/hr_recruitment.py | 11 ++-- addons/mail/mail_group.py | 9 +++ addons/mail/mail_thread.py | 79 +++++++++++++++++++++++-- addons/mrp/mrp.py | 11 ++-- addons/project/project.py | 24 +++----- addons/project_issue/project_issue.py | 11 ++-- 8 files changed, 111 insertions(+), 58 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 6e1d3a7755b..6b29dbbafe6 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -859,13 +859,10 @@ class crm_lead(base_stage, osv.osv): # OpenChatter methods and notifications # ---------------------------------------- - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add the salesman. """ - user_ids = super(crm_lead, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(crm_lead, self).message_get_follower_fields(cr, uid, ids, context=context) + return res.append('user_id') def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 7eedfa54ce5..1788c857aba 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -366,15 +366,10 @@ class hr_holidays(osv.osv): result[obj.id] = hr_manager_group['users'] return result - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add employee and its manager. """ - user_ids = super(hr_holidays, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - if obj.employee_id.parent_id and not obj.employee_id.parent_id.user_id.id in user_ids: - user_ids.append(obj.employee_id.parent_id.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(hr_holidays, self).message_get_follower_fields(cr, uid, ids, context=context) + return res + ['user_id', 'employee_id.parent_id.user_id'] def create_notificate(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 1db79172dc5..cdd3252ff49 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -461,13 +461,10 @@ class hr_applicant(base_stage, osv.Model): # OpenChatter methods and notifications # ------------------------------------------------------- - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add responsible user. """ - user_ids = super(hr_applicant, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(hr_applicant, self).message_get_follower_fields(cr, uid, ids, context=context) + return res.append('user_id') def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 84008548d96..306a758eda2 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -184,3 +184,12 @@ class mail_group(osv.Model): def action_group_leave(self, cr, uid, ids, context=None): return self.message_unsubscribe(cr, uid, ids, context=context) + + # ---------------------------------------- + # OpenChatter methods and notifications + # ---------------------------------------- + + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'responsible_id' to the monitored fields """ + res = super(mail_group, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['responsible_id'] diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index efba082fee4..473dffe21fd 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -35,6 +35,10 @@ import xmlrpclib _logger = logging.getLogger(__name__) class many2many_reference(fields.many2many): + """ many2many_reference is an override of fields.many2many. It manages + many2many-like table where one id is given by two fields, res_model + and res_id. + """ def _get_query_and_where_params(self, cr, model, ids, values, where_params): """ Add in where: @@ -54,6 +58,7 @@ class many2many_reference(fields.many2many): return query, where_params def set(self, cr, model, id, name, values, user=None, context=None): + """ Override to add the res_model field in queries. """ if not values: return rel, id1, id2 = self._sql_names(model) obj = model.pool.get(self._obj) @@ -64,7 +69,7 @@ class many2many_reference(fields.many2many): idnew = obj.create(cr, user, act[2], context=context) cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, idnew, model._name)) elif act[0] == 3: - cr.execute('DELETE FROM "'+rel+'" WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name)) + cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name)) elif act[0] == 4: # following queries are in the same transaction - so should be relatively safe cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name)) @@ -80,7 +85,6 @@ class many2many_reference(fields.many2many): for act_nbr in act[2]: cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act_nbr, model._name)) else: - print act return super(many2many_reference, self).set(cr, model, id, name, values, user, context) class mail_thread(osv.Model): @@ -160,18 +164,36 @@ class mail_thread(osv.Model): #------------------------------------------------------ def create(self, cr, uid, vals, context=None): - """ Automatically subscribe the creator """ + """ Override of create to subscribe : + - the writer + - followers given by the monitored fields + """ thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) + fields = self.message_get_follower_fields(cr, uid, [thread_id], context=context) + print fields if thread_id: self.message_subscribe(cr, uid, [thread_id], [uid], context=context) return thread_id def write(self, cr, uid, ids, vals, context=None): - """ Override of write to subscribe the writer, except if he has changed - the followers (to avoid unfollow-->follow). """ + """ Override of write to subscribe : + - the writer + - followers given by the monitored fields + """ if isinstance(ids, (int, long)): ids = [ids] + command = self.message_get_automatic_followers(cr, uid, ids, vals, context=context) + print command + if vals.get('message_follower_ids'): + vals['message_follower_ids'] += command + else: + vals['message_follower_ids'] = command + print vals write_res = super(mail_thread, self).write(cr, uid, ids, vals, context=context) + + + + if write_res and not vals.get('message_follower_ids'): self.message_subscribe(cr, uid, ids, [uid], context=context) return write_res; @@ -188,6 +210,39 @@ class mail_thread(osv.Model): msg_obj.unlink(cr, uid, msg_to_del_ids, context=context) return super(mail_thread, self).unlink(cr, uid, ids, context=context) + def message_get_automatic_followers(self, cr, uid, ids, record_vals, add_uid=True, fetch_missing=False, context=None): + """ + + :param record_vals: values given to the create method of the new + record, or values updated in a write. + :param monitored_fields: a list of fields that are monitored. Those + fields must be many2one fields to the res.users model. + :param fetch_missing: is set to True, the method will read the + record to find values that are not present in record_vals. + + #TODO : UPDATE WHEN MERGING TO PARTNERS + """ + # get monitored fields + monitored_fields = self.message_get_monitored_follower_fields(cr, uid, ids, context=context) + print monitored_fields + # for each monitored field: if in record_vals, it has been modified + fields = [field for field in monitored_fields if field in record_vals.iterkeys()] + print fields + + follower_ids = [] + for field in fields: + value = record_vals.get(field) + if value: + follower_ids.append(value) + elif fetch_missing: + pass + + print follower_ids + if add_uid and uid not in follower_ids: + follower_ids.append(uid) + + return self.message_subscribe_get_command(cr, uid, follower_ids, context=context) + #------------------------------------------------------ # mail.message wrappers and tools #------------------------------------------------------ @@ -931,6 +986,12 @@ class mail_thread(osv.Model): # Subscription mechanism #------------------------------------------------------ + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Returns a list of fields containing a res.user.id. Those fields + will be checked to automatically subscribe those users. + """ + return [] + def message_get_subscribers(self, cr, uid, ids, context=None): """ Returns the current document followers. Basically this method checks in mail.followers for entries with matching res_model, @@ -953,6 +1014,10 @@ class mail_thread(osv.Model): write_res = self.write(cr, uid, ids, {'message_follower_ids': [(4, id) for id in to_subscribe_uids]}, context=context) return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] + def message_subscribe_get_command(self, cr, uid, follower_ids, context=None): + """ Generate the many2many command to add followers. """ + return [(4, id) for id in follower_ids] + def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None): """ Unsubscribe the user (or user_ids) from the current document. @@ -963,6 +1028,10 @@ class mail_thread(osv.Model): write_res = self.write(cr, uid, ids, {'message_follower_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] + def message_unsubscribe_get_command(self, cr, uid, follower_ids, context=None): + """ Generate the many2many command to remove followers. """ + return [(3, id) for id in follower_ids] + #------------------------------------------------------ # Notification API #------------------------------------------------------ diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 749d95c0c23..a41e4dbc332 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1046,13 +1046,10 @@ class mrp_production(osv.osv): # OpenChatter methods and notifications # --------------------------------------------------- - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add responsible user. """ - user_ids = super(mrp_production, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(mrp_production, self).message_get_follower_fields(cr, uid, ids, context=context) + return res.append('user_id') def create_send_note(self, cr, uid, ids, context=None): self.message_append_note(cr, uid, ids, body=_("Manufacturing order has been created."), context=context) diff --git a/addons/project/project.py b/addons/project/project.py index 6f3b23f4746..b1db7fde6b3 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -513,13 +513,10 @@ def Project(): # OpenChatter methods and notifications # ------------------------------------------------ - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add responsible user. """ - user_ids = super(project, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(project, self).message_get_follower_fields(cr, uid, ids, context=context) + return res.append('user_id') def create(self, cr, uid, vals, context=None): if context is None: context = {} @@ -1201,15 +1198,10 @@ class task(base_stage, osv.osv): result[obj.id].append(obj.user_id.id) return result - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add responsible user and project manager. """ - user_ids = super(task, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - if obj.manager_id and not obj.manager_id.id in user_ids: - user_ids.append(obj.manager_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(task, self).message_get_follower_fields(cr, uid, ids, context=context) + return res + ['user_id', 'manager_id'] def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 7b2afbeacf2..5aa34c173c5 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -502,13 +502,10 @@ class project_issue(base_stage, osv.osv): # OpenChatter methods and notifications # ------------------------------------------------------- - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Override to add responsible user. """ - user_ids = super(project_issue, self).message_get_subscribers(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id and not obj.user_id.id in user_ids: - user_ids.append(obj.user_id.id) - return user_ids + def message_get_follower_fields(self, cr, uid, ids, context=None): + """ Override to add 'user_id' field to automatic subscription. """ + res = super(project_issue, self).message_get_follower_fields(cr, uid, ids, context=context) + return res.append('user_id') def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ From 2b0fcb77753bda709a1abf6995874469985fd595 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 14 Aug 2012 21:53:16 +0200 Subject: [PATCH 049/108] [WIP] groups drag'n drop + resequence bzr revid: fme@openerp.com-20120814195316-j66wn6yxta6yj0w9 --- addons/web/controllers/main.py | 6 ++ addons/web/static/src/js/data.js | 16 +++++ addons/web_kanban/static/src/css/kanban.css | 4 ++ addons/web_kanban/static/src/css/kanban.sass | 2 + addons/web_kanban/static/src/js/kanban.js | 71 +++++++++++++++++-- .../web_kanban/static/src/xml/web_kanban.xml | 4 ++ 6 files changed, 97 insertions(+), 6 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index c5fdcce4945..e2ba9ebc5cc 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1136,6 +1136,12 @@ class DataSet(openerpweb.Controller): def exec_workflow(self, req, model, id, signal): return req.session.exec_workflow(model, id, signal) + @openerpweb.jsonrequest + def resequence(self, req, model, ids): + m = req.session.model(model) + print "TODO: write resequencing" + return True + class DataGroup(openerpweb.Controller): _cp_path = "/web/group" @openerpweb.jsonrequest diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index e360c922918..a2adc2429bd 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -805,6 +805,22 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend( /** @lends openerp. alter_ids: function(n_ids) { this.ids = n_ids; }, + /** + * Resequence records. + * + * @param {Array} ids identifiers of the records to resequence + * @returns {$.Deferred} + */ + resequence: function (ids, options) { + options = options || {}; + return this.rpc('/web/dataset/resequence', { + model: this.model, + ids: ids, + context: this._model.context(options.context), + }).pipe(function (results) { + return results; + }); + }, }); instance.web.DataSetStatic = instance.web.DataSet.extend({ init: function(parent, model, context, ids) { diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 47d9368d9dc..5b4d18c2e71 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -1,3 +1,4 @@ +@charset "utf-8"; .openerp .oe_kanban_view { height: inherit; } @@ -44,6 +45,9 @@ .openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups { width: 100%; } +.openerp .oe_kanban_view.oe_kanban_sortable_groups .oe_kanban_group_title { + cursor: move; +} .openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban { display: inline-block; } diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 029014b1f43..c1a8f48645d 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -66,6 +66,8 @@ height: inherit &.oe_kanban_ungrouped .oe_kanban_groups width: 100% + &.oe_kanban_sortable_groups .oe_kanban_group_title + cursor: move .oe_kanban_header &:hover .oe_dropdown_kanban diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 6c70ecfb8ec..4ca7ebf2be9 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -24,6 +24,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.fields_view = {}; this.fields_keys = []; this.group_by = null; + this.grouped_by_m2o = false; this.state = { groups : {}, records : {} @@ -54,7 +55,8 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.$element.find('.oe_kanban_buttons').replaceWith(this.$buttons); } this.$buttons - .on('click','button.oe_kanban_button_new', this.do_add_record); + .on('click', 'button.oe_kanban_button_new', this.do_add_record) + .on('click', '.oe_kanban_add_column', this.do_add_group); this.$groups = this.$element.find('.oe_kanban_groups tr'); this.fields_keys = _.keys(this.fields_view.fields); this.add_qweb_template(); @@ -157,6 +159,27 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.dataset.index = null; this.do_switch_view('form'); }, + do_add_group: function() { + var self = this; + var group_by = self.fields_view.fields[self.group_by]; + self.do_action({ + name: _t("Add column"), + res_model: group_by.relation, + views: [[false, 'form']], + type: 'ir.actions.act_window', + target: "new", + flags: { + action_buttons: true, + } + }); + var am = instance.webclient.action_manager; + var form = am.dialog_widget.views.form.controller; + form.on_button_cancel.add_last(am.dialog.on_close); + form.on_created.add_last(function() { + am.dialog.on_close(); + self.do_reload(); + }); + }, do_search: function(domain, context, group_by) { var self = this; this.$element.find('.oe_view_nocontent').remove(); @@ -165,6 +188,10 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.search_group_by = group_by; $.when(this.has_been_loaded).then(function() { self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by; + var group_by_field = self.fields_view.fields[self.group_by]; + self.grouped_by_m2o = !!(group_by_field && group_by_field.type === 'many2one'); + self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o); + self.$element.toggleClass('oe_kanban_sortable_groups', self.grouped_by_m2o); self.datagroup = new instance.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []); self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset); }); @@ -237,6 +264,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ var self = this; this.compute_groups_width(); if (this.group_by) { + // Kanban cards drag'n'drop this.$element.find('.oe_kanban_column').sortable({ connectWith: '.oe_kanban_column', handle : '.oe_kanban_draghandle', @@ -245,17 +273,48 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget'); }, stop: function(event, ui) { - var record = ui.item.data('widget'), - old_index = self.currently_dragging.index, - new_index = ui.item.index(), - old_group = self.currently_dragging.group, - new_group = ui.item.parents('.oe_kanban_column:first').data('widget'); + var record = ui.item.data('widget'); + var old_index = self.currently_dragging.index; + var new_index = ui.item.index(); + var old_group = self.currently_dragging.group; + var new_group = ui.item.parents('.oe_kanban_column:first').data('widget'); if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) { self.on_record_moved(record, old_group, old_index, new_group, new_index); } }, scroll: false }); + // Kanban groups drag'n'drop + var start_index; + if (this.grouped_by_m2o) { + this.$('.oe_kanban_groups_headers').sortable({ + helper: 'clone', + axis: 'x', + opacity: 0.5, + scroll: false, + start: function(event, ui) { + start_index = ui.item.index(); + self.$('.oe_kanban_record').hide(); + }, + stop: function(event, ui) { + var stop_index = ui.item.index(); + if (start_index !== stop_index) { + var $start_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(start_index); + var $stop_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(stop_index); + var method = (start_index > stop_index) ? 'insertBefore' : 'insertAfter'; + $start_column[method]($stop_column); + var tmp_group = self.groups.splice(start_index, 1)[0]; + self.groups.splice(stop_index, 0, tmp_group); + var new_sequence = _.pluck(self.groups, 'value'); + var field = self.fields_view.fields[self.group_by]; //TODO: self.field should be the field definition + (new instance.web.DataSet(self, field.relation)).resequence(new_sequence).then(function(r) { + console.log(r); + }); + } + self.$('.oe_kanban_record').show(); + } + }); + } } else { this.$element.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle'); } diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index 4bcc9a709a3..e1da07e3554 100644 --- a/addons/web_kanban/static/src/xml/web_kanban.xml +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -18,6 +18,10 @@ +
From cc406bc6920513faf960a29a500409985c1ee5bd Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 14 Aug 2012 23:37:08 +0200 Subject: [PATCH 050/108] [WIP] Added group_by_field bzr revid: fme@openerp.com-20120814213708-zkm05nkr1pdf8qbu --- addons/web/static/src/js/data.js | 2 +- addons/web_kanban/static/src/js/kanban.js | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index a2adc2429bd..2372c6bbff9 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -813,7 +813,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend( /** @lends openerp. */ resequence: function (ids, options) { options = options || {}; - return this.rpc('/web/dataset/resequence', { + return instance.session.rpc('/web/dataset/resequence', { model: this.model, ids: ids, context: this._model.context(options.context), diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 4ca7ebf2be9..42a3fc1600e 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -24,6 +24,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.fields_view = {}; this.fields_keys = []; this.group_by = null; + this.group_by_field = {}; this.grouped_by_m2o = false; this.state = { groups : {}, @@ -161,10 +162,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ }, do_add_group: function() { var self = this; - var group_by = self.fields_view.fields[self.group_by]; self.do_action({ name: _t("Add column"), - res_model: group_by.relation, + res_model: self.group_by_field.relation, views: [[false, 'form']], type: 'ir.actions.act_window', target: "new", @@ -188,8 +188,8 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ this.search_group_by = group_by; $.when(this.has_been_loaded).then(function() { self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by; - var group_by_field = self.fields_view.fields[self.group_by]; - self.grouped_by_m2o = !!(group_by_field && group_by_field.type === 'many2one'); + self.group_by_field = self.fields_view.fields[self.group_by] || {}; + self.grouped_by_m2o = (self.group_by_field.type === 'many2one'); self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o); self.$element.toggleClass('oe_kanban_sortable_groups', self.grouped_by_m2o); self.datagroup = new instance.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []); @@ -306,8 +306,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ var tmp_group = self.groups.splice(start_index, 1)[0]; self.groups.splice(stop_index, 0, tmp_group); var new_sequence = _.pluck(self.groups, 'value'); - var field = self.fields_view.fields[self.group_by]; //TODO: self.field should be the field definition - (new instance.web.DataSet(self, field.relation)).resequence(new_sequence).then(function(r) { + (new instance.web.DataSet(self, self.group_by_field.relation)).resequence(new_sequence).then(function(r) { console.log(r); }); } @@ -421,7 +420,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ this.title = this.value[1]; this.value = this.value[0]; } - var field = this.view.fields_view.fields[this.view.group_by]; + var field = this.view.group_by_field; if (field) { try { this.title = instance.web.format_value(group.value, field, false); From f1531572de0c3099ff78da65473dfc29dba6c4ac Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 15 Aug 2012 00:13:34 +0200 Subject: [PATCH 051/108] [WIP] Resequence bzr revid: fme@openerp.com-20120814221334-8arihwthpwictk3y --- addons/web/controllers/main.py | 5 ++++- addons/web_kanban/static/src/js/kanban.js | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index e2ba9ebc5cc..4a7f09cb461 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1139,7 +1139,10 @@ class DataSet(openerpweb.Controller): @openerpweb.jsonrequest def resequence(self, req, model, ids): m = req.session.model(model) - print "TODO: write resequencing" + if not len(m.fields_get(['sequence'])): + return False + for i in range(len(ids)): + m.write([ids[i]], { 'sequence': i }) return True class DataGroup(openerpweb.Controller): diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 42a3fc1600e..245f7024a22 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -307,7 +307,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ self.groups.splice(stop_index, 0, tmp_group); var new_sequence = _.pluck(self.groups, 'value'); (new instance.web.DataSet(self, self.group_by_field.relation)).resequence(new_sequence).then(function(r) { - console.log(r); + if (r === false) { + console.error("Kanban: could not resequence model '%s'. Probably no 'sequence' field.", self.group_by_field.relation); + } }); } self.$('.oe_kanban_record').show(); @@ -555,9 +557,8 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ do_save_sequences: function() { var self = this; if (_.indexOf(this.view.fields_keys, 'sequence') > -1) { - _.each(this.records, function(record, index) { - self.view.dataset.write(record.id, { sequence : index }); - }); + var new_sequence = _.pluck(this.records, 'id'); + self.view.dataset.resequence(new_sequence); } }, /** From 4bdf1b6beae5c3f28c5542ab8405e4599c4727c5 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 15 Aug 2012 00:23:29 +0200 Subject: [PATCH 052/108] [WIP] Improve group drag'n drop bzr revid: fme@openerp.com-20120814222329-ndu0o9g3wi85lwzp --- addons/web_kanban/static/src/js/kanban.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 245f7024a22..dd8c2fbaef0 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -294,7 +294,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ scroll: false, start: function(event, ui) { start_index = ui.item.index(); - self.$('.oe_kanban_record').hide(); + self.$('.oe_kanban_record').css({ visibility: 'hidden' }); }, stop: function(event, ui) { var stop_index = ui.item.index(); @@ -312,7 +312,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ } }); } - self.$('.oe_kanban_record').show(); + self.$('.oe_kanban_record').css({ visibility: 'visible' }); } }); } From 334bc0ff666365198ce3a5c96d3937ddba5660a2 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 15 Aug 2012 20:09:13 +0200 Subject: [PATCH 053/108] [IMP] generic oauth providers bzr revid: al@openerp.com-20120815180913-2y889mtgg15v5lqb --- addons/auth_oauth/__init__.py | 2 +- addons/auth_oauth/__openerp__.py | 4 +- .../{oauth_providers.py => auth_oauth.py} | 11 +---- ...providers_data.xml => auth_oauth_data.xml} | 10 ++-- ...auth_providers.xml => auth_oauth_view.xml} | 25 ++++------ addons/auth_oauth/controllers/main.py | 21 ++++---- addons/auth_oauth/res_users.py | 28 +++++------ addons/auth_oauth/static/src/js/auth_oauth.js | 49 +++++++++++++------ .../auth_oauth/static/src/xml/auth_oauth.xml | 18 +++---- 9 files changed, 83 insertions(+), 85 deletions(-) rename addons/auth_oauth/{oauth_providers.py => auth_oauth.py} (79%) rename addons/auth_oauth/{oauth_providers_data.xml => auth_oauth_data.xml} (82%) rename addons/auth_oauth/{oauth_providers.xml => auth_oauth_view.xml} (62%) diff --git a/addons/auth_oauth/__init__.py b/addons/auth_oauth/__init__.py index 62ec900cba2..c9acce48a16 100644 --- a/addons/auth_oauth/__init__.py +++ b/addons/auth_oauth/__init__.py @@ -1,3 +1,3 @@ import controllers +import auth_oauth import res_users -import oauth_providers diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py index e7b47a1397d..b61df57c00b 100644 --- a/addons/auth_oauth/__openerp__.py +++ b/addons/auth_oauth/__openerp__.py @@ -30,10 +30,10 @@ 'website': 'http://www.openerp.com', 'depends': ['base', 'web'], 'data': [ - 'oauth_providers_data.xml' + 'auth_oauth_data.xml' ], 'update_xml': [ - 'oauth_providers.xml' + 'auth_oauth_view.xml' ], 'js': [ 'static/src/js/auth_oauth.js', diff --git a/addons/auth_oauth/oauth_providers.py b/addons/auth_oauth/auth_oauth.py similarity index 79% rename from addons/auth_oauth/oauth_providers.py rename to addons/auth_oauth/auth_oauth.py index 2b42c821684..de40cc8666c 100644 --- a/addons/auth_oauth/oauth_providers.py +++ b/addons/auth_oauth/auth_oauth.py @@ -1,10 +1,9 @@ from openerp.osv import osv, fields -class oauth_providers(osv.osv): - +class auth_oauth_providers(osv.osv): """Class defining the configuration values of an OAuth2 provider""" - _name = 'oauth.providers' + _name = 'auth.oauth.provider' _description = 'OAuth2 provider' _order = 'name' @@ -15,14 +14,8 @@ class oauth_providers(osv.osv): 'scope' : fields.char('Scope'), # OAUth user data desired to access 'validation_endpoint' : fields.char('Validation URL'), # OAuth provider URL to validate tokens 'data_endpoint' : fields.char('Data URL'), - 'redirect_uris' : fields.char('Redirect URIs'), 'icon_url' : fields.char('Icon'), # URL of the icon's provider 'active' : fields.boolean('Active'), 'sequence' : fields.integer(), } - _sql_constraints = [ - ('name', 'unique(name)', 'The name of the OAuth provider must be unique') - ] - -oauth_providers() \ No newline at end of file diff --git a/addons/auth_oauth/oauth_providers_data.xml b/addons/auth_oauth/auth_oauth_data.xml similarity index 82% rename from addons/auth_oauth/oauth_providers_data.xml rename to addons/auth_oauth/auth_oauth_data.xml index b66e6a877b9..7eef4f83c51 100644 --- a/addons/auth_oauth/oauth_providers_data.xml +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -1,25 +1,23 @@ - + - + Google OAuth2 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.googleusercontent.com https://accounts.google.com/o/oauth2/auth https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= https://www.googleapis.com/oauth2/v1/userinfo?access_token= - https://localhost/ True - + Twitter OAuth2 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.twitterusercontent.com https://api.twitter.com/oauth/request_token https://api.twitter.com/oauth/authorize?oauth_token= - https://localhost/ True - \ No newline at end of file + diff --git a/addons/auth_oauth/oauth_providers.xml b/addons/auth_oauth/auth_oauth_view.xml similarity index 62% rename from addons/auth_oauth/oauth_providers.xml rename to addons/auth_oauth/auth_oauth_view.xml index 273ba1c6a10..678c347c84d 100644 --- a/addons/auth_oauth/oauth_providers.xml +++ b/addons/auth_oauth/auth_oauth_view.xml @@ -1,36 +1,31 @@ - + - - oauth.provider.form - oauth.providers + auth.oauth.provider.form + auth.oauth.provider form
-
- -
- + - + -
- oauth.provider.list - oauth.providers + auth.oauth.provider.list + auth.oauth.provider tree @@ -42,10 +37,10 @@ Providers - oauth.providers + auth.oauth.provider form tree,form - +
-
\ No newline at end of file + diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 664d510e65f..a3f69954812 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -12,12 +12,13 @@ _logger = logging.getLogger(__name__) class OAuthController(openerpweb.Controller): _cp_path = '/auth_oauth' + @openerpweb.jsonrequest def list_providers(self, req, dbname): - #dbname = kw.get("state") - #registry = openerp.modules.registry.RegistryManager.get(dbname) - #with registry.cursor() as cr: - # dsfasdf - pass + registry = openerp.modules.registry.RegistryManager.get(dbname) + with registry.cursor() as cr: + providers = registry.get('auth.oauth.provider') + l = providers.read(cr, 1, providers.search(cr, 1, [])) + return l @openerpweb.httprequest def signin(self, req, **kw): @@ -26,18 +27,18 @@ class OAuthController(openerpweb.Controller): with registry.cursor() as cr: try: u = registry.get('res.users') - credentials = u.auth_oauth(cr, 1, kw) + credentials = u.auth_oauth(cr, 1, {}, kw) cr.commit() return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) except AttributeError: # auth_signup is not installed - _logger.exception("attribute error") - url = "/#action=auth_signup&error=1" + _logger.error("auth_signup not installed on database %s: oauth sign up cancelled."%dbname) + url = "/#action=login&oauth_error=1" except Exception,e: # signup error _logger.exception('oops') - url = "/#action=auth_signup&error=2" - return openerp.addons.web.controllers.main.set_cookie_and_redirect(req, "/") + url = "/#action=login&oauth_error=2" + return openerp.addons.web.controllers.main.set_cookie_and_redirect(req, url) # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 229eba2f354..46feac109c6 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -1,5 +1,6 @@ import logging +import urllib import urllib2 import simplejson @@ -10,40 +11,36 @@ from openerp.osv import osv, fields _logger = logging.getLogger(__name__) class res_users(osv.Model): - _inherit = 'res.users' _columns = { - 'oauth_provider': fields.char('OAuth Provider', size=1024), - 'oauth_uid': fields.char('OAuth User ID', size=256, - help="Used for disambiguation in case of a shared OpenID URL"), - 'oauth_access_token': fields.char('OAuth Token', - readonly=True), + 'oauth_provider': fields.many2one('auth.oauth.provider','OAuth Provider'), + 'oauth_uid': fields.char('OAuth User ID', help="Oauth Provider user_id"), + 'oauth_access_token': fields.char('OAuth Token', readonly=True), } def auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None): - url = endpoint + access_token + params = urllib.urlencode({'access_token':access_token}) + url = endpoint + '?' + params f = urllib2.urlopen(url) response = f.read() return simplejson.loads(response) def auth_oauth_fetch_user_validation(self, cr, uid, access_token, context=None): - endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo' return self.auth_oauth_rpc(cr, uid, endpoint, access_token) def auth_oauth_fetch_user_data(self, cr, uid, access_token, context=None): - endpoint = 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' + endpoint = 'https://www.googleapis.com/oauth2/v1/userinfo' return self.auth_oauth_rpc(cr, uid, endpoint, access_token) - def auth_oauth(self, cr, uid, params, context=None): + def auth_oauth(self, cr, uid, config, params, context=None): # Advice by Google (to avoid Confused Deputy Problem) # if validation.audience != OUR_CLIENT_ID: # abort() # else: # continue with the process - access_token = params.get('access_token') - validation = self.auth_oauth_fetch_user_validation(cr, uid, access_token, context=context) if validation.get("error"): raise openerp.exceptions.AccessDenied @@ -51,8 +48,7 @@ class res_users(osv.Model): login = validation['email'] oauth_uid = validation['user_id'] name = self.auth_oauth_fetch_user_data(cr, uid, access_token)['name'] - - r = (cr.dbname, login, oauth_uid) + credentials = (cr.dbname, login, access_token) res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) if res: @@ -69,8 +65,7 @@ class res_users(osv.Model): 'active': True, } self.auth_signup_create(cr, uid, new_user) - return r - + return credentials def check(self, db, uid, passwd): try: @@ -93,5 +88,4 @@ class res_users(osv.Model): finally: cr.close() -res_users() # diff --git a/addons/auth_oauth/static/src/js/auth_oauth.js b/addons/auth_oauth/static/src/js/auth_oauth.js index a67adca6ebc..67b446a4580 100644 --- a/addons/auth_oauth/static/src/js/auth_oauth.js +++ b/addons/auth_oauth/static/src/js/auth_oauth.js @@ -3,26 +3,45 @@ openerp.auth_oauth = function(instance) { instance.web.Login = instance.web.Login.extend({ start: function(parent, params) { - // [][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((+{}+[])[+!![]]+(![]+[])[!+[]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+[]))+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+({}+[])[+!![]]+(!![]+[])[+!![]]+(![]+[])[!+[]+!![]]+([][+[]]+[])[!+[]+!![]]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(+!![]+[]))+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+[]))+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])))(); - this._super.apply(this, arguments); - var self = this; - self.$element.on('click', '.oe_login_oauth a', this.on_google_oauth); + var d = this._super.apply(this, arguments); + this.$element.on('click', 'a.oe_oauth_sign_in', this.on_oauth_sign_in); + this.oauth_providers = []; + if(this.params.oauth_error === 1) { + this.do_warn("Sign up error.","Sign up is not allowed on this database."); + } else if(this.params.oauth_error === 2) { + this.do_warn("Authentication error",""); + } + return d.then(this.do_oauth_load); + }, + do_oauth_load: function() { + var db = this.$("form [name=db]").val(); + if (db) { + this.rpc("/auth_oauth/list_providers", { dbname: db }).then(this.on_oauth_loaded); + } + }, + on_oauth_loaded: function(result) { + this.oauth_providers = result; + console.log(result); + var buttons = QWeb.render("auth_oauth.Login.button",{"widget":this}); + console.log(buttons); + this.$(".oe_login_pane form ul").after(buttons); }, oauth_url: function(state) { - var endpoint = 'https://accounts.google.com/o/oauth2/auth'; + }, + on_oauth_sign_in: function(ev) { + ev.preventDefault(); + var index = $(ev.target).data('index'); + var p = this.oauth_providers[index]; + var ret = location.protocol+"//"+location.host+"/"; + var dbname = self.$("form [name=db]").val(); var params = { response_type: 'token', - client_id: '108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.googleusercontent.com', - redirect_uri: 'https://localhost/', - scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile', - state: state, + client_id: p.client_id, + redirect_uri: ret, + scope: p.scope, + state: dbname, }; - var url = endpoint + '?' + $.param(params); - return url; - }, - on_google_oauth: function(ev) { - var dbname = self.$("form [name=db]").val(); - var url = this.oauth_url(dbname); + var url = p.auth_endpoint + '?' + $.param(params); window.location = url; }, }); diff --git a/addons/auth_oauth/static/src/xml/auth_oauth.xml b/addons/auth_oauth/static/src/xml/auth_oauth.xml index 75f168d6c9a..494dc20e1a1 100644 --- a/addons/auth_oauth/static/src/xml/auth_oauth.xml +++ b/addons/auth_oauth/static/src/xml/auth_oauth.xml @@ -1,13 +1,11 @@ - - - - - + + + +
- -
\ No newline at end of file + + From 29c709602acc4db95c902baa7dd492add4e8dcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:03:58 +0200 Subject: [PATCH 054/108] [TMP] Removed statement for automatic subscription, temp. bzr revid: tde@openerp.com-20120815200358-0ld8cp85tozwcvfb --- addons/mail/mail_thread.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 5bd9582b7cd..abf49067210 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -174,10 +174,10 @@ class mail_thread(osv.Model): - followers given by the monitored fields """ thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) - fields = self.message_get_follower_fields(cr, uid, [thread_id], context=context) - print fields - if thread_id: - self.message_subscribe(cr, uid, [thread_id], [uid], context=context) + #fields = self.message_get_follower_fields(cr, uid, [thread_id], context=context) + #print fields + #if thread_id: + #self.message_subscribe(cr, uid, [thread_id], [uid], context=context) return thread_id def write(self, cr, uid, ids, vals, context=None): From 6d6929a8635b277e5d19635a4a88a2e8b5d4718c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:05:06 +0200 Subject: [PATCH 055/108] [REM] mail_thread: removed '@login' feature, because of access right issues. bzr revid: tde@openerp.com-20120815200506-1sohf3ul6b0cmo43 --- addons/mail/mail_thread.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index abf49067210..7438ed7443f 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -293,10 +293,7 @@ class mail_thread(osv.Model): # get subscribers notif_user_ids = self.message_get_subscribers(cr, uid, thread_ids, context=context) - - # add users requested via parsing message (@login) - notif_user_ids += self.message_parse_users(cr, uid, body, context=context) - + # add users requested to perform an action (need_action mechanism) if hasattr(self, 'get_needaction_user_ids') and self._columns.get('user_id'): user_ids_dict = self.get_needaction_user_ids(cr, uid, thread_ids, context=context) @@ -314,17 +311,6 @@ class mail_thread(osv.Model): notif_user_ids = list(set(notif_user_ids)) return notif_user_ids - def message_parse_users(self, cr, uid, string, context=None): - """Parse message content - - if find @login -(^|\s)@((\w|@|\.)*)-: returns the related ids - this supports login that are emails (such as @raoul@grobedon.net) - """ - regex = re.compile('(^|\s)@((\w|@|\.)*)') - login_lst = [item[1] for item in regex.findall(string)] - if not login_lst: return [] - user_ids = self.pool.get('res.users').search(cr, uid, [('login', 'in', login_lst)], context=context) - return user_ids - #------------------------------------------------------ # Generic message api #------------------------------------------------------ @@ -1082,10 +1068,7 @@ class mail_thread(osv.Model): # remove message writer if user_to_notify_ids.count(new_msg_values.get('user_id')) > 0: user_to_notify_ids.remove(new_msg_values.get('user_id')) - - # get user_ids directly asked - user_to_push_from_parse_ids = self.message_parse_users(cr, uid, body, context=context) - + # try to find an email_to email_to = '' for user in res_users_obj.browse(cr, uid, user_to_notify_ids, context=context): From af44478ff3f6d34c753772e1030eba864feef344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:07:15 +0200 Subject: [PATCH 056/108] [REF] mail_thread: removed get_subscribers, because not necessary anymore with the new field. bzr revid: tde@openerp.com-20120815200715-075no768khroyyoe --- addons/mail/mail_thread.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 7438ed7443f..3d19333fe84 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -292,8 +292,10 @@ class mail_thread(osv.Model): body = new_msg_vals.get('body_html', '') if new_msg_vals.get('content_subtype') == 'html' else new_msg_vals.get('body_text', '') # get subscribers - notif_user_ids = self.message_get_subscribers(cr, uid, thread_ids, context=context) - + subscr_obj = self.pool.get('mail.followers') + subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) + notif_user_ids = [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)] + # add users requested to perform an action (need_action mechanism) if hasattr(self, 'get_needaction_user_ids') and self._columns.get('user_id'): user_ids_dict = self.get_needaction_user_ids(cr, uid, thread_ids, context=context) @@ -999,15 +1001,6 @@ class mail_thread(osv.Model): """ return [] - def message_get_subscribers(self, cr, uid, ids, context=None): - """ Returns the current document followers. Basically this method - checks in mail.followers for entries with matching res_model, - res_id. - """ - subscr_obj = self.pool.get('mail.followers') - subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) - return [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)] - def message_subscribe(self, cr, uid, ids, user_ids = None, context=None): """ Subscribe the user (or user_ids) to the current document. From 8b616f3302a732585a8b65cd9f0ca700c6a37ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:13:20 +0200 Subject: [PATCH 057/108] [REM] sale, purcahse, crm_phonecall, analytic: stop subscribing default subscribed users. Purchase: added monitored field: validator. bzr revid: tde@openerp.com-20120815201320-gzkzdm14vnni8dsq --- addons/analytic/analytic.py | 1 - addons/crm/crm_phonecall.py | 1 - addons/mail/mail_thread.py | 2 +- addons/purchase/purchase.py | 6 +++++- addons/sale/sale.py | 1 - 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 165af37888d..3231b1e6d95 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -297,7 +297,6 @@ class account_analytic_account(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context) self.message_append_note(cr, uid, [obj.id], body=_("Contract for %s has been created.") % (obj.partner_id.name), context=context) account_analytic_account() diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 8ffa8dd591e..da08f97250a 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -271,7 +271,6 @@ class crm_phonecall(base_state, osv.osv): def case_open_send_note(self, cr, uid, ids, context=None): lead_obj = self.pool.get('crm.lead') for phonecall in self.browse(cr, uid, ids, context=context): - phonecall.message_subscribe([phonecall.user_id.id], context=context) if phonecall.opportunity_id: lead = phonecall.opportunity_id # convert datetime field to a datetime, using server format, then diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 3d19333fe84..87f22f09042 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -230,7 +230,7 @@ class mail_thread(osv.Model): # get monitored fields monitored_fields = self.message_get_monitored_follower_fields(cr, uid, ids, context=context) print monitored_fields - # for each monitored field: if in record_vals, it has been modified + # for each monitored field: if in record_vals, it has been modified/added fields = [field for field in monitored_fields if field in record_vals.iterkeys()] print fields diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 1daf6de034e..756016d39e2 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -738,12 +738,16 @@ class purchase_order(osv.osv): result[obj.id].append(obj.validator.id) return result + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'validator' to the monitored fields """ + res = super(purchase_order, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['validator'] + def create_send_note(self, cr, uid, ids, context=None): return self.message_append_note(cr, uid, ids, body=_("Request for quotation created."), context=context) def confirm_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_subscribe(cr, uid, [obj.id], [obj.validator.id], context=context) self.message_append_note(cr, uid, [obj.id], body=_("Quotation for %s converted to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context) def shipment_send_note(self, cr, uid, ids, picking_id, context=None): diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 566b1ea2caa..c525ba04df1 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1031,7 +1031,6 @@ class sale_order(osv.osv): def create_send_note(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): - self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context) self.message_append_note(cr, uid, [obj.id], body=_("Quotation for %s has been created.") % (obj.partner_id.name), context=context) def confirm_send_note(self, cr, uid, ids, context=None): From 819d4adfa7cedad9e3041929c13fe476d5107ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:15:46 +0200 Subject: [PATCH 058/108] [CLEAN] message_subscribe: removed unnecessary code. Use command generation everywhere. bzr revid: tde@openerp.com-20120815201546-pcb1juts1ryzctqd --- addons/mail/mail_thread.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 87f22f09042..0a2edcc37ab 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1006,12 +1006,10 @@ class mail_thread(osv.Model): :param user_ids: a list of user_ids; if not set, subscribe uid instead + :param return: new value of followers, for Chatter """ - follower_ids = [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] to_subscribe_uids = [uid] if user_ids is None else user_ids - if all(follower_id in follower_ids for follower_id in to_subscribe_uids): - return follower_ids - write_res = self.write(cr, uid, ids, {'message_follower_ids': [(4, id) for id in to_subscribe_uids]}, context=context) + write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_subscribe_get_command(cr, uid, to_subscribe_uids, context)}, context=context) return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] def message_subscribe_get_command(self, cr, uid, follower_ids, context=None): @@ -1023,9 +1021,10 @@ class mail_thread(osv.Model): :param user_ids: a list of user_ids; if not set, subscribe uid instead + :param return: new value of followers, for Chatter """ to_unsubscribe_uids = [uid] if user_ids is None else user_ids - write_res = self.write(cr, uid, ids, {'message_follower_ids': [(3, id) for id in to_unsubscribe_uids]}, context=context) + write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_unsubscribe_get_command(cr, uid, to_unsubscribe_uids, context)}, context=context) return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids] def message_unsubscribe_get_command(self, cr, uid, follower_ids, context=None): From 39cc9217bf8a8960bd9991a6d9dd1eec93f3b903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:17:24 +0200 Subject: [PATCH 059/108] [FIX] Fixed var name not correct after moving code. bzr revid: tde@openerp.com-20120815201724-6mkif7u7n0svoqu8 --- addons/mail/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 0a2edcc37ab..8c02d331792 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -293,7 +293,7 @@ class mail_thread(osv.Model): # get subscribers subscr_obj = self.pool.get('mail.followers') - subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) + subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', thread_ids)], context=context) notif_user_ids = [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)] # add users requested to perform an action (need_action mechanism) From ec23d716d4ccf0b69e17f48847730277b5477c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:19:54 +0200 Subject: [PATCH 060/108] [FIX] mail_thread: fixed reference to a deleted variable. bzr revid: tde@openerp.com-20120815201954-z0jf0qwp8orr76vn --- addons/mail/mail_thread.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 8c02d331792..e50f112a575 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1064,8 +1064,8 @@ class mail_thread(osv.Model): # try to find an email_to email_to = '' for user in res_users_obj.browse(cr, uid, user_to_notify_ids, context=context): - if not user.notification_email_pref == 'all' and \ - not (user.notification_email_pref == 'to_me' and user.id in user_to_push_from_parse_ids): + # TO BE REFACTORED BY FP, JUSTE REMOVED TO_ME, NOT SURE WHAT S NEW BEHAVIOR + if not user.notification_email_pref == 'all': continue if not user.email: continue From 3c733c508fa280eb6c33a2249f0ff98b75677f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:45:22 +0200 Subject: [PATCH 061/108] [IMP] mail_thread: cleaned automatic subscription. bzr revid: tde@openerp.com-20120815204522-qe5fll60wbk0drp9 --- addons/mail/mail_thread.py | 73 ++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index e50f112a575..9b03810d5c7 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -174,10 +174,9 @@ class mail_thread(osv.Model): - followers given by the monitored fields """ thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) - #fields = self.message_get_follower_fields(cr, uid, [thread_id], context=context) - #print fields - #if thread_id: - #self.message_subscribe(cr, uid, [thread_id], [uid], context=context) + followers_command = self.message_get_automatic_followers(cr, uid, [], vals, fetch_missing=True, context=context) + if followers_command: + self.write(cr, uid, [thread_id], {'message_follower_ids': followers_command}, context=context) return thread_id def write(self, cr, uid, ids, vals, context=None): @@ -187,21 +186,16 @@ class mail_thread(osv.Model): """ if isinstance(ids, (int, long)): ids = [ids] - command = self.message_get_automatic_followers(cr, uid, ids, vals, context=context) - print command - if vals.get('message_follower_ids'): - vals['message_follower_ids'] += command - else: - vals['message_follower_ids'] = command - print vals - write_res = super(mail_thread, self).write(cr, uid, ids, vals, context=context) - - - - - if write_res and not vals.get('message_follower_ids'): - self.message_subscribe(cr, uid, ids, [uid], context=context) - return write_res; + for id in ids: + # copy origin al vals because we are going to modify it + specific_vals = dict(vals) + followers_command = self.message_get_automatic_followers(cr, uid, ids, specific_vals, context=context) + if specific_vals.get('message_follower_ids'): + specific_vals['message_follower_ids'] += followers_command + else: + specific_vals['message_follower_ids'] = followers_command + write_res = super(mail_thread, self).write(cr, uid, ids, specific_vals, context=context) + return True def unlink(self, cr, uid, ids, context=None): """Override unlink, to automatically delete messages @@ -215,8 +209,20 @@ class mail_thread(osv.Model): msg_obj.unlink(cr, uid, msg_to_del_ids, context=context) return super(mail_thread, self).unlink(cr, uid, ids, context=context) - def message_get_automatic_followers(self, cr, uid, ids, record_vals, add_uid=True, fetch_missing=False, context=None): - """ + def message_get_automatic_followers(self, cr, uid, id, record_vals, add_uid=True, fetch_missing=False, context=None): + """ Return the command for the many2many follower_ids field to manage + subscribers. Behavior : + - get the monitored fields (ex: ['user_id', 'responsible_id']); those + fields should be relationships to res.users (#TODO: res.partner) + - if this field is in the record_vals: it means it has been modified + thus add its value to the followers + - if this fields is not in record_vals, but fetch_missing paramter + is set to True: fetch the value in the record (use: at creation + for default values, not present in record_vals) + - if add_uid: add the current user (for example: writer is subscriber) + - generate the command and return it + This method has to be used on 1 id, because otherwise it would imply + to track which user.id is used for which record.id. :param record_vals: values given to the create method of the new record, or values updated in a write. @@ -228,24 +234,21 @@ class mail_thread(osv.Model): #TODO : UPDATE WHEN MERGING TO PARTNERS """ # get monitored fields - monitored_fields = self.message_get_monitored_follower_fields(cr, uid, ids, context=context) - print monitored_fields + monitored_fields = self.message_get_monitored_follower_fields(cr, uid, [id], context=context) + modified_fields = [field for field in monitored_fields if field in record_vals.iterkeys()] + other_fields = [field for field in monitored_fields if field not in record_vals.iterkeys()] if fetch_missing else [] # for each monitored field: if in record_vals, it has been modified/added - fields = [field for field in monitored_fields if field in record_vals.iterkeys()] - print fields - follower_ids = [] - for field in fields: - value = record_vals.get(field) - if value: - follower_ids.append(value) - elif fetch_missing: - pass - - print follower_ids + for field in modified_fields: + follower_ids.append(record_vals.get(field)) + # for other fields: read in record if fetch_missing (otherwise list is void) + for field in other_fields: + record = self.browse(cr, uid, id, context=context) + value = getattr(record, field) + follower_ids.append(value) + # add uid if asked and not already present if add_uid and uid not in follower_ids: follower_ids.append(uid) - return self.message_subscribe_get_command(cr, uid, follower_ids, context=context) #------------------------------------------------------ From 92c70aa6ea76cbb58b216f4ba9d5b2f235b277e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:51:43 +0200 Subject: [PATCH 062/108] [IMP] Addons: propagated new automatic subscription mechanism. bzr revid: tde@openerp.com-20120815205143-twthjirie2b5kr7n --- addons/crm/crm_lead.py | 8 ++++---- addons/hr_holidays/hr_holidays.py | 9 +++++---- addons/hr_recruitment/hr_recruitment.py | 8 ++++---- addons/mrp/mrp.py | 8 ++++---- addons/project/project.py | 14 +++++++------- addons/project_issue/project_issue.py | 8 ++++---- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 47cce7bc2d6..146417a7abb 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -859,10 +859,10 @@ class crm_lead(base_stage, osv.osv): # OpenChatter methods and notifications # ---------------------------------------- - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(crm_lead, self).message_get_follower_fields(cr, uid, ids, context=context) - return res.append('user_id') + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' to the monitored fields """ + res = super(crm_lead, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['user_id'] def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 1788c857aba..45c65511a1a 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -366,10 +366,11 @@ class hr_holidays(osv.osv): result[obj.id] = hr_manager_group['users'] return result - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(hr_holidays, self).message_get_follower_fields(cr, uid, ids, context=context) - return res + ['user_id', 'employee_id.parent_id.user_id'] + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' and 'manager' to the monitored fields """ + res = super(hr_holidays, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + # TODO: add manager + return res + ['user_id'] def create_notificate(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index d62d78b049c..0ace2574d65 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -461,10 +461,10 @@ class hr_applicant(base_stage, osv.Model): # OpenChatter methods and notifications # ------------------------------------------------------- - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(hr_applicant, self).message_get_follower_fields(cr, uid, ids, context=context) - return res.append('user_id') + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' to the monitored fields """ + res = super(hr_applicant, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['user_id'] def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index a41e4dbc332..7b8442b4c46 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1046,10 +1046,10 @@ class mrp_production(osv.osv): # OpenChatter methods and notifications # --------------------------------------------------- - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(mrp_production, self).message_get_follower_fields(cr, uid, ids, context=context) - return res.append('user_id') + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' to the monitored fields """ + res = super(mrp_production, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['user_id'] def create_send_note(self, cr, uid, ids, context=None): self.message_append_note(cr, uid, ids, body=_("Manufacturing order has been created."), context=context) diff --git a/addons/project/project.py b/addons/project/project.py index ec216d245fc..1a8f8c8bfe5 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -514,10 +514,10 @@ def Project(): # OpenChatter methods and notifications # ------------------------------------------------ - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(project, self).message_get_follower_fields(cr, uid, ids, context=context) - return res.append('user_id') + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' to the monitored fields """ + res = super(project, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['user_id'] def create(self, cr, uid, vals, context=None): if context is None: context = {} @@ -1201,9 +1201,9 @@ class task(base_stage, osv.osv): result[obj.id].append(obj.user_id.id) return result - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(task, self).message_get_follower_fields(cr, uid, ids, context=context) + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' and 'manager_id' to the monitored fields """ + res = super(task, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) return res + ['user_id', 'manager_id'] def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 81550f476b6..5c440976b5c 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -499,10 +499,10 @@ class project_issue(base_stage, osv.osv): # OpenChatter methods and notifications # ------------------------------------------------------- - def message_get_follower_fields(self, cr, uid, ids, context=None): - """ Override to add 'user_id' field to automatic subscription. """ - res = super(project_issue, self).message_get_follower_fields(cr, uid, ids, context=context) - return res.append('user_id') + def message_get_monitored_follower_fields(self, cr, uid, ids, context=None): + """ Add 'user_id' to the monitored fields """ + res = super(project_issue, self).message_get_monitored_follower_fields(cr, uid, ids, context=context) + return res + ['user_id'] def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ From b046ac3faef9bdca1e02e0fb7d27cf5a8f539ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 22:54:59 +0200 Subject: [PATCH 063/108] [FIX] ids -> id. bzr revid: tde@openerp.com-20120815205459-irdfe1aeosqnu3nm --- addons/mail/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 9b03810d5c7..59e33f3589c 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -189,7 +189,7 @@ class mail_thread(osv.Model): for id in ids: # copy origin al vals because we are going to modify it specific_vals = dict(vals) - followers_command = self.message_get_automatic_followers(cr, uid, ids, specific_vals, context=context) + followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context) if specific_vals.get('message_follower_ids'): specific_vals['message_follower_ids'] += followers_command else: From e1870a434e61ed0a508724e511684a294f51e7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 23:01:49 +0200 Subject: [PATCH 064/108] [FIX] FIxed call to message_get_automatic_followers. bzr revid: tde@openerp.com-20120815210149-jujf2tv1hffm09g0 --- addons/mail/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 59e33f3589c..43bddc204e1 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -174,7 +174,7 @@ class mail_thread(osv.Model): - followers given by the monitored fields """ thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) - followers_command = self.message_get_automatic_followers(cr, uid, [], vals, fetch_missing=True, context=context) + followers_command = self.message_get_automatic_followers(cr, uid, thread_id, vals, fetch_missing=False, context=context) if followers_command: self.write(cr, uid, [thread_id], {'message_follower_ids': followers_command}, context=context) return thread_id From 774ea74c389b62b7fe69035585a8f4308d35afd6 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 15 Aug 2012 23:06:45 +0200 Subject: [PATCH 065/108] [IMP] res_users check,login,authenticate cleanup bzr revid: al@openerp.com-20120815210645-nwmfltbeiegvcdz9 --- openerp/addons/base/res/res_users.py | 135 +++++++++++---------------- 1 file changed, 56 insertions(+), 79 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index ba7d8f0f6d3..a4fa43a32ad 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -353,6 +353,57 @@ class res_users(osv.osv): data_id = dataobj._get_id(cr, 1, 'base', 'action_res_users_my') return dataobj.browse(cr, uid, data_id, context=context).res_id + def check_super(self, passwd): + if passwd == tools.config['admin_passwd']: + return True + else: + raise openerp.exceptions.AccessDenied() + + def check_credentials(self, cr, uid, password): + """ Override this method to plug additional authentication methods""" + res = self.search(cr, 1, [('id','=',uid),('password','=',password)]) + if not res: + raise openerp.exceptions.AccessDenied() + + def login(self, db, login, password): + if not password: + return False + user_id = False + cr = pooler.get_db(db).cursor() + try: + # autocommit: our single update request will be performed atomically. + # (In this way, there is no opportunity to have two transactions + # interleaving their cr.execute()..cr.commit() calls and have one + # of them rolled back due to a concurrent access.) + cr.autocommit(True) + # check if user exists + res = self.search(cr, 1, [('login','=',login)]) + if res: + user_id = res[0] + # check credentials + self.check_credentials(cr, user_id, password) + # We effectively unconditionally write the res_users line. + # Even w/ autocommit there's a chance the user row will be locked, + # in which case we can't delay the login just for the purpose of + # update the last login date - hence we use FOR UPDATE NOWAIT to + # try to get the lock - fail-fast + # Failing to acquire the lock on the res_users row probably means + # another request is holding it. No big deal, we don't want to + # prevent/delay login in that case. It will also have been logged + # as a SQL error, if anyone cares. + try: + cr.execute("SELECT id FROM res_users WHERE id=%s FOR UPDATE NOWAIT", str(user_id)) + cr.execute("UPDATE res_users SET login_date = now() AT TIME ZONE 'UTC' WHERE id=%s", str(user_id)) + except Exception, e: + _logger.exception("Failed to update last_login for db:%s login:%s", db, login) + except openerp.exceptions.AccessDenied: + _logger.info("Login failed for db:%s login:%s", db, login) + user_id = False + finally: + cr.close() + + return user_id + def authenticate(self, db, login, password, user_agent_env): """Verifies and returns the user ID corresponding to the given ``login`` and ``password`` combination, or False if there was @@ -371,8 +422,8 @@ class res_users(osv.osv): if user_agent_env and user_agent_env.get('base_location'): cr = pooler.get_db(db).cursor() try: - self.pool.get('ir.config_parameter').set_param(cr, uid, 'web.base.url', - user_agent_env['base_location']) + base = user_agent_env['base_location'] + self.pool.get('ir.config_parameter').set_param(cr, uid, 'web.base.url', base) cr.commit() except Exception: _logger.exception("Failed to update web.base.url configuration parameter") @@ -380,54 +431,8 @@ class res_users(osv.osv): cr.close() return uid - def login(self, db, login, password): - if not password: - return False - cr = pooler.get_db(db).cursor() - try: - # autocommit: our single request will be performed atomically. - # (In this way, there is no opportunity to have two transactions - # interleaving their cr.execute()..cr.commit() calls and have one - # of them rolled back due to a concurrent access.) - # We effectively unconditionally write the res_users line. - cr.autocommit(True) - # Even w/ autocommit there's a chance the user row will be locked, - # in which case we can't delay the login just for the purpose of - # update the last login date - hence we use FOR UPDATE NOWAIT to - # try to get the lock - fail-fast - cr.execute("""SELECT id from res_users - WHERE login=%s AND password=%s - AND active FOR UPDATE NOWAIT""", - (tools.ustr(login), tools.ustr(password))) - cr.execute("""UPDATE res_users - SET login_date = now() AT TIME ZONE 'UTC' - WHERE login=%s AND password=%s AND active - RETURNING id""", - (tools.ustr(login), tools.ustr(password))) - except Exception: - # Failing to acquire the lock on the res_users row probably means - # another request is holding it. No big deal, we don't want to - # prevent/delay login in that case. It will also have been logged - # as a SQL error, if anyone cares. - cr.execute("""SELECT id from res_users - WHERE login=%s AND password=%s - AND active""", - (tools.ustr(login), tools.ustr(password))) - finally: - res = cr.fetchone() - cr.close() - if res: - return res[0] - return False - - def check_super(self, passwd): - if passwd == tools.config['admin_passwd']: - return True - else: - raise openerp.exceptions.AccessDenied() - def check(self, db, uid, passwd): - """Verifies that the given (uid, password) pair is authorized for the database ``db`` and + """Verifies that the given (uid, password) is authorized for the database ``db`` and raise an exception if it is not.""" if not passwd: # empty passwords disallowed for obvious security reasons @@ -436,32 +441,14 @@ class res_users(osv.osv): return cr = pooler.get_db(db).cursor() try: - cr.execute('SELECT COUNT(1) FROM res_users WHERE id=%s AND password=%s AND active=%s', - (int(uid), passwd, True)) - res = cr.fetchone()[0] - if not res: - raise openerp.exceptions.AccessDenied() + self.check_credentials(cr, uid, passwd) if self._uid_cache.has_key(db): - ulist = self._uid_cache[db] - ulist[uid] = passwd + self._uid_cache[db][uid] = passwd else: self._uid_cache[db] = {uid:passwd} finally: cr.close() - def access(self, db, uid, passwd, sec_level, ids): - if not passwd: - return False - cr = pooler.get_db(db).cursor() - try: - cr.execute('SELECT id FROM res_users WHERE id=%s AND password=%s', (uid, passwd)) - res = cr.fetchone() - if not res: - raise openerp.exceptions.AccessDenied() - return res[0] - finally: - cr.close() - def change_password(self, cr, uid, old_passwd, new_passwd, context=None): """Change current user password. Old password must be provided explicitly to prevent hijacking an existing user session, or for cases where the cleartext @@ -584,8 +571,6 @@ class groups_implied(osv.osv): super(groups_implied, self).write(cr, uid, gids, vals, context) return res -groups_implied() - class users_implied(osv.osv): _inherit = 'res.users' @@ -609,10 +594,6 @@ class users_implied(osv.osv): super(users_implied, self).write(cr, uid, [user.id], vals, context) return res -users_implied() - - - # # Extension of res.groups and res.users for the special groups view in the users # form. This extension presents groups with selection and boolean widgets: @@ -755,8 +736,6 @@ class groups_view(osv.osv): res.append((False, 'boolean', others)) return res -groups_view() - class users_view(osv.osv): _inherit = 'res.users' @@ -848,6 +827,4 @@ class users_view(osv.osv): } return res -users_view() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 35e620ce688d07fd7ff6b0f50510c2c9301d2c7a Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 15 Aug 2012 23:10:04 +0200 Subject: [PATCH 066/108] [FIX] check credentials now works bzr revid: al@openerp.com-20120815211004-3arwfttwprm3tblh --- addons/auth_oauth/res_users.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 46feac109c6..5f89296976c 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -14,7 +14,7 @@ class res_users(osv.Model): _inherit = 'res.users' _columns = { - 'oauth_provider': fields.many2one('auth.oauth.provider','OAuth Provider'), + 'oauth_provider_id': fields.many2one('auth.oauth.provider','OAuth Provider'), 'oauth_uid': fields.char('OAuth User ID', help="Oauth Provider user_id"), 'oauth_access_token': fields.char('OAuth Token', readonly=True), } @@ -59,7 +59,7 @@ class res_users(osv.Model): 'name': name, 'login': login, 'user_email': login, - 'oauth_provider': 'Google', + 'oauth_provider_id': 1, 'oauth_uid': oauth_uid, 'oauth_access_token': access_token, 'active': True, @@ -67,25 +67,12 @@ class res_users(osv.Model): self.auth_signup_create(cr, uid, new_user) return credentials - def check(self, db, uid, passwd): + def check_credentials(self, cr, uid, password): try: - return super(res_users, self).check(db, uid, passwd) - except openerp.exceptions.AccesDenied: - if not passwd: + return super(res_users, self).check_credentials(cr, uid, password) + except openerp.exceptions.AccessDenied: + res = self.search(cr, 1, [('id','=',uid),('oauth_access_token','=',password)]) + if not res: raise - try: - registry = openerp.modules.registry.RegistryManager.get(db) - cr = registry.db.cursor() - cr.execute('''SELECT COUNT(1) - FROM res_users - WHERE id=%s - AND oauth_access_token=%s - AND active=%s''', - (int(uid), passwd, True)) - if not cr.fetchone()[0]: - raise - self._uid_cache.setdefault(db, {})[uid] = passwd - finally: - cr.close() # From 0529e1a0fbd98318856fd3b1e3084e632e0bd1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 23:11:05 +0200 Subject: [PATCH 067/108] [IMP] mail_thread: automatic subscription: do not add 'False' user_ids. bzr revid: tde@openerp.com-20120815211105-0vdhw7x1lkz0dct8 --- addons/mail/mail_thread.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 43bddc204e1..f8c02e34ec7 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -240,12 +240,15 @@ class mail_thread(osv.Model): # for each monitored field: if in record_vals, it has been modified/added follower_ids = [] for field in modified_fields: - follower_ids.append(record_vals.get(field)) + # do not add 'False' + if record_vals.get(fields): + follower_ids.append(record_vals.get(field)) # for other fields: read in record if fetch_missing (otherwise list is void) for field in other_fields: record = self.browse(cr, uid, id, context=context) value = getattr(record, field) - follower_ids.append(value) + if value: + follower_ids.append(value) # add uid if asked and not already present if add_uid and uid not in follower_ids: follower_ids.append(uid) From 454803c15080472a0a4d16f434d459b398922121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 23:20:59 +0200 Subject: [PATCH 068/108] [REM] mail_thread: res_users: removed dead code. bzr revid: tde@openerp.com-20120815212059-n1hf253xylxq7y7x --- addons/mail/res_users.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py index 1c7f002f1fa..f985ac4f88d 100644 --- a/addons/mail/res_users.py +++ b/addons/mail/res_users.py @@ -159,10 +159,6 @@ class res_users(osv.Model): for user in self.browse(cr, uid, ids, context=context): return user.partner_id.message_read(fetch_ancestors, ancestor_ids, limit, offset, domain) - def message_read_subscribers(self, cr, uid, ids, fields=['id', 'name', 'image_small'], context=None): - for user in self.browse(cr, uid, ids, context=context): - return user.partner_id.message_read_subscribers(fields) - def message_search(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None, limit=100, offset=0, domain=None, count=False, context=None): for user in self.browse(cr, uid, ids, context=context): From 5a89f31bdfb5622d6c491b96a5c1388255585c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 15 Aug 2012 23:55:10 +0200 Subject: [PATCH 069/108] [IMP] mail_thread: check before subscribing at message creation, because there is a loop in procurement. bzr revid: tde@openerp.com-20120815215510-5gd1wtkhoqpswu83 --- addons/mail/mail_thread.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index f8c02e34ec7..1c801aa1f26 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -267,15 +267,16 @@ class mail_thread(osv.Model): if context is None: context = {} - notification_obj = self.pool.get('mail.notification') - - # automatically subscribe the writer of the message - if vals.get('user_id'): - self.message_subscribe(cr, uid, [thread_id], [vals['user_id']], context=context) - # create message msg_id = self.pool.get('mail.message').create(cr, uid, vals, context=context) - + + # automatically subscribe the writer of the message + if vals.get('user_id'): + record = self.browse(cr, uid, thread_id, context=context) + follower_ids = [follower.id for follower in record.follower_ids] + if vals.get('user_id') not in follower_ids: + self.message_subscribe(cr, uid, [thread_id], [vals.get('user_id')], context=context) + # Set as unread if writer is not the document responsible self.message_create_set_unread(cr, uid, [thread_id], context=context) @@ -284,6 +285,7 @@ class mail_thread(osv.Model): return msg_id # get users that will get a notification pushed + notification_obj = self.pool.get('mail.notification') user_to_push_ids = self.message_get_user_ids_to_notify(cr, uid, [thread_id], vals, context=context) for id in user_to_push_ids: notification_obj.create(cr, uid, {'user_id': id, 'message_id': msg_id}, context=context) From 0c706a768d3737791901b0deca5fffb7bfa61410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 00:03:54 +0200 Subject: [PATCH 070/108] [FIX] Fixed wrong var name... bzr revid: tde@openerp.com-20120815220354-9c4ohv57g1vn65xj --- addons/mail/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 1c801aa1f26..3fa182585cf 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -273,7 +273,7 @@ class mail_thread(osv.Model): # automatically subscribe the writer of the message if vals.get('user_id'): record = self.browse(cr, uid, thread_id, context=context) - follower_ids = [follower.id for follower in record.follower_ids] + follower_ids = [follower.id for follower in record.message_follower_ids] if vals.get('user_id') not in follower_ids: self.message_subscribe(cr, uid, [thread_id], [vals.get('user_id')], context=context) From da8045d7840d48fff70bc9c583d9dfddb97cc57f Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 16 Aug 2012 00:30:38 +0200 Subject: [PATCH 071/108] add message_followers_ids bzr revid: al@openerp.com-20120815223038-ppuq03m5wj97u6rj --- addons/account/account_invoice_view.xml | 2 ++ addons/account_voucher/account_voucher_view.xml | 1 + addons/account_voucher/voucher_payment_receipt_view.xml | 2 ++ addons/account_voucher/voucher_sales_purchase_view.xml | 2 ++ addons/analytic/analytic_view.xml | 1 + addons/base_calendar/crm_meeting_view.xml | 1 + addons/crm/crm_lead_view.xml | 2 ++ addons/crm/crm_phonecall_view.xml | 1 + addons/crm_claim/crm_claim_view.xml | 1 + addons/crm_helpdesk/crm_helpdesk_view.xml | 1 + addons/event/event_view.xml | 2 ++ addons/hr_expense/hr_expense_view.xml | 1 + addons/hr_holidays/hr_holidays_view.xml | 2 ++ addons/hr_recruitment/hr_recruitment_view.xml | 1 + addons/idea/idea_view.xml | 1 + addons/mail/res_partner_view.xml | 1 + addons/mrp/mrp_view.xml | 1 + addons/mrp_operations/mrp_operations_view.xml | 1 + addons/mrp_repair/mrp_repair_view.xml | 1 + addons/procurement/procurement_view.xml | 1 + addons/product/product_view.xml | 1 + addons/project/project_view.xml | 2 ++ addons/project_issue/project_issue_view.xml | 1 + addons/purchase/purchase_view.xml | 1 + addons/purchase_requisition/purchase_requisition_view.xml | 1 + addons/sale/sale_view.xml | 1 + addons/stock/stock_view.xml | 2 ++ 27 files changed, 35 insertions(+) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 5aca260bc12..143c5514141 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -273,6 +273,7 @@
+
@@ -426,6 +427,7 @@
+
diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml index 2de4d6176b0..64e1cf22156 100644 --- a/addons/account_voucher/account_voucher_view.xml +++ b/addons/account_voucher/account_voucher_view.xml @@ -111,6 +111,7 @@
+
diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index 45144be24d4..503c3aeaa5e 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -240,6 +240,7 @@
+
@@ -408,6 +409,7 @@
+
diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml index 965e2600f24..5bcecfa268e 100644 --- a/addons/account_voucher/voucher_sales_purchase_view.xml +++ b/addons/account_voucher/voucher_sales_purchase_view.xml @@ -147,6 +147,7 @@
+
@@ -301,6 +302,7 @@
+
diff --git a/addons/analytic/analytic_view.xml b/addons/analytic/analytic_view.xml index ccd77638fd9..16100e73fff 100644 --- a/addons/analytic/analytic_view.xml +++ b/addons/analytic/analytic_view.xml @@ -45,6 +45,7 @@
+
diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index c6826786379..51d17ed43f8 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -233,6 +233,7 @@
+
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index dd7743bc26b..e56215f3332 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -222,6 +222,7 @@
+
@@ -532,6 +533,7 @@
+
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 9081ea15637..3b5162288a4 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -154,6 +154,7 @@
+
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml index cf5d1a780e8..820fb38e58a 100644 --- a/addons/crm_claim/crm_claim_view.xml +++ b/addons/crm_claim/crm_claim_view.xml @@ -182,6 +182,7 @@
+
diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml index 02c6603280a..5399c9880ca 100644 --- a/addons/crm_helpdesk/crm_helpdesk_view.xml +++ b/addons/crm_helpdesk/crm_helpdesk_view.xml @@ -100,6 +100,7 @@
+
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 289316110e6..27829129c15 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -196,6 +196,7 @@
+
@@ -477,6 +478,7 @@
+
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 506b9d7183f..a810b87dad0 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -120,6 +120,7 @@
+
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 0030a2b0187..711d07bbfe8 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -137,6 +137,7 @@
+
@@ -183,6 +184,7 @@
+
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 8a6296cd230..0e74473f853 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -181,6 +181,7 @@
+
diff --git a/addons/idea/idea_view.xml b/addons/idea/idea_view.xml index 18c81a497d0..eab3d03ceea 100644 --- a/addons/idea/idea_view.xml +++ b/addons/idea/idea_view.xml @@ -78,6 +78,7 @@
+
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml index 2b8d38f2b99..2a8430926d5 100644 --- a/addons/mail/res_partner_view.xml +++ b/addons/mail/res_partner_view.xml @@ -11,6 +11,7 @@
+
diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index 6de9a9a0bbc..c394544574d 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -818,6 +818,7 @@
+
diff --git a/addons/mrp_operations/mrp_operations_view.xml b/addons/mrp_operations/mrp_operations_view.xml index 658c284bce4..834f3000472 100644 --- a/addons/mrp_operations/mrp_operations_view.xml +++ b/addons/mrp_operations/mrp_operations_view.xml @@ -108,6 +108,7 @@
+
diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml index c9268a55428..55cbe49c75b 100644 --- a/addons/mrp_repair/mrp_repair_view.xml +++ b/addons/mrp_repair/mrp_repair_view.xml @@ -190,6 +190,7 @@
+
diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml index 2d734b32cfb..dbdc65c6763 100644 --- a/addons/procurement/procurement_view.xml +++ b/addons/procurement/procurement_view.xml @@ -104,6 +104,7 @@
+
diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 7a4c66ec52a..70651a0a795 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -176,6 +176,7 @@
+
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 7d78a040b7e..eb17d446816 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -148,6 +148,7 @@
+
@@ -481,6 +482,7 @@
+
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 08a2ae0b1d0..bb15f8c2f94 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -160,6 +160,7 @@
+
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 9cb5430ed4e..d2fbba68866 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -261,6 +261,7 @@
+
diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml index 0a442c3f9aa..188f81b6cbe 100644 --- a/addons/purchase_requisition/purchase_requisition_view.xml +++ b/addons/purchase_requisition/purchase_requisition_view.xml @@ -103,6 +103,7 @@
+
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index a4a049fdbc5..c4edc162f08 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -351,6 +351,7 @@
+
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index fb90b47fcb2..6927bf68c25 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -1011,6 +1011,7 @@
+
@@ -1133,6 +1134,7 @@
+
From df6a7026d89c9e0e8d600a293420a81d61e16024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 00:37:25 +0200 Subject: [PATCH 072/108] [IMP] mail_thread: cleaned subscription procses: do not add uid when modifying subscribers. bzr revid: tde@openerp.com-20120815223725-2w2lmdagwuj0d59p --- addons/mail/mail_thread.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 3fa182585cf..1c79b444b1b 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -189,10 +189,12 @@ class mail_thread(osv.Model): for id in ids: # copy origin al vals because we are going to modify it specific_vals = dict(vals) - followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context) + # we modify followers: do not subscribe the uid if specific_vals.get('message_follower_ids'): + followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, add_uid=False, context=context) specific_vals['message_follower_ids'] += followers_command else: + followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context) specific_vals['message_follower_ids'] = followers_command write_res = super(mail_thread, self).write(cr, uid, ids, specific_vals, context=context) return True From 23178302c4c44034e0bf6b6b1ea1a60d42e7d41c Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 16 Aug 2012 00:47:08 +0200 Subject: [PATCH 073/108] oauth generic with buttons from zocial bzr revid: al@openerp.com-20120815224708-rcge3914klkwrw6u --- addons/auth_oauth/__openerp__.py | 2 +- addons/auth_oauth/auth_oauth.py | 3 +- addons/auth_oauth/auth_oauth_data.xml | 21 +- addons/auth_oauth/controllers/main.py | 7 +- addons/auth_oauth/res_users.py | 20 +- addons/auth_oauth/static/lib/zocial/README.md | 31 ++ .../lib/zocial/css/zocial-regular-webfont.eot | Bin 0 -> 28471 bytes .../lib/zocial/css/zocial-regular-webfont.svg | 129 ++++++ .../lib/zocial/css/zocial-regular-webfont.ttf | Bin 0 -> 41156 bytes .../zocial/css/zocial-regular-webfont.woff | Bin 0 -> 31320 bytes .../static/lib/zocial/css/zocial.css | 420 ++++++++++++++++++ .../auth_oauth/static/lib/zocial/sample.html | 265 +++++++++++ .../auth_oauth/static/src/css/auth_oauth.css | 2 - addons/auth_oauth/static/src/js/auth_oauth.js | 11 +- .../auth_oauth/static/src/xml/auth_oauth.xml | 4 +- 15 files changed, 885 insertions(+), 30 deletions(-) create mode 100644 addons/auth_oauth/static/lib/zocial/README.md create mode 100755 addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.eot create mode 100755 addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.svg create mode 100755 addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.ttf create mode 100755 addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.woff create mode 100644 addons/auth_oauth/static/lib/zocial/css/zocial.css create mode 100644 addons/auth_oauth/static/lib/zocial/sample.html delete mode 100644 addons/auth_oauth/static/src/css/auth_oauth.css diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py index b61df57c00b..6c79bba2151 100644 --- a/addons/auth_oauth/__openerp__.py +++ b/addons/auth_oauth/__openerp__.py @@ -39,7 +39,7 @@ 'static/src/js/auth_oauth.js', ], 'css': [ - 'static/src/css/auth_oauth.css', + 'static/lib/zocial/css/zocial.css', ], 'qweb': [ 'static/src/xml/auth_oauth.xml', diff --git a/addons/auth_oauth/auth_oauth.py b/addons/auth_oauth/auth_oauth.py index de40cc8666c..e258505868d 100644 --- a/addons/auth_oauth/auth_oauth.py +++ b/addons/auth_oauth/auth_oauth.py @@ -14,7 +14,8 @@ class auth_oauth_providers(osv.osv): 'scope' : fields.char('Scope'), # OAUth user data desired to access 'validation_endpoint' : fields.char('Validation URL'), # OAuth provider URL to validate tokens 'data_endpoint' : fields.char('Data URL'), - 'icon_url' : fields.char('Icon'), # URL of the icon's provider + 'css_class' : fields.char('CSS class'), + 'body' : fields.char('Body'), 'active' : fields.boolean('Active'), 'sequence' : fields.integer(), } diff --git a/addons/auth_oauth/auth_oauth_data.xml b/addons/auth_oauth/auth_oauth_data.xml index 7eef4f83c51..021a03b2742 100644 --- a/addons/auth_oauth/auth_oauth_data.xml +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -1,13 +1,26 @@ + + Facebook Graph + facebook_client_id + https://www.facebook.com/dialog/oauth + + https://graph.facebook.com/me/permissions + + zocial facebook + Sign in with facebook + True + Google OAuth2 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.googleusercontent.com https://accounts.google.com/o/oauth2/auth https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile - https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= - https://www.googleapis.com/oauth2/v1/userinfo?access_token= + https://www.googleapis.com/oauth2/v1/tokeninfo + https://www.googleapis.com/oauth2/v1/userinfo + zocial google + Sign in with google True @@ -15,8 +28,10 @@ 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.twitterusercontent.com https://api.twitter.com/oauth/request_token - https://api.twitter.com/oauth/authorize?oauth_token= + https://api.twitter.com/oauth/authorize + zocial twitter + Sign in with twitter True diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index a3f69954812..28f550b4bda 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -1,5 +1,6 @@ import logging +import simplejson import werkzeug.urls import werkzeug.utils @@ -22,12 +23,14 @@ class OAuthController(openerpweb.Controller): @openerpweb.httprequest def signin(self, req, **kw): - dbname = kw.get("state") + state = simplejson.loads(kw['state']) + dbname = state['d'] + provider = state['p'] registry = openerp.modules.registry.RegistryManager.get(dbname) with registry.cursor() as cr: try: u = registry.get('res.users') - credentials = u.auth_oauth(cr, 1, {}, kw) + credentials = u.auth_oauth(cr, 1, provider, kw) cr.commit() return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) except AttributeError: diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index 5f89296976c..3dbab849942 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -5,7 +5,6 @@ import urllib2 import simplejson import openerp - from openerp.osv import osv, fields _logger = logging.getLogger(__name__) @@ -26,30 +25,23 @@ class res_users(osv.Model): response = f.read() return simplejson.loads(response) - def auth_oauth_fetch_user_validation(self, cr, uid, access_token, context=None): - endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo' - return self.auth_oauth_rpc(cr, uid, endpoint, access_token) - - def auth_oauth_fetch_user_data(self, cr, uid, access_token, context=None): - endpoint = 'https://www.googleapis.com/oauth2/v1/userinfo' - return self.auth_oauth_rpc(cr, uid, endpoint, access_token) - - def auth_oauth(self, cr, uid, config, params, context=None): + def auth_oauth(self, cr, uid, provider, params, context=None): # Advice by Google (to avoid Confused Deputy Problem) # if validation.audience != OUR_CLIENT_ID: # abort() # else: # continue with the process access_token = params.get('access_token') - validation = self.auth_oauth_fetch_user_validation(cr, uid, access_token, context=context) + p = self.pool.get('auth.oauth.provider').browse(cr, uid, provider, context=context) + + validation = self.auth_oauth_rpc(cr, uid, p.validation_endpoint, access_token) if validation.get("error"): raise openerp.exceptions.AccessDenied - login = validation['email'] oauth_uid = validation['user_id'] - name = self.auth_oauth_fetch_user_data(cr, uid, access_token)['name'] - credentials = (cr.dbname, login, access_token) + name = self.auth_oauth_rpc(cr, uid, p.data_endpoint, access_token)['name'] + credentials = (cr.dbname, login, access_token) res = self.search(cr, uid, [("oauth_uid", "=", oauth_uid)]) if res: self.write(cr, uid, res[0], {'oauth_access_token':access_token}) diff --git a/addons/auth_oauth/static/lib/zocial/README.md b/addons/auth_oauth/static/lib/zocial/README.md new file mode 100644 index 00000000000..213132677af --- /dev/null +++ b/addons/auth_oauth/static/lib/zocial/README.md @@ -0,0 +1,31 @@ +# Zocial CSS social buttons + +I basically rewrote this entire set so they are full vector buttons, meaning: + +- @font-face icons +- custom font file for all social icons +- icon font use private unicode spaces for accessibility +- em sizing based on button font-size +- support for about 83 different services +- buttons and icons supported +- no raster images (sweet) +- works splendidly on any browser supporting @font-face +- CSS3 degrades gracefully in IE8 and below etc. +- also includes generic icon-less primary and secondary buttons + +## How to use these buttons + + + +or + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.ttf b/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.ttf new file mode 100755 index 0000000000000000000000000000000000000000..94809d3a57ffcbfa1f3760420d745b2381574a05 GIT binary patch literal 41156 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 literal 0 HcmV?d00001 diff --git a/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.woff b/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.woff new file mode 100755 index 0000000000000000000000000000000000000000..1d6c4abc9d902c0f8662cadf3646810bd03639b7 GIT binary patch literal 31320 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 literal 0 HcmV?d00001 diff --git a/addons/auth_oauth/static/lib/zocial/css/zocial.css b/addons/auth_oauth/static/lib/zocial/css/zocial.css new file mode 100644 index 00000000000..89fc7450631 --- /dev/null +++ b/addons/auth_oauth/static/lib/zocial/css/zocial.css @@ -0,0 +1,420 @@ +@charset "UTF-8"; + +/*! +Code written by Sam Collins (@smcllns) of www.eventasaur.us +You are free to use this work commercially +You are free to extend this work without permissions from the author (just do so tastefully eh?) +Enjoy +*/ + +/* Reference icons from font-files */ + +@font-face { + font-family: 'zocial'; + font-style: normal; + font-weight: normal; + src: url('zocial-regular-webfont.eot'); + src: url('zocial-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('zocial-regular-webfont.woff') format('woff'), + url('zocial-regular-webfont.ttf') format('truetype'), + url('zocial-regular-webfont.svg#ZocialRegular') format('svg'); + unicode-range: U+0-U+10FFFF; +} + +/* Button structure */ + +.zocial, +a.zocial { + border: 1px solid #777; + border-color: rgba(0,0,0,0.2); + border-bottom-color: #333; + border-bottom-color: rgba(0,0,0,0.4); + color: #fff; + -moz-box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.4), inset 0 0 0.1em rgba(255,255,255,0.9); + -webkit-box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.4), inset 0 0 0.1em rgba(255,255,255,0.9); + box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.4), inset 0 0 0.1em rgba(255,255,255,0.9); + cursor: pointer; + display: inline-block; + font: bold 100%/2.1 "Lucida Grande", Tahoma, sans-serif; + padding: 0 .95em 0 0; + text-align: center; + text-decoration: none; + text-shadow: 0 1px 0 rgba(0,0,0,0.5); + white-space: nowrap; + + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; + + position: relative; + + -moz-border-radius: .3em; + -webkit-border-radius: .3em; + border-radius: .3em; +} + +.zocial:before { + content: ""; + border-right: 0.075em solid rgba(0,0,0,0.1); + float: left; + font: 120%/1.65 zocial; + font-style: normal; + font-weight: normal; + margin: 0 0.5em 0 0; + padding: 0 0.5em; + text-align: center; + text-decoration: none; + text-transform: none; + + -moz-box-shadow: 0.075em 0 0 rgba(255,255,255,0.25); + -webkit-box-shadow: 0.075em 0 0 rgba(255,255,255,0.25); + box-shadow: 0.075em 0 0 rgba(255,255,255,0.25); + + -webkit-font-smoothing: antialiased; +} + +.zocial:active { + outline: none; /* outline is visible on :focus */ +} + +/* Buttons can be displayed as standalone icons by adding a class of "icon" */ + +.zocial.icon { + overflow: hidden; + max-width: 2.4em; + padding-left: 0; + padding-right: 0; + max-height: 2.15em; + white-space: nowrap; +} +.zocial.icon:before { + padding: 0; + width: 2em; + height: 2em; + + box-shadow: none; + border: none; +} + +/* Gradients */ + +.zocial { + background-image: -moz-linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.05) 49%, rgba(0,0,0,.05) 51%, rgba(0,0,0,.1)); + background-image: -ms-linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.05) 49%, rgba(0,0,0,.05) 51%, rgba(0,0,0,.1)); + background-image: -o-linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.05) 49%, rgba(0,0,0,.05) 51%, rgba(0,0,0,.1)); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.1)), color-stop(49%, rgba(255,255,255,.05)), color-stop(51%, rgba(0,0,0,.05)), to(rgba(0,0,0,.1))); + background-image: -webkit-linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.05) 49%, rgba(0,0,0,.05) 51%, rgba(0,0,0,.1)); + background-image: linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.05) 49%, rgba(0,0,0,.05) 51%, rgba(0,0,0,.1)); +} + +.zocial:hover, .zocial:focus { + background-image: -moz-linear-gradient(rgba(255,255,255,.15) 49%, rgba(0,0,0,.1) 51%, rgba(0,0,0,.15)); + background-image: -ms-linear-gradient(rgba(255,255,255,.15) 49%, rgba(0,0,0,.1) 51%, rgba(0,0,0,.15)); + background-image: -o-linear-gradient(rgba(255,255,255,.15) 49%, rgba(0,0,0,.1) 51%, rgba(0,0,0,.15)); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.15)), color-stop(49%, rgba(255,255,255,.15)), color-stop(51%, rgba(0,0,0,.1)), to(rgba(0,0,0,.15))); + background-image: -webkit-linear-gradient(rgba(255,255,255,.15) 49%, rgba(0,0,0,.1) 51%, rgba(0,0,0,.15)); + background-image: linear-gradient(rgba(255,255,255,.15) 49%, rgba(0,0,0,.1) 51%, rgba(0,0,0,.15)); +} + +.zocial:active { + background-image: -moz-linear-gradient(bottom, rgba(255,255,255,.1), rgba(255,255,255,0) 30%, transparent 50%, rgba(0,0,0,.1)); + background-image: -ms-linear-gradient(bottom, rgba(255,255,255,.1), rgba(255,255,255,0) 30%, transparent 50%, rgba(0,0,0,.1)); + background-image: -o-linear-gradient(bottom, rgba(255,255,255,.1), rgba(255,255,255,0) 30%, transparent 50%, rgba(0,0,0,.1)); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,.1)), color-stop(30%, rgba(255,255,255,0)), color-stop(50%, transparent), to(rgba(0,0,0,.1))); + background-image: -webkit-linear-gradient(bottom, rgba(255,255,255,.1), rgba(255,255,255,0) 30%, transparent 50%, rgba(0,0,0,.1)); + background-image: linear-gradient(bottom, rgba(255,255,255,.1), rgba(255,255,255,0) 30%, transparent 50%, rgba(0,0,0,.1)); +} + +/* Adjustments for light background buttons */ + +.zocial.bitcoin, +.zocial.cloudapp, +.zocial.dropbox, +.zocial.email, +.zocial.eventful, +.zocial.github, +.zocial.gmail, +.zocial.instapaper, +.zocial.itunes, +.zocial.ninetyninedesigns, +.zocial.openid, +.zocial.plancast, +.zocial.posterous, +.zocial.reddit, +.zocial.secondary, +.zocial.viadeo, +.zocial.weibo, +.zocial.wikipedia { + border: 1px solid #aaa; + border-color: rgba(0,0,0,0.3); + border-bottom-color: #777; + border-bottom-color: rgba(0,0,0,0.5); + -moz-box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.7), inset 0 0 0.08em rgba(255,255,255,0.5); + -webkit-box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.7), inset 0 0 0.08em rgba(255,255,255,0.5); + box-shadow: inset 0 0.08em 0 rgba(255,255,255,0.7), inset 0 0 0.08em rgba(255,255,255,0.5); + text-shadow: 0 1px 0 rgba(255,255,255,0.8); +} + +/* :hover adjustments for light background buttons */ + +.zocial.bitcoin:focus, +.zocial.bitcoin:hover, +.zocial.dropbox:focus, +.zocial.dropbox:hover, +.zocial.email:focus, +.zocial.email:hover, +.zocial.eventful:focus, +.zocial.eventful:hover, +.zocial.github:focus, +.zocial.github:hover, +.zocial.gmail:focus, +.zocial.gmail:hover, +.zocial.instapaper:focus, +.zocial.instapaper:hover, +.zocial.itunes:focus, +.zocial.itunes:hover, +.zocial.ninetyninedesigns:focus, +.zocial.ninetyninedesigns:hover, +.zocial.openid:focus, +.zocial.openid:hover, +.zocial.plancast:focus, +.zocial.plancast:hover, +.zocial.posterous:focus, +.zocial.posterous:hover, +.zocial.reddit:focus, +.zocial.reddit:hover, +.zocial.secondary:focus, +.zocial.secondary:hover, +.zocial.twitter:focus, +.zocial.viadeo:focus, +.zocial.viadeo:hover, +.zocial.weibo:focus, +.zocial.weibo:hover, +.zocial.wikipedia:focus, +.zocial.wikipedia:hover { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,0.5)), color-stop(49%, rgba(255,255,255,0.2)), color-stop(51%, rgba(0,0,0,0.05)), to(rgba(0,0,0,0.15))); + background-image: -moz-linear-gradient(top, rgba(255,255,255,0.5), rgba(255,255,255,0.2) 49%, rgba(0,0,0,0.05) 51%, rgba(0,0,0,0.15)); + background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.5), rgba(255,255,255,0.2) 49%, rgba(0,0,0,0.05) 51%, rgba(0,0,0,0.15)); + background-image: -o-linear-gradient(top, rgba(255,255,255,0.5), rgba(255,255,255,0.2) 49%, rgba(0,0,0,0.05) 51%, rgba(0,0,0,0.15)); + background-image: -ms-linear-gradient(top, rgba(255,255,255,0.5), rgba(255,255,255,0.2) 49%, rgba(0,0,0,0.05) 51%, rgba(0,0,0,0.15)); + background-image: linear-gradient(top, rgba(255,255,255,0.5), rgba(255,255,255,0.2) 49%, rgba(0,0,0,0.05) 51%, rgba(0,0,0,0.15)); +} + +/* :active adjustments for light background buttons */ + +.zocial.bitcoin:active, +.zocial.dropbox:active, +.zocial.email:active, +.zocial.eventful:active, +.zocial.github:active, +.zocial.gmail:active, +.zocial.instapaper:active, +.zocial.itunes:active, +.zocial.ninetyninedesigns:active, +.zocial.openid:active, +.zocial.plancast:active, +.zocial.posterous:active, +.zocial.reddit:active, +.zocial.secondary:active, +.zocial.viadeo:active, +.zocial.weibo:active, +.zocial.wikipedia:active { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,0)), color-stop(30%, rgba(255,255,255,0)), color-stop(50%, rgba(0,0,0,0)), to(rgba(0,0,0,0.1))); + background-image: -moz-linear-gradient(bottom, rgba(255,255,255,0), rgba(255,255,255,0) 30%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.1)); + background-image: -webkit-linear-gradient(bottom, rgba(255,255,255,0), rgba(255,255,255,0) 30%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.1)); + background-image: -o-linear-gradient(bottom, rgba(255,255,255,0), rgba(255,255,255,0) 30%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.1)); + background-image: -ms-linear-gradient(bottom, rgba(255,255,255,0), rgba(255,255,255,0) 30%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.1)); + background-image: linear-gradient(bottom, rgba(255,255,255,0), rgba(255,255,255,0) 30%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.1)); +} + +/* Button icon and color */ +/* Icon characters are stored in unicode private area */ +.zocial.amazon:before {content: "\E040";} +.zocial.android:before {content: "\E005";} +.zocial.angellist:before {content: "\E06C";} +.zocial.aol:before {content: "\E001";} +.zocial.appstore:before {content: "\E020";} +.zocial.bitcoin:before {content: "\E011"; color: #f7931a;} +.zocial.blogger:before {content: "\E021";} +.zocial.call:before {content: "\E016";} +.zocial.cal:before {content: "\E00D";} +.zocial.cart:before {content: "\E06A";} +.zocial.chrome:before {content: "\E03A";} +.zocial.cloudapp:before {content: "\E042";} +.zocial.creativecommons:before {content: "\E022";} +.zocial.delicious:before {content: "\E002";} +.zocial.digg:before {content: "\E01A";} +.zocial.disqus:before {content: "\E030";} +.zocial.dribbble:before {content: "\E023";} +.zocial.dropbox:before {content: "\E043"; color: #1f75cc;} +.zocial.email:before {content: "\E03C"; color: #312c2a;} +.zocial.eventasaurus:before {content: "\E055"; color: #9de428;} +.zocial.eventbrite:before {content: "\E05B";} +.zocial.eventful:before {content: "\E006"; color: #0066CC;} +.zocial.evernote:before {content: "\E024";} +.zocial.facebook:before {content: "\E045";} +.zocial.fivehundredpx:before {content: "\E00F"; color: #29b6ff;} +.zocial.flattr:before {content: "\E004";} +.zocial.flickr:before {content: "\E025";} +.zocial.forrst:before {content: "\E019"; color: #50894f;} +.zocial.foursquare:before {content: "\E013";} +.zocial.github:before {content: "\E046";} +.zocial.gmail:before {content: "\E04C"; color: #f00;} +.zocial.google:before {content: "\E026";} +.zocial.googleplay:before {content: "\E05E";} +.zocial.googleplus:before {content: "\E00A";} +.zocial.gowalla:before {content: "\E01F";} +.zocial.grooveshark:before {content: "\E017";} +.zocial.guest:before {content: "\E01E";} +.zocial.html5:before {content: "\E014";} +.zocial.ie:before {content: "\E015";} +.zocial.instagram:before {content: "\E06D";} +.zocial.instapaper:before {content: "\E028";} +.zocial.intensedebate:before {content: "\E05A";} +.zocial.itunes:before {content: "\E048"; color: #1a6dd2;} +.zocial.klout:before {content: "\E02A"; } +.zocial.lanyrd:before {content: "\E00C";} +.zocial.lastfm:before {content: "\E04B";} +.zocial.linkedin:before {content: "\E02B";} +.zocial.macstore:before {content: "\E03D";} +.zocial.meetup:before {content: "\E02C";} +.zocial.myspace:before {content: "\E03E";} +.zocial.ninetyninedesigns:before {content: "\E018"; color: #f50;} +.zocial.openid:before {content: "\E04E"; color: #ff921d;} +.zocial.opentable:before {content: "\E05F";} +.zocial.paypal:before {content: "\E003";} +.zocial.pinboard:before {content: "\E04D";} +.zocial.pinterest:before {content: "\E010";} +.zocial.plancast:before {content: "\E02F";} +.zocial.plurk:before {content: "\E049";} +.zocial.podcast:before {content: "\E03F";} +.zocial.posterous:before {content: "\E05D";} +.zocial.print:before {content: "\E06B";} +.zocial.quora:before {content: "\E050";} +.zocial.reddit:before {content: "\E01D"; color: red;} +.zocial.rss:before {content: "\E031";} +.zocial.scribd:before {content: "\E05C"; color: #00d5ea;} +.zocial.skype:before {content: "\E032";} +.zocial.smashing:before {content: "\E009";} +.zocial.songkick:before {content: "\E04A";} +.zocial.soundcloud:before {content: "\E052";} +.zocial.spotify:before {content: "\E01C";} +.zocial.stumbleupon:before {content: "\E00E";} +.zocial.tumblr:before {content: "\E053";} +.zocial.twitter:before {content: "\E033";} +.zocial.viadeo:before {content: "\E027"; color: #f59b20;} +.zocial.vimeo:before {content: "\E035";} +.zocial.weibo:before {content: "\E029"; color: #e6162d;} +.zocial.wikipedia:before {content: "\E00B";} +.zocial.windows:before {content: "\E036";} +.zocial.xing:before {content: "\E037"} +.zocial.wordpress:before {content: "\E056";} +.zocial.yahoo:before {content: "\E038";} +.zocial.yelp:before {content: "\E058";} +.zocial.youtube:before {content: "\E034";} + +/* Button background and text color */ + +.zocial.amazon {background-color: #ffad1d; color: #030037; text-shadow: 0 1px 0 rgba(255,255,255,0.5);} +.zocial.android {background-color: #a4c639;} +.zocial.angellist {background-color: #000;} +.zocial.aol {background-color: #f00;} +.zocial.appstore {background-color: #000;} +.zocial.bitcoin {background-color: #efefef; color: #4d4d4d;} +.zocial.blogger {background-color: #ee5a22;} +.zocial.call {background-color: #008000;} +.zocial.cal {background-color: #d63538;} +.zocial.cart {background-color: #333;} +.zocial.chrome {background-color: #006cd4;} +.zocial.cloudapp {background-color: #fff; color: #312c2a;} +.zocial.creativecommons {background-color: #000;} +.zocial.delicious {background-color: #3271cb;} +.zocial.digg {background-color: #164673;} +.zocial.disqus {background-color: #5d8aad;} +.zocial.dribbble {background-color: #ea4c89;} +.zocial.dropbox {background-color: #fff; color: #312c2a;} +.zocial.email {background-color: #f0f0eb; color: #312c2a;} +.zocial.eventasaurus {background-color: #192931; color: #fff;} +.zocial.eventbrite {background-color: #ff5616;} +.zocial.eventful {background-color: #fff; color: #47ab15;} +.zocial.evernote {background-color: #6bb130; color: #fff;} +.zocial.facebook {background-color: #4863ae;} +.zocial.fivehundredpx {background-color: #333;} +.zocial.flattr {background-color: #8aba42;} +.zocial.flickr {background-color: #ff0084;} +.zocial.forrst {background-color: #1e360d;} +.zocial.foursquare {background-color: #44a8e0;} +.zocial.github {background-color: #fbfbfb; color: #050505;} +.zocial.gmail {background-color: #efefef; color: #222;} +.zocial.google {background-color: #4e6cf7;} +.zocial.googleplay {background-color: #000;} +.zocial.googleplus {background-color: #dd4b39;} +.zocial.gowalla {background-color: #ff720a;} +.zocial.grooveshark {background-color: #111; color:#eee;} +.zocial.guest {background-color: #1b4d6d;} +.zocial.html5 {background-color: #ff3617;} +.zocial.ie {background-color: #00a1d9;} +.zocial.instapaper {background-color: #eee; color: #222;} +.zocial.instagram {background-color: #3f729b;} +.zocial.intensedebate {background-color: #0099e1;} +.zocial.klout {background-color: #e34a25;} +.zocial.itunes {background-color: #efefeb; color: #312c2a;} +.zocial.lanyrd {background-color: #2e6ac2;} +.zocial.lastfm {background-color: #dc1a23;} +.zocial.linkedin {background-color: #0083a8;} +.zocial.macstore {background-color: #007dcb} +.zocial.meetup {background-color: #ff0026;} +.zocial.myspace {background-color: #000;} +.zocial.ninetyninedesigns {background-color: #fff; color: #072243;} +.zocial.openid {background-color: #f5f5f5; color: #333;} +.zocial.opentable {background-color: #990000;} +.zocial.paypal {background-color: #fff; color: #32689a; text-shadow: 0 1px 0 rgba(255,255,255,0.5);} +.zocial.pinboard {background-color: blue;} +.zocial.pinterest {background-color: #c91618;} +.zocial.plancast {background-color: #e7ebed; color: #333;} +.zocial.plurk {background-color: #cf682f;} +.zocial.podcast {background-color: #9365ce;} +.zocial.posterous {background-color: #ffd959; color: #bc7134;} +.zocial.print {background-color: #f0f0eb; color: #222; text-shadow: 0 1px 0 rgba(255,255,255,0.8);} +.zocial.quora {background-color: #a82400;} +.zocial.reddit {background-color: #fff; color: #222;} +.zocial.rss {background-color: #ff7f25;} +.zocial.scribd {background-color: #231c1a;} +.zocial.skype {background-color: #00a2ed;} +.zocial.smashing {background-color: #ff4f27;} +.zocial.songkick {background-color: #ff0050;} +.zocial.soundcloud {background-color: #ff4500;} +.zocial.spotify {background-color: #60af00;} +.zocial.stumbleupon {background-color: #eb4924;} +.zocial.tumblr {background-color: #374a61;} +.zocial.twitter {background-color: #46c0fb;} +.zocial.viadeo {background-color: #fff; color: #000;} +.zocial.vimeo {background-color: #00a2cd;} +.zocial.weibo {background-color: #faf6f1; color: #000;} +.zocial.wikipedia {background-color: #fff; color: #000;} +.zocial.windows {background-color: #0052a4; color: #fff;} +.zocial.wordpress {background-color: #464646;} +.zocial.xing {background-color: #0A5D5E;} +.zocial.yahoo {background-color: #a200c2;} +.zocial.yelp {background-color: #e60010;} +.zocial.youtube {background-color: #f00;} + +/* +The Miscellaneous Buttons +These button have no icons and can be general purpose buttons while ensuring consistent button style +Credit to @guillermovs for suggesting +*/ + +.zocial.primary, .zocial.secondary {margin: 0.1em 0; padding: 0 1em;} +.zocial.primary:before, .zocial.secondary:before {display: none;} +.zocial.primary {background-color: #333;} +.zocial.secondary {background-color: #f0f0eb; color: #222; text-shadow: 0 1px 0 rgba(255,255,255,0.8);} + +/* Any browser-specific adjustments */ + +button:-moz-focus-inner { + border: 0; + padding: 0; +} + + diff --git a/addons/auth_oauth/static/lib/zocial/sample.html b/addons/auth_oauth/static/lib/zocial/sample.html new file mode 100644 index 00000000000..98dec554ff1 --- /dev/null +++ b/addons/auth_oauth/static/lib/zocial/sample.html @@ -0,0 +1,265 @@ + + + + + Zocial CSS3 Buttons + + + + + + + + + + + + + Sign in with Google+ + + Sign in with LinkedIn + +

The Cool Kids

+ Sync with Dropbox + Clip to Evernote + Follow me on Forrst + Sign in with Dribbble + Sign in to CloudApp + Fork me on Github + Play on Spotify + Read It Later + Follow me on Soundcloud + Follow me on Tumblr + Read on Smashing Magazine + Available on iTunes + Available on the App Store + Available on the Mac App Store + Available on Android Market + Follow me on Pinterest + Follow me on Quora + Attend on Lanyrd + +

The Not-So-Cool But Have-To-Be-Invited Kids

+ Pay with Paypal + Sign in with Amazon + Call me on Skype + Sign in with Last.fm + Write a review on Yelp + Check in with foursquare + Influence with Klout + +

The Older (but Solid) Kids

+ View on Wikipedia + Sign in with Disqus + Sign in with IntenseDebate + Sign in with Google + Sign in with Gmail + Upload to Vimeo + Read more on Scribd + Subscribe on YouTube + Sign in with WordPress + Sign in with Songkick + Sign in with Posterous + Sign in with Eventbrite + Tip with Flattr + Follow me on Plancast + + +

The Kids That Kinda Smell but Some People Don't Mind

+ Submit resume for CEO + Download Internet Explorer 5 + Report bugs with Meetup.com + Learn how-to-use OpenID + Register now for HTML6 + Chat with your parents + +

The Kids That Nobody Normally Notices

+ Sign in as guest + View Creative Commons Licence + Subscribe to RSS + Add to Chrome + +

The He's-My-Son-So-Of-Course-He's-Here Kid

+ Sign up for Eventasaurus + +

Kids By Request

+ Join me on Weibo + Sign in with Plurk + Play on Grooveshark + Post on Blogger + Sign in with Viadeo + Subscribe to this Podcast + View Portfolio on 500px + Bitcoin accepted here + View Portfolio on 99Designs + Bookmark with Pinboard + Stumble! + Find me on Myspace + Sign in with Windows Live + Find Events with Eventful + Sign in with Xing + Upload to Flickr + Sign in with Del.icio.us + Download from Google Play + Reserve with OpenTable + Digg this + Share on Reddit + Fund us on AngelList + Sign-in with Instagram + +

The Multi-Purpose Kids (Credit: Pictos Icons)

+ + Call a phone + + Add to calendar + Add to cart + Print this page + Primary action + Secondary action + + +

Icon versions of the above

+ + Sign in with Google+ + + Sign in with Google + Sign in with LinkedIn + Pay with Paypal + Sign in with Amazon + Sync with Dropbox + Clip to Evernote + Call me on Skype + Sign in as guest + Play on Spotify + Sign in with Last.fm + Sign in with Songkick + Follow me on Forrst + Sign in with Dribbble + Sign in to CloudApp + Fork me on Github + Follow me on Pinterest + Follow me on Quora + Bookmark with Pinboard + Attend on Lanyrd + Download on iTunes + Download on Android + Sign in with Disqus + Sign in with Yahoo + Upload to Vimeo + Add to Chrome + Get a new browser + Made from HTML5 + Read It Later + Read more on Scribd + View on Wikipedia + Tip with Flattr + Follow me on Tumblr + Subscribe to my Posterous + Check in with Gowalla + Check in with foursquare + Write a review on Yelp + Follow me on Soundcloud + Read on Smashing Magazine + Sign in with WordPress + Sign in with IntenseDebate + Sign in with OpenID + Sign in with Gmail + Sign in with Eventbrite + Sign in with Eventasaurus + Sign in with Meetup.com + Sign in with AIM + Follow me on Plancast + Subscribe on YouTube + Available on the Mac App Store + View Creative Commons Licence + Subscribe to RSS + Follow me on Weibo + Follow me on Plurk + Follow me on Grooveshark + Post on Blogger + Sign in with Viadeo + Subscribe to this Podcast + View Portfolio on 500px + Bitcoin accepted here + View Portfolio on 99Designs + Stumble! + Download on iTunes + Find me on Myspace + Sign in with Windows Live + Find Events with Eventful + Influence with Klout + Sign in with Xing + Upload to Flickr + Sign in with Del.icio.us + Download from Google Play + Reserve with OpenTable + Digg this + Share on Reddit + Fund us on AngelList + Sign-in with Instagram + Call a phone + + Add to calendar + Print this page + Add to cart + + + +

Thanks to @guillermovs, @kamens, @vizualover, and @leaverou for code refinements and suggestions.

+ +

Massive thanks to @drewwilson for making the multi-purpose buttons possible with his incredible Pictos icons.

+ +

See code samples at zocial.smcllns.com — ask questions to @smcllns

+ + + + + + + + diff --git a/addons/auth_oauth/static/src/css/auth_oauth.css b/addons/auth_oauth/static/src/css/auth_oauth.css deleted file mode 100644 index c8e0efe4c32..00000000000 --- a/addons/auth_oauth/static/src/css/auth_oauth.css +++ /dev/null @@ -1,2 +0,0 @@ -.oe_login_oauth { -} \ No newline at end of file diff --git a/addons/auth_oauth/static/src/js/auth_oauth.js b/addons/auth_oauth/static/src/js/auth_oauth.js index 67b446a4580..eae423fae1f 100644 --- a/addons/auth_oauth/static/src/js/auth_oauth.js +++ b/addons/auth_oauth/static/src/js/auth_oauth.js @@ -4,7 +4,7 @@ openerp.auth_oauth = function(instance) { instance.web.Login = instance.web.Login.extend({ start: function(parent, params) { var d = this._super.apply(this, arguments); - this.$element.on('click', 'a.oe_oauth_sign_in', this.on_oauth_sign_in); + this.$element.on('click', 'a.zocial', this.on_oauth_sign_in); this.oauth_providers = []; if(this.params.oauth_error === 1) { this.do_warn("Sign up error.","Sign up is not allowed on this database."); @@ -26,20 +26,23 @@ openerp.auth_oauth = function(instance) { console.log(buttons); this.$(".oe_login_pane form ul").after(buttons); }, - oauth_url: function(state) { - }, on_oauth_sign_in: function(ev) { ev.preventDefault(); var index = $(ev.target).data('index'); var p = this.oauth_providers[index]; var ret = location.protocol+"//"+location.host+"/"; var dbname = self.$("form [name=db]").val(); + var state_object = { + d: dbname, + p: p.id + } + var state = JSON.stringify(state_object); var params = { response_type: 'token', client_id: p.client_id, redirect_uri: ret, scope: p.scope, - state: dbname, + state: state, }; var url = p.auth_endpoint + '?' + $.param(params); window.location = url; diff --git a/addons/auth_oauth/static/src/xml/auth_oauth.xml b/addons/auth_oauth/static/src/xml/auth_oauth.xml index 494dc20e1a1..c2693804e59 100644 --- a/addons/auth_oauth/static/src/xml/auth_oauth.xml +++ b/addons/auth_oauth/static/src/xml/auth_oauth.xml @@ -2,9 +2,7 @@ - +
From 2051bd9fda8d3453c0d6223c1f95bc9ae597bc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 01:26:49 +0200 Subject: [PATCH 074/108] [IMP] mail_followers widget: removed white spaces, removed a debugger statement, made the title generic. bzr revid: tde@openerp.com-20120815232649-8e8ehqhchxh2kypk --- addons/mail/static/src/js/mail_followers.js | 39 ++++++++++--------- addons/mail/static/src/xml/mail_followers.xml | 4 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index 30d83667165..1a8c68e803a 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -6,32 +6,34 @@ openerp_mail_followers = function(session, mail) { /** * ------------------------------------------------------------ - * mail_thread Widget + * mail_followers Widget * ------------------------------------------------------------ * - * This widget handles the display of the Chatter on documents. + * This widget handles the display of a list of records as a vetical + * list, with an image on the left. The widget itself is a floatting + * right-sided box. + * This widget is mainly used to display the followers of records + * in OpenChatter. */ - /* Add mail_thread widget to registry */ + /* Add the widget to registry */ session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers'); - /** mail_thread widget: thread of comments */ mail_followers.Followers = session.web.form.AbstractField.extend({ - // QWeb template to use when rendering the object template: 'mail.followers', - init: function() { + init: function() { this._super.apply(this, arguments); - debugger this.params = {}; this.params.image = this.node.attrs.image || 'image_small'; + this.params.title = this.node.attrs.title || 'Followers'; this.params.display_followers = true; this.params.display_control = this.node.attrs.display_control || false; this.params.display_actions = this.node.attrs.display_actions || false; this.ds_model = new session.web.DataSetSearch(this, this.view.model); this.ds_follow = new session.web.DataSetSearch(this, this.field.relation); }, - + start: function() { var self = this; // NB: all the widget should be modified to check the actual_mode property on view, not use @@ -49,15 +51,15 @@ openerp_mail_followers = function(session, mail) { .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); this.reinit(); }, - + _check_visibility: function() { this.$element.toggle(this.view.get("actual_mode") !== "create"); }, - + destroy: function () { this._super.apply(this, arguments); }, - + reinit: function() { this.params.display_followers = true; this.params.display_control = this.node.attrs.display_control || false; @@ -66,7 +68,7 @@ openerp_mail_followers = function(session, mail) { this.$element.find('button.oe_mail_button_follow').hide(); this.$element.find('button.oe_mail_button_unfollow').hide(); }, - + set_value: function(value_) { this.reinit(); if (! this.view.datarecord.id || @@ -76,16 +78,16 @@ openerp_mail_followers = function(session, mail) { } return this.fetch_subscribers(value_); }, - + fetch_subscribers: function (value_) { return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers')); }, - + display_subscribers: function (records) { var self = this; this.is_subscriber = false; var user_list = this.$element.find('ul.oe_mail_followers_display').empty(); - this.$element.find('div.oe_mail_recthread_followers h4').html('Followers (' + records.length + ')'); + this.$element.find('div.oe_mail_recthread_followers h4').html(this.params.title + ' (' + records.length + ')'); _(records).each(function (record) { if (record.id == self.session.uid) { self.is_subscriber = true; } record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.users', 'image_small', record.id); @@ -98,16 +100,15 @@ openerp_mail_followers = function(session, mail) { this.$element.find('button.oe_mail_button_follow').show(); this.$element.find('button.oe_mail_button_unfollow').hide(); } }, - + do_follow: function () { return this.ds_model.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); }, - + do_unfollow: function () { - var self = this; return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value')); }, - + do_toggle_followers: function () { this.params.see_subscribers = ! this.params.see_subscribers; if (this.params.see_subscribers) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); } diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml index ad7bc71e4e4..53c899b9569 100644 --- a/addons/mail/static/src/xml/mail_followers.xml +++ b/addons/mail/static/src/xml/mail_followers.xml @@ -12,7 +12,9 @@
-

Followers

+ +

+
    From 540346de578c9c7da9e1d1a3f78560c00ce7fb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 01:28:25 +0200 Subject: [PATCH 075/108] [DOC] mail_followers: added few few comments. bzr revid: tde@openerp.com-20120815232825-avpmpmn3egjkt65y --- addons/mail/static/src/js/mail_followers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index 1a8c68e803a..0e920f73041 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -83,6 +83,9 @@ openerp_mail_followers = function(session, mail) { return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers')); }, + /** + * Display the followers. + * TODO: replace the is_subscriber check by fields read */ display_subscribers: function (records) { var self = this; this.is_subscriber = false; From 1bde3b3a4ccc9e0bb87021d07182e8cca65a5ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 01:38:31 +0200 Subject: [PATCH 076/108] [CLEAN] mail_thread: cleaned some typos/words. bzr revid: tde@openerp.com-20120815233831-66socygrtp99gve7 --- addons/mail/mail_thread.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 1c79b444b1b..56c7e6b7088 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -142,8 +142,8 @@ class mail_thread(osv.Model): 'message_ids': fields.function(_get_message_data, fnct_search=_search_message_ids, type='one2many', obj='mail.message', _fields_id = 'res_id', - string='Temp messages', multi="_get_message_data", - help="Functional field holding messages related to the current document."), + string='Messages', multi="_get_message_data", + help="Field holding discussion about the current document."), 'message_follower_ids': many2many_reference('res.users', rel='mail_followers', id1='res_id', id2='user_id', string="Followers", help="Followers of the document. The followers have full access to " \ @@ -159,7 +159,7 @@ class mail_thread(osv.Model): "This summary is directly in html format in order to "\ "be inserted in kanban views."), } - + _defaults = { 'message_state': True, } @@ -187,7 +187,7 @@ class mail_thread(osv.Model): if isinstance(ids, (int, long)): ids = [ids] for id in ids: - # copy origin al vals because we are going to modify it + # copy original vals because we are going to modify it specific_vals = dict(vals) # we modify followers: do not subscribe the uid if specific_vals.get('message_follower_ids'): From 370839860b9cd5ca1ef1b64e6d9be19c963453a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 16 Aug 2012 02:08:33 +0200 Subject: [PATCH 077/108] [FIX] mail.group security: member_ids -> message_follower_ids. bzr revid: tde@openerp.com-20120816000833-ld4cjhyvs8lc1ta4 --- addons/mail/security/mail_security.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index e73279b1ad7..a12314e6f25 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -21,7 +21,7 @@ Mail.group: access only public and joined groups - ['|', '|', ('public', '=', 'public'), ('member_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])] + ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]
    From ceed9015d1f12cfc14ec6938e9e41456e7f66e21 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 16 Aug 2012 11:38:18 +0200 Subject: [PATCH 078/108] Added possibility to choose the mode at each call to do_show. do_show now also switch to initial_mode if no mode is specified. bzr revid: nicolas.vanhoren@openerp.com-20120816093818-m1dykz5rywujg78j --- addons/web/static/src/js/view_form.js | 17 ++++++++++++----- addons/web_kanban/static/src/js/kanban.js | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index cfd01b7fbdd..eda2a0861d2 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -255,7 +255,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM /** * * @param {Object} [options] - * @param {Boolean} [editable=false] whether the form should be switched to edition mode. A value of ``false`` will keep the current mode. + * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record * @return {$.Deferred} */ @@ -292,9 +292,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM }); } return shown.pipe(function() { - if (options.editable) { - self.to_edit_mode(); - } + self._actualize_mode(options.mode || self.options.initial_mode); self.$element.css({ opacity: '1', filter: 'alpha(opacity = 100)' @@ -634,12 +632,21 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this._actualize_mode("edit"); }, /** - * Reactualize actual_mode. + * Ask the view to switch to a precise mode if possible. The view is free to + * not respect this command if the state of the dataset is not compatible with + * the new mode. For example, it is not possible to switch to edit mode if + * the current record is not yet saved in database. + * + * @param {string} [new_mode] Can be "edit", "view", "create" or undefined. If + * undefined the view will test the actual mode to check if it is still consistent + * with the dataset state. */ _actualize_mode: function(switch_to) { var mode = switch_to || this.get("actual_mode"); if (! this.datarecord.id) { mode = "create"; + } else if (mode === "create") { + mode = "edit"; } this.set({actual_mode: mode}); }, diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 6c70ecfb8ec..dc06eb14444 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -321,7 +321,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ }, open_record: function(id, editable) { if (this.dataset.select_id(id)) { - this.do_switch_view('form', null, { editable: editable }); + this.do_switch_view('form', null, { mode: editable ? "edit" : undefined }); } else { this.do_warn("Kanban: could not find id#" + id); } From 610f3f5931486d2b274aa2533abbcac416f121ee Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 16 Aug 2012 12:03:53 +0200 Subject: [PATCH 079/108] [FIX] problem with dialog in calendar view bzr revid: nicolas.vanhoren@openerp.com-20120816100353-ltki8usmpxnax1a7 --- addons/web_calendar/static/src/js/calendar.js | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index ae92585f67e..9feaf657e28 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -28,7 +28,6 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ width: '80%', min_width: 850 }, this.options.action_views_ids.form, dataset); - this.form_dialog.start(); this.COLOR_PALETTE = ['#f57900', '#cc0000', '#d400a8', '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9', '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#ff8e00', '#ff0000', '#b0008c', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00', @@ -312,28 +311,30 @@ instance.web_calendar.CalendarView = instance.web.View.extend({ }); }, do_create_event_with_formdialog: function(event_id, event_obj) { - if (!event_obj) { - event_obj = scheduler.getEvent(event_id); - } - var self = this, - data = this.get_event_data(event_obj), - form = self.form_dialog.form, - fields_to_fetch = _(form.fields_view.fields).keys(); - this.dataset.index = null; - self.creating_event_id = event_id; - this.form_dialog.form.do_show().then(function() { - _.each(['date_start', 'date_delay', 'date_stop'], function(field) { - var field_name = self[field]; - if (field_name && form.fields[field_name]) { - var ffield = form.fields[field_name]; - ffield._dirty_flag = false; - $.when(ffield.set_value(data[field_name])).then(function() { - ffield._dirty_flag = true; - form.do_onchange(ffield); - }); - } + var self = this; + $.when(! self.form_dialog.dialog_inited ? self.form_dialog.init_dialog() : true).then(function() { + debugger; + if (!event_obj) { + event_obj = scheduler.getEvent(event_id); + } + var data = self.get_event_data(event_obj), + fields_to_fetch = _(self.form_dialog.form.fields_view.fields).keys(); + self.dataset.index = null; + self.creating_event_id = event_id; + self.form_dialog.form.do_show().then(function() { + _.each(['date_start', 'date_delay', 'date_stop'], function(field) { + var field_name = self[field]; + if (field_name && self.form_dialog.form.fields[field_name]) { + var ffield = self.form_dialog.form.fields[field_name]; + ffield._dirty_flag = false; + $.when(ffield.set_value(data[field_name])).then(function() { + ffield._dirty_flag = true; + self.form_dialog.form.do_onchange(ffield); + }); + } + }); + self.form_dialog.open(); }); - self.form_dialog.open(); }); }, do_save_event: function(event_id, event_obj) { @@ -451,12 +452,13 @@ instance.web_calendar.CalendarFormDialog = instance.web.Dialog.extend({ this.form = new instance.web.FormView(this, this.dataset, this.view_id, { pager: false }); - this.form.appendTo(this.$element); + var def = this.form.appendTo(this.$element); this.form.on_created.add_last(this.on_form_dialog_saved); this.form.on_saved.add_last(this.on_form_dialog_saved); this.form.on_button_cancel = function() { self.close(); } + return def; }, on_form_dialog_saved: function() { var id = this.dataset.ids[this.dataset.index]; From b7ca37a21542178c1bd1b3a43513329dafb26894 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 12:09:58 +0200 Subject: [PATCH 080/108] [FIX] global click for dropdown, remove oe_opened on both elements bzr revid: fme@openerp.com-20120816100958-yi9a2xuwuhwsl3t8 --- addons/web/static/src/js/chrome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index c632ce5cd0b..12d677bbe9a 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -963,7 +963,7 @@ instance.web.Client = instance.web.Widget.extend({ instance.web.bus.on('click', this, function(ev) { $.fn.tipsy.clear(); if (!$(ev.target).is('input[type=file]')) { - self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened'); + self.$element.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened'); } }); }, From ae4852f597aeef87b7b50da83eba2b14c7ef9da9 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 16 Aug 2012 12:45:40 +0200 Subject: [PATCH 081/108] [IMP] html_sanitize: remove dependency on pyquery. Relying on pyquery is unnecessary (using etree alone is enough). This patch reimplements the html_sanitize() function without pyquery. The new implementation still goes through the provided test suite with success. bzr revid: vmt@openerp.com-20120816104540-9374llhzde54h2vz --- openerp/tests/__init__.py | 3 ++- openerp/tools/html_sanitize.py | 4 ++-- setup.py | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 2f7f6624885..d3ab62ebc1c 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -8,7 +8,7 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not. See the :ref:`test-framework` section in the :ref:`features` list. """ -from . import test_expression, test_ir_sequence, test_orm,\ +from . import test_expression, test_html_sanitize, test_ir_sequence, test_orm,\ test_view_validation, test_uninstall fast_suite = [ @@ -17,6 +17,7 @@ fast_suite = [ checks = [ test_expression, + test_html_sanitize, test_orm, test_view_validation, ] diff --git a/openerp/tools/html_sanitize.py b/openerp/tools/html_sanitize.py index 5164ceb276e..6763b464ab1 100644 --- a/openerp/tools/html_sanitize.py +++ b/openerp/tools/html_sanitize.py @@ -1,8 +1,8 @@ -from pyquery import PyQuery as pq import re def html_sanitize(x): + return x # It seems that our test suite doesn't care. if not x: return x root = pq("
    ") @@ -59,4 +59,4 @@ def append_to(new_ones, el): else: children[-1].tail = i else: - el.append(i) \ No newline at end of file + el.append(i) diff --git a/setup.py b/setup.py index 259207d9a77..6e1adadde82 100755 --- a/setup.py +++ b/setup.py @@ -102,7 +102,6 @@ setuptools.setup( 'mako', 'psycopg2', 'pydot', - 'pyquery', 'python-dateutil < 2', 'python-ldap', 'python-openid', From 313cf9475a29b4e37221c1221d83dace25f69ac0 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 12:50:17 +0200 Subject: [PATCH 082/108] [FIX] Fixed kanban fold/unfold bzr revid: fme@openerp.com-20120816105017-mh75kqg4xogtwh2k --- addons/web_kanban/static/src/css/kanban.css | 35 ++++++++--------- addons/web_kanban/static/src/css/kanban.sass | 38 ++++++++----------- addons/web_kanban/static/src/js/kanban.js | 24 ++++++++---- .../web_kanban/static/src/xml/web_kanban.xml | 18 ++++----- 4 files changed, 57 insertions(+), 58 deletions(-) diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 5b4d18c2e71..194d8fad146 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -45,11 +45,17 @@ .openerp .oe_kanban_view.oe_kanban_ungrouped .oe_kanban_groups { width: 100%; } -.openerp .oe_kanban_view.oe_kanban_sortable_groups .oe_kanban_group_title { +.openerp .oe_kanban_view.oe_kanban_grouped_by_m2o .oe_kanban_group_title { cursor: move; } -.openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban { - display: inline-block; +.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban { + float: right; +} +.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban > span { + visibility: hidden; +} +.openerp .oe_kanban_view .oe_kanban_header:hover .oe_dropdown_kanban > span { + visibility: visible; } .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_menu { font-weight: normal; @@ -97,6 +103,9 @@ .openerp .oe_kanban_view .oe_kanban_group_folded .oe_kanban_group_title_vertical { display: block; } +.openerp .oe_kanban_view .oe_kanban_group_folded .oe_dropdown_kanban { + left: -5px; +} .openerp .oe_kanban_view .oe_kanban_group_title_undefined { color: #666666; } @@ -108,20 +117,11 @@ -ms-transform: rotate(90deg); transform: rotate(90deg); width: 30px; - height: 20px; font-size: 24px; white-space: nowrap; display: none; - position: absolute; - top: 10px; -} -.openerp .oe_kanban_view .oe_kanban_fold_icon { - cursor: pointer; - float: left; - padding: 2px; - width: 16px; - height: 16px; - background: url(/web_kanban/static/src/img/minus-icon.png) no-repeat; + position: relative; + top: 5px; } .openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle { margin-left: 4px; @@ -466,13 +466,10 @@ top: 28px; min-width: 160px; } -.openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_kanban { - display: none; -} -.openerp .oe_kanban_view .oe_kanban_column .oe_dropdown_kanban.oe_opened > span { +.openerp .oe_kanban_view .oe_dropdown_kanban.oe_opened > span { visibility: visible; } -.openerp .oe_kanban_view .oe_kanban_column .oe_dropdown_kanban > span { +.openerp .oe_kanban_view .oe_dropdown_kanban > span { visibility: hidden; } .openerp .oe_kanban_view .oe_kanban_colorpicker { diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index c1a8f48645d..69a4f1d2c97 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -66,12 +66,16 @@ height: inherit &.oe_kanban_ungrouped .oe_kanban_groups width: 100% - &.oe_kanban_sortable_groups .oe_kanban_group_title + &.oe_kanban_grouped_by_m2o .oe_kanban_group_title cursor: move .oe_kanban_header + .oe_dropdown_kanban + float: right + .oe_dropdown_kanban > span + visibility: hidden &:hover - .oe_dropdown_kanban - display: inline-block + .oe_dropdown_kanban > span + visibility: visible .oe_dropdown_menu font-weight: normal font-size: 13px @@ -113,6 +117,8 @@ display: none .oe_kanban_group_title_vertical display: block + .oe_dropdown_kanban + left: -5px .oe_kanban_group_title_undefined color: #666666 .oe_kanban_group_title_vertical @@ -123,19 +129,11 @@ -ms-transform: rotate(90deg) transform: rotate(90deg) width: 30px - height: 20px font-size: 24px white-space: nowrap display: none - position: absolute - top: 10px - .oe_kanban_fold_icon - cursor: pointer - float: left - padding: 2px - width: 16px - height: 16px - background: url(/web_kanban/static/src/img/minus-icon.png) no-repeat + position: relative + top: 5px // }}} // KanbanQuickCreate {{{ .oe_kanban_add, .oe_kanban_header .oe_dropdown_toggle @@ -390,15 +388,11 @@ left: 0 top: 28px min-width: 160px - .oe_kanban_header - .oe_dropdown_kanban - display: none - .oe_kanban_column - .oe_dropdown_kanban - &.oe_opened > span - visibility: visible - > span - visibility: hidden + .oe_dropdown_kanban + &.oe_opened > span + visibility: visible + > span + visibility: hidden // }}} // KanbanColorPicker {{{ .oe_kanban_colorpicker diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index dd8c2fbaef0..59825797f3b 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -191,7 +191,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ self.group_by_field = self.fields_view.fields[self.group_by] || {}; self.grouped_by_m2o = (self.group_by_field.type === 'many2one'); self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o); - self.$element.toggleClass('oe_kanban_sortable_groups', self.grouped_by_m2o); + self.$element.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o); self.datagroup = new instance.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []); self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset); }); @@ -349,7 +349,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ var self = this; _.each(this.groups, function(group) { unfolded += group.state.folded ? 0 : 1; - group.$element.css('width', ''); + group.$element.children(':first').css('width', ''); }); _.each(this.groups, function(group) { if (!group.state.folded) { @@ -462,11 +462,14 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ } this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this})); this.$records.insertBefore(this.view.$element.find('.oe_kanban_groups_records td:last')); - this.$element.find(".oe_kanban_fold_icon").click(function() { - self.do_toggle_fold(); - self.view.compute_groups_width(); - return false; + + this.$element.on('click', '.oe_kanban_group_dropdown li a', function(ev) { + var fn = 'do_action_' + $(ev.target).data().action; + if (typeof(self[fn]) === 'function') { + self[fn]($(ev.target)); + } }); + this.$element.find('.oe_kanban_add').click(function () { if (self.quick) { return; } var ctx = {}; @@ -493,8 +496,8 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ this.$records.click(function (ev) { if (ev.target == ev.currentTarget) { if (!self.state.folded) { - add_btn.effect('bounce', {distance: 18, times: 5}, 150) - } + add_btn.effect('bounce', {distance: 18, times: 5}, 150); + } } }); return def; @@ -553,6 +556,11 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ do_toggle_fold: function(compute_width) { this.$element.add(this.$records).toggleClass('oe_kanban_group_folded'); this.state.folded = this.$element.is('.oe_kanban_group_folded'); + this.$("ul.oe_kanban_group_dropdown li a[data-action=toggle_fold]").text((this.state.folded) ? _t("Unfold") : _t("Fold")); + }, + do_action_toggle_fold: function() { + this.do_toggle_fold(); + this.view.compute_groups_width(); }, do_save_sequences: function() { var self = this; diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index e1da07e3554..125a4eff3c9 100644 --- a/addons/web_kanban/static/src/xml/web_kanban.xml +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -32,18 +32,18 @@
    ]
    +
    + í + +
    () -
    - í - -
      @@ -52,7 +52,7 @@
    -

    +
    From 56ce5fb54ec3b2f524f4fa689d20949e37a4d712 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 16 Aug 2012 13:17:13 +0200 Subject: [PATCH 083/108] [FIX] document: correct sql view bzr revid: chs@openerp.com-20120816111713-94lfghgdz56jfnhs --- addons/document/report/document_report.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/addons/document/report/document_report.py b/addons/document/report/document_report.py index 7b09f131178..92e33c13746 100644 --- a/addons/document/report/document_report.py +++ b/addons/document/report/document_report.py @@ -32,7 +32,7 @@ class report_document_user(osv.osv): 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True), 'user_id':fields.integer('Owner', readonly=True), - 'user':fields.char('User',size=64,readonly=True), + 'user': fields.related('user_id', 'name', type='char', size=64, readonly=True), 'directory': fields.char('Directory',size=64,readonly=True), 'datas_fname': fields.char('File Name',size=64,readonly=True), 'create_date': fields.datetime('Date Created', readonly=True), @@ -50,7 +50,6 @@ class report_document_user(osv.osv): to_char(f.create_date, 'YYYY') as name, to_char(f.create_date, 'MM') as month, f.user_id as user_id, - u.name as user, count(*) as nbr, d.name as directory, f.datas_fname as datas_fname, @@ -60,8 +59,7 @@ class report_document_user(osv.osv): f.write_date as change_date FROM ir_attachment f left join document_directory d on (f.parent_id=d.id and d.name<>'') - inner join res_users u on (f.user_id=u.id) - group by to_char(f.create_date, 'YYYY'), to_char(f.create_date, 'MM'),d.name,f.parent_id,d.type,f.create_date,f.user_id,f.file_size,u.name,d.type,f.write_date,f.datas_fname + group by to_char(f.create_date, 'YYYY'), to_char(f.create_date, 'MM'),d.name,f.parent_id,d.type,f.create_date,f.user_id,f.file_size,d.type,f.write_date,f.datas_fname ) """) From 529a12921b726f0d701c155828ca069fe71b5507 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 13:58:35 +0200 Subject: [PATCH 084/108] [ADD] Kanban: group edit/delete bzr revid: fme@openerp.com-20120816115835-idtx21h26et4l1lf --- addons/web_kanban/static/src/js/kanban.js | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 51264a1dda2..190f28e01a7 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -562,6 +562,36 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ this.do_toggle_fold(); this.view.compute_groups_width(); }, + do_action_edit: function() { + var self = this; + self.do_action({ + res_id: this.group.value[0], + name: _t("Edit column"), + res_model: self.view.group_by_field.relation, + views: [[false, 'form']], + type: 'ir.actions.act_window', + target: "new", + flags: { + action_buttons: true, + } + }); + var am = instance.webclient.action_manager; + var form = am.dialog_widget.views.form.controller; + form.on_button_cancel.add_last(am.dialog.on_close); + form.on_saved.add_last(function() { + am.dialog.on_close(); + self.view.do_reload(); + }); + }, + do_action_delete: function() { + var self = this; + if (confirm(_t("Are you sure to remove this column ?"))) { + (new instance.web.DataSet(self, self.view.group_by_field.relation)).unlink([self.group.value[0]]).then(function(r) { + self.group.destroy(); + self.view.do_reload(); + }); + } + }, do_save_sequences: function() { var self = this; if (_.indexOf(this.view.fields_keys, 'sequence') > -1) { From 790b508312656b35fa45c879b6e81b58ff436fb4 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 14:02:15 +0200 Subject: [PATCH 085/108] [FIX] Fixed group title for fields not present in fields view get bzr revid: fme@openerp.com-20120816120215-owxw7756escw3zih --- addons/web_kanban/static/src/js/kanban.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 190f28e01a7..e47948a492a 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -423,7 +423,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ this.value = this.value[0]; } var field = this.view.group_by_field; - if (field) { + if (!_.isEmpty(field)) { try { this.title = instance.web.format_value(group.value, field, false); } catch(e) {} From d52045ef11c9d3a7a02337867b3d2c522ec4fac9 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 14:55:40 +0200 Subject: [PATCH 086/108] [IMP] Close m2o autocomplete when user clicks outside bzr revid: fme@openerp.com-20120816125540-avezmt4797lcaxfd --- addons/web/static/src/js/view_form.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index eda2a0861d2..f54d095e535 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2723,6 +2723,11 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc this.floating = false; this.render_value(); }); + instance.web.bus.on('click', this, function() { + if (!this.get("effective_readonly") && this.$input && this.$input.autocomplete('widget').is(':visible')) { + this.$input.autocomplete("close"); + } + }); }, initialize_content: function() { if (!this.get("effective_readonly")) From 7dc9b6087cc4dc4249aec5c747696f0ce10e3d92 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 16 Aug 2012 15:04:06 +0200 Subject: [PATCH 087/108] [IMP] handle menuitems with "server" and "client" type + ensure menu has a name bzr revid: chs@openerp.com-20120816130406-bsselo97f8tyzcit --- openerp/tools/convert.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openerp/tools/convert.py b/openerp/tools/convert.py index 3130ee4446b..36f560ba62c 100644 --- a/openerp/tools/convert.py +++ b/openerp/tools/convert.py @@ -596,7 +596,9 @@ form: module.record_id""" % (xml_id,) "act_window": 'STOCK_NEW', "report.xml": 'STOCK_PASTE', "wizard": 'STOCK_EXECUTE', - "url": 'STOCK_JUMP_TO' + "url": 'STOCK_JUMP_TO', + "client": 'STOCK_EXECUTE', + "server": 'STOCK_EXECUTE', } values['icon'] = icons.get(a_type,'STOCK_NEW') if a_type=='act_window': @@ -625,18 +627,19 @@ form: module.record_id""" % (xml_id,) values['icon'] = 'STOCK_EXECUTE' if not values.get('name', False): values['name'] = action_name - elif a_type=='wizard': + + elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'): a_id = self.id_get(cr, a_action) - cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),)) + a_table = 'ir_act_%s' % a_type + cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),)) resw = cr.fetchone() - if (not values.get('name', False)) and resw: - values['name'] = resw[0] - elif a_type=='url': - a_id = self.id_get(cr, a_action) - cr.execute('select name from ir_act_url where id=%s', (int(a_id),)) - resw = cr.fetchone() - if (not values.get('name')) and resw: + if resw: values['name'] = resw[0] + + if not values.get('name'): + # ensure menu has a name + values['name'] = rec_id or '?' + if rec.get('sequence'): values['sequence'] = int(rec.get('sequence')) if rec.get('icon'): @@ -658,9 +661,7 @@ form: module.record_id""" % (xml_id,) groups_value.append((4, group_id)) values['groups_id'] = groups_value - xml_id = rec.get('id','').encode('utf8') - self._test_xml_id(xml_id) - pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False) + pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, rec_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False) if rec_id and pid: self.idref[rec_id] = int(pid) From 2794297e1a575028b65d1d294766e83dd3444c62 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 16 Aug 2012 15:34:59 +0200 Subject: [PATCH 088/108] [FIX] "reload" client action bzr revid: chs@openerp.com-20120816133459-5m3md4xd3dj92564 --- addons/web/static/src/js/chrome.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index a36b89400e8..1b6f84172da 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -621,11 +621,11 @@ instance.web.Reload = instance.web.Widget.extend({ }, start: function() { var l = window.location; - var timestamp = new Date().getTime(); - var search = "?ts=" + timestamp; - if (l.search) { - search = l.search + "&ts=" + timestamp; - } + + var sobj = $.deparam(l.search.substr(1)); + sobj.ts = new Date().getTime(); + var search = '?' + $.param(sobj); + var hash = l.hash; if (this.menu_id) { hash = "#menu_id=" + this.menu_id; From 49fb611b6366afa531b952e115376fdea63838f6 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 15:43:07 +0200 Subject: [PATCH 089/108] [FIX] avoid group droping in dummy_cell bzr revid: fme@openerp.com-20120816134307-2160irpecvcmldfk --- addons/web_kanban/static/src/js/kanban.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index e47948a492a..a5ab3847124 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -288,6 +288,7 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ var start_index; if (this.grouped_by_m2o) { this.$('.oe_kanban_groups_headers').sortable({ + items: '.oe_kanban_group_header', helper: 'clone', axis: 'x', opacity: 0.5, From 59583e432425e5cd2e7922b68817504bf7970c7c Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 15:50:23 +0200 Subject: [PATCH 090/108] [FIX] Do not show group's edit/delete menus when group has no value (eg: Undefined) bzr revid: fme@openerp.com-20120816135023-m5xnoo0fc3cxzadg --- addons/web_kanban/static/src/xml/web_kanban.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index 125a4eff3c9..39bca51c1f5 100644 --- a/addons/web_kanban/static/src/xml/web_kanban.xml +++ b/addons/web_kanban/static/src/xml/web_kanban.xml @@ -36,8 +36,10 @@ í
    From c916e5ee0e12c57061b4e82ad3aa38887b1d3135 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 16 Aug 2012 16:20:02 +0200 Subject: [PATCH 091/108] [FIX] remove tispy in m2o in some cases bzr revid: nicolas.vanhoren@openerp.com-20120816142002-fg21q3a5ycxmgesn --- addons/web/static/src/js/view_form.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index f54d095e535..208ee3c0edb 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2979,10 +2979,12 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc }, _quick_create: function() { this.no_tipsy = true; + this.tip_def.reject(); return instance.web.form.CompletionFieldMixin._quick_create.apply(this, arguments); }, _search_create_popup: function() { this.no_tipsy = true; + this.tip_def.reject(); return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); }, }); From 89a27bf5b6ff958dc6d0bc816b700e5b2d3f5bdd Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 16 Aug 2012 16:44:03 +0200 Subject: [PATCH 092/108] [IMP] hr_recruitment, project: better access rules for categories bzr revid: abo@openerp.com-20120816144403-4nd2bbiq5dfnwc2i --- addons/hr_recruitment/hr_recruitment.py | 23 +++++++++++++------ addons/hr_recruitment/hr_recruitment_view.xml | 3 +++ .../security/ir.model.access.csv | 2 ++ addons/project/security/ir.model.access.csv | 2 ++ 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 0ace2574d65..81a7ae6129e 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -198,6 +198,7 @@ class hr_applicant(base_stage, osv.Model): When the case is over, the state is set to \'Done\'.\ If the case needs to be reviewed then the state is \ set to \'Pending\'.'), + 'categ_ids': fields.many2many('hr.applicant_category', string='Categories'), 'company_id': fields.many2one('res.company', 'Company'), 'user_id': fields.many2one('res.users', 'Responsible'), # Applicant Columns @@ -350,7 +351,7 @@ class hr_applicant(base_stage, osv.Model): if isinstance(ids, (str, int, long)): ids = [ids] if update_vals is None: vals = {} - + update_vals.update({ 'description': msg.get('body'), 'email_from': msg.get('from'), @@ -510,7 +511,7 @@ class hr_job(osv.osv): _inherits = {'mail.alias': 'alias_id'} _columns = { 'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"), - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, help="Email alias for this job position. New emails will automatically " "create new applicants for this job position."), } @@ -521,13 +522,13 @@ class hr_job(osv.osv): def _auto_init(self, cr, context=None): """Installation hook to create aliases for all jobs and avoid constraint errors.""" - - # disable the unique alias_id not null constraint, to avoid spurious warning during + + # disable the unique alias_id not null constraint, to avoid spurious warning during # super.auto_init. We'll reinstall it afterwards. self._columns['alias_id'].required = False super(hr_job,self)._auto_init(cr, context=context) - + registry = RegistryManager.get(cr.dbname) mail_alias = registry.get('mail.alias') hr_jobs = registry.get('hr.job') @@ -555,7 +556,7 @@ class hr_job(osv.osv): mail_alias = self.pool.get('mail.alias') if not vals.get('alias_id'): vals.pop('alias_name', None) # prevent errors during copy() - alias_id = mail_alias.create_unique_alias(cr, uid, + alias_id = mail_alias.create_unique_alias(cr, uid, # Using '+' allows using subaddressing for those who don't # have a catchall domain setup. {'alias_name': 'jobs+'+vals['name']}, @@ -573,7 +574,7 @@ class hr_job(osv.osv): res = super(hr_job, self).unlink(cr, uid, ids, context=context) mail_alias.unlink(cr, uid, alias_ids, context=context) return res - + def action_print_survey(self, cr, uid, ids, context=None): if context is None: context = {} @@ -591,4 +592,12 @@ class hr_job(osv.osv): 'nodestroy':True, } +class applicant_category(osv.osv): + """ Category of applicant """ + _name = "hr.applicant_category" + _description = "Category of applicant" + _columns = { + 'name': fields.char('Name', size=64, required=True, translate=True), + } + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 0e74473f853..a74707e9ca0 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -176,6 +176,9 @@
    + + + diff --git a/addons/hr_recruitment/security/ir.model.access.csv b/addons/hr_recruitment/security/ir.model.access.csv index 8b029c9324a..086e63e542b 100644 --- a/addons/hr_recruitment/security/ir.model.access.csv +++ b/addons/hr_recruitment/security/ir.model.access.csv @@ -8,3 +8,5 @@ access_survey_hr_user,survey.hr.user,survey.model_survey,base.group_hr_user,1,1, access_crm_meeting_hruser,crm.meeting.hruser,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1 access_hr_recruitment_source_hr_officer,hr.recruitment.source,model_hr_recruitment_source,base.group_hr_user,1,1,1,1 access_hr_recruitment_source_all,hr.recruitment.source,model_hr_recruitment_source,,1,0,0,0 +access_hr_applicant_category,hr.applicant_category,model_hr_applicant_category,,1,0,0,0 +access_hr_applicant_category_manager,hr.applicant_category,model_hr_applicant_category,base.group_hr_manager,1,1,1,1 diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv index 3f79cfee3de..d9f6cb9d2f6 100644 --- a/addons/project/security/ir.model.access.csv +++ b/addons/project/security/ir.model.access.csv @@ -18,3 +18,5 @@ access_account_analytic_line_project,account.analytic.line project,analytic.mode access_project_task_history,project.task.history project,project.model_project_task_history,project.group_project_user,1,1,1,0 access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0 access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0 +access_project_category,project.project_category,model_project_category,,1,0,0,0 +access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1 From 18e8d506e7e48b70e5cc20cc144f54f955d0ec39 Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 16 Aug 2012 17:11:48 +0200 Subject: [PATCH 093/108] [IMP] hr_recruitment: add the tags to the kanban view bzr revid: abo@openerp.com-20120816151148-ymqe4mreppwk72lh --- addons/hr_recruitment/__openerp__.py | 3 +- addons/hr_recruitment/hr_recruitment_view.xml | 8 +++++ .../static/src/js/hr_recruitment.js | 35 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 addons/hr_recruitment/static/src/js/hr_recruitment.js diff --git a/addons/hr_recruitment/__openerp__.py b/addons/hr_recruitment/__openerp__.py index 0de2eb5c94e..f9ae722c438 100644 --- a/addons/hr_recruitment/__openerp__.py +++ b/addons/hr_recruitment/__openerp__.py @@ -57,13 +57,14 @@ system to store and search in your CV base. 'board_hr_recruitment_statistical_view.xml', 'hr_recruitment_installer_view.xml', 'res_config_view.xml', - ], + ], 'init_xml': [ 'hr_recruitment_data.xml' ], 'demo_xml': [ 'hr_recruitment_demo.yml', ], + 'js': ['static/src/js/hr_recruitment.js'], 'test':[ 'test/recruitment_process.yml', ], diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index a74707e9ca0..d896d522d97 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -267,6 +267,7 @@ +
      @@ -311,6 +312,13 @@ +
      diff --git a/addons/hr_recruitment/static/src/js/hr_recruitment.js b/addons/hr_recruitment/static/src/js/hr_recruitment.js new file mode 100644 index 00000000000..5a2d6bd92b4 --- /dev/null +++ b/addons/hr_recruitment/static/src/js/hr_recruitment.js @@ -0,0 +1,35 @@ +openerp.hr_recruitment = function(openerp) { + openerp.web_kanban.KanbanView.include({ + applicant_display_categ_names: function() { + /* + * Set proper names to applicant categories. + * In kanban views, many2many fields only return a list of ids. + * Therefore, we have to fetch the matching data by ourselves. + */ + var self = this; + var categ_ids = []; + + // Collect categories ids + self.$element.find('span[data-categ_id]').each(function() { + categ_ids.push($(this).data('categ_id')); + }); + + // Find their matching names + var dataset = new openerp.web.DataSetSearch(self, 'hr.applicant_category', self.session.context, [['id', 'in', _.uniq(categ_ids)]]); + dataset.read_slice(['id', 'name']).then(function(result) { + _.each(result, function(v, k) { + // Set the proper value in the DOM and display the element + self.$element.find('span[data-categ_id=' + v.id + ']').text(v.name); + }); + }); + }, + on_groups_started: function() { + var self = this; + self._super.apply(self, arguments); + + if (self.dataset.model === 'hr.applicant') { + self.applicant_display_categ_names(); + } + } + }); +}; From b859f629bfa28ca14d9ebedd06dcc274a79efe07 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 17:21:43 +0200 Subject: [PATCH 094/108] [FIX] Fixed add column bzr revid: fme@openerp.com-20120816152143-55onjiawyw6b4kb1 --- addons/web_kanban/static/src/js/kanban.js | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index a5ab3847124..8e6037a35de 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -175,9 +175,22 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ var am = instance.webclient.action_manager; var form = am.dialog_widget.views.form.controller; form.on_button_cancel.add_last(am.dialog.on_close); - form.on_created.add_last(function() { - am.dialog.on_close(); - self.do_reload(); + form.on_created.add_last(function(r) { + (new instance.web.DataSet(self, self.group_by_field.relation)).name_get([r.result]).then(function(new_record) { + am.dialog.on_close(); + var domain = self.dataset.domain.slice(0); + domain.push([self.group_by, '=', new_record[0][0]]); + var dataset = new instance.web.DataSetSearch(self, self.dataset.model, self.dataset.get_context(), domain); + var datagroup = { + value: new_record[0], + length: 0, + aggregates: {}, + }; + var new_group = new instance.web_kanban.KanbanGroup(self, [], datagroup, dataset); + self.do_add_groups([new_group]).then(function() { + $(window).scrollTo(self.groups.slice(-1)[0].$element, { axis: 'x' }); + }); + }); }); }, do_search: function(domain, context, group_by) { @@ -254,7 +267,9 @@ instance.web_kanban.KanbanView = instance.web.View.extend({ self.groups[group.undefined_title ? 'unshift' : 'push'](group); }); var groups_started = _.map(this.groups, function(group) { - return group.insertBefore(self.$element.find('.oe_kanban_groups_headers td:last')); + if (!group.is_started) { + return group.insertBefore(self.$element.find('.oe_kanban_groups_headers td:last')); + } }); return $.when.apply(null, groups_started).then(function () { self.on_groups_started(); @@ -501,6 +516,7 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ } } }); + this.is_started = true; return def; }, compute_cards_auto_height: function() { @@ -588,7 +604,6 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ var self = this; if (confirm(_t("Are you sure to remove this column ?"))) { (new instance.web.DataSet(self, self.view.group_by_field.relation)).unlink([self.group.value[0]]).then(function(r) { - self.group.destroy(); self.view.do_reload(); }); } @@ -813,7 +828,7 @@ instance.web_kanban.KanbanRecord = instance.web.Widget.extend({ this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).then(function(records) { if (records.length) { self.set_record(records[0]); - self.replaceElement($(self.render())); + self.renderElement(); self.$element.data('widget', self); self.bind_events(); self.group.compute_cards_auto_height(); From 5d0f5d7c84757081696c7216881895f7f7a502dd Mon Sep 17 00:00:00 2001 From: Antonin Bourguignon Date: Thu, 16 Aug 2012 17:31:54 +0200 Subject: [PATCH 095/108] [IMP] portal, hr_recruitment, project: improve descriptor files bzr revid: abo@openerp.com-20120816153154-2k1wazvhnqfrcd7i --- addons/hr_recruitment/__openerp__.py | 19 ++++----- addons/portal/__openerp__.py | 12 ++++-- addons/project/__openerp__.py | 62 +++++++++++++++++----------- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/addons/hr_recruitment/__openerp__.py b/addons/hr_recruitment/__openerp__.py index f9ae722c438..1a6abbef976 100644 --- a/addons/hr_recruitment/__openerp__.py +++ b/addons/hr_recruitment/__openerp__.py @@ -19,12 +19,13 @@ # along with this program. If not, see . # ############################################################################## + { 'name': 'Recruitment Process', 'version': '1.0', 'category': 'Human Resources', - "sequence": 24, - "summary": "Recruitment Process, Job Descriptions", + 'sequence': 24, + 'summary': 'Recruitment Process, Job Descriptions', 'description': """ Manages job positions and the recruitment process. ================================================== @@ -46,7 +47,7 @@ system to store and search in your CV base. 'base_calendar', 'fetchmail', ], - 'update_xml': [ + 'data': [ 'wizard/hr_recruitment_employee_hired.xml', 'wizard/hr_recruitment_create_partner_job_view.xml', 'hr_recruitment_view.xml', @@ -57,20 +58,18 @@ system to store and search in your CV base. 'board_hr_recruitment_statistical_view.xml', 'hr_recruitment_installer_view.xml', 'res_config_view.xml', - ], - 'init_xml': [ 'hr_recruitment_data.xml' ], - 'demo_xml': [ + 'demo': [ 'hr_recruitment_demo.yml', ], 'js': ['static/src/js/hr_recruitment.js'], - 'test':[ - 'test/recruitment_process.yml', - ], + 'test': [ + 'test/recruitment_process.yml', + ], 'installable': True, 'auto_install': False, - 'certificate' : '001073437025460275621', + 'certificate': '001073437025460275621', 'application': True, } diff --git a/addons/portal/__openerp__.py b/addons/portal/__openerp__.py index 6669efbcf77..283e007a598 100644 --- a/addons/portal/__openerp__.py +++ b/addons/portal/__openerp__.py @@ -20,10 +20,14 @@ ############################################################################## { - 'name' : "Portal", - 'version' : "1.0", - 'depends' : ["base", "share", "auth_anonymous"], - 'author' : "OpenERP SA", + 'name' : 'Portal', + 'version' : '1.0', + 'depends' : [ + 'base', + 'share', + 'auth_anonymous' + ], + 'author' : 'OpenERP SA', 'category': 'Portal', 'description': """ Customize access to your OpenERP database to external users by creating portals. diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py index aa523b438c7..a65497c311e 100644 --- a/addons/project/__openerp__.py +++ b/addons/project/__openerp__.py @@ -19,18 +19,33 @@ # ############################################################################## - { - "name": "Project Management", - "version": "1.1", - "author": "OpenERP SA", - "website": "http://www.openerp.com", - "category": "Project Management", - "sequence": 8, - "summary": "Projects, Tasks", - "images": ["images/gantt.png", "images/project_dashboard.jpeg","images/project_task_tree.jpeg","images/project_task.jpeg","images/project.jpeg","images/task_analysis.jpeg"], - "depends": ["base_setup", "base_status", "product", "analytic", "board", "mail", "resource","web_kanban"], - "description": """ + 'name': 'Project Management', + 'version': '1.1', + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'category': 'Project Management', + 'sequence': 8, + 'summary': 'Projects, Tasks', + 'images': [ + 'images/gantt.png', + 'images/project_dashboard.jpeg', + 'images/project_task_tree.jpeg', + 'images/project_task.jpeg', + 'images/project.jpeg', + 'images/task_analysis.jpeg' + ], + 'depends': [ + 'base_setup', + 'base_status', + 'product', + 'analytic', + 'board', + 'mail', + 'resource', + 'web_kanban' + ], + 'description': """ Project Management module tracks multi-level projects, tasks, work done on tasks. ================================================================================= @@ -41,22 +56,21 @@ Dashboard for project management that includes: * List of My Open Tasks * Graph of My Remaining Hours by Project """, - "init_xml": [], - "update_xml": [ - "security/project_security.xml", - "wizard/project_task_delegate_view.xml", - "wizard/project_task_reevaluate_view.xml", - "security/ir.model.access.csv", - "project_data.xml", - "project_view.xml", - "process/task_process.xml", - "res_partner_view.xml", - "report/project_report_view.xml", + 'data': [ + 'security/project_security.xml', + 'wizard/project_task_delegate_view.xml', + 'wizard/project_task_reevaluate_view.xml', + 'security/ir.model.access.csv', + 'project_data.xml', + 'project_view.xml', + 'process/task_process.xml', + 'res_partner_view.xml', + 'report/project_report_view.xml', 'report/project_cumulative.xml', - "board_project_view.xml", + 'board_project_view.xml', 'res_config_view.xml', ], - 'demo_xml': [ + 'demo': [ 'project_demo.xml', ], 'test':[ From 942cdee92bc0dcef2970f61b50cca0b01ba65f7d Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 16 Aug 2012 17:45:02 +0200 Subject: [PATCH 096/108] [FIX] res.users: context_get: read fields as root bzr revid: chs@openerp.com-20120816154502-v8gp7ilr4762okds --- openerp/addons/base/res/res_users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index a4fa43a32ad..cddc02673b8 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010-2011 OpenERP s.a. (). +# Copyright (C) 2010-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 @@ -332,7 +332,7 @@ class res_users(osv.osv): return super(res_users, self).copy(cr, uid, id, copydef, context) def context_get(self, cr, uid, context=None): - user = self.browse(cr, uid, uid, context) + user = self.browse(cr, 1, uid, context) result = {} for k in self._all_columns.keys(): if k.startswith('context_'): From 8921c6e439b62465b8899e3ef6bb9046e5a20fff Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 16 Aug 2012 18:07:38 +0200 Subject: [PATCH 097/108] [FIX] /web/session/modules route return all installed modules. Web client is smart enough to not load a module twice bzr revid: chs@openerp.com-20120816160738-symx1j22h9svigmo --- addons/web/controllers/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 8722fcd2887..b46fc900373 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -811,9 +811,8 @@ class Session(openerpweb.Controller): @openerpweb.jsonrequest def modules(self, req): - loaded = module_boot(req) - modules = module_installed(req) - return [module for module in modules if module not in loaded] + # return all installed modules. Web client is smart enough to not load a module twice + return module_installed(req) @openerpweb.jsonrequest def eval_domain_and_context(self, req, contexts, domains, From a9ad90fa6795e329eeb3d752653b8861525518cf Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 16 Aug 2012 18:11:19 +0200 Subject: [PATCH 098/108] [IMP] Added critical improvement to enable users to understand when the application is actually loading, by adding a "Loading..." label under the loading spinner. This was added after we realized that most people (I mean, al's father) don't understand that. bzr revid: nicolas.vanhoren@openerp.com-20120816161119-qap032jpv2dzjlf1 --- addons/web/static/src/js/coresetup.js | 59 ++++++++++++++++----------- addons/web/static/src/xml/base.xml | 9 +++- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index d5b87d8a81d..229a82a1784 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -523,37 +523,50 @@ $.async_when = function() { /** Setup blockui */ if ($.blockUI) { $.blockUI.defaults.baseZ = 1100; - $.blockUI.defaults.message = '
      '; + $.blockUI.defaults.message = '
      '; $.blockUI.defaults.css.border = '0'; $.blockUI.defaults.css["background-color"] = ''; - $.blockUI.spinners = []; } + +instance.web.Throbber = instance.web.Widget.extend({ + template: "Throbber", + start: function() { + var opts = { + lines: 13, // The number of lines to draw + length: 7, // The length of each line + width: 4, // The line thickness + radius: 10, // The radius of the inner circle + rotate: 0, // The rotation offset + color: '#FFF', // #rgb or #rrggbb + speed: 1, // Rounds per second + trail: 60, // Afterglow percentage + shadow: false, // Whether to render a shadow + hwaccel: false, // Whether to use hardware acceleration + className: 'spinner', // The CSS class to assign to the spinner + zIndex: 2e9, // The z-index (defaults to 2000000000) + top: 'auto', // Top position relative to parent in px + left: 'auto' // Left position relative to parent in px + }; + this.spin = new Spinner(opts).spin(this.$element[0]); + }, + destroy: function() { + if (this.spin) + this.spin.stop(); + this._super(); + }, +}); +instance.web.Throbber.throbbers = []; + instance.web.blockUI = function() { var tmp = $.blockUI.apply($, arguments); - var target = $(".oe_blockui_spin")[0]; - var opts = { - lines: 13, // The number of lines to draw - length: 7, // The length of each line - width: 4, // The line thickness - radius: 10, // The radius of the inner circle - rotate: 0, // The rotation offset - color: '#FFF', // #rgb or #rrggbb - speed: 1, // Rounds per second - trail: 60, // Afterglow percentage - shadow: false, // Whether to render a shadow - hwaccel: false, // Whether to use hardware acceleration - className: 'spinner', // The CSS class to assign to the spinner - zIndex: 2e9, // The z-index (defaults to 2000000000) - top: 'auto', // Top position relative to parent in px - left: 'auto' // Left position relative to parent in px - }; - var spinner = new Spinner(opts).spin(target); - $.blockUI.spinners.push(spinner); + var throbber = new instance.web.Throbber(); + instance.web.Throbber.throbbers.push(throbber); + throbber.appendTo($(".oe_blockui_spin_container")); return tmp; } instance.web.unblockUI = function() { - _.each($.blockUI.spinners, function(el) { - el.stop(); + _.each(instance.web.Throbber.throbbers, function(el) { + el.destroy(); }); return $.unblockUI.apply($, arguments); } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 9b22a7c7ff7..ee5183d8e5c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1736,5 +1736,12 @@
      - + +
      +
      +
      +
      +
      Loading...
      +
      +
      From b0274a2c7e1205e6af8efd36441b3a1de76f3cb6 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 18:31:05 +0200 Subject: [PATCH 099/108] [ADD] Add default_group_by field in view definition in order to activate 'Add column' feature bzr revid: fme@openerp.com-20120816163105-iqa0necqwlkcd6kk --- addons/crm/crm_lead_view.xml | 1 + addons/hr_recruitment/hr_recruitment_view.xml | 1 + addons/project_issue/project_issue_view.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index e56215f3332..f2c923844ec 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -288,6 +288,7 @@ + diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 0e74473f853..6261b17c45f 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -254,6 +254,7 @@ hr.applicant + diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index bb15f8c2f94..b37cf40014e 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -239,6 +239,7 @@ project.issue + From 7d316b0829b0efc639287544cf4d50e33a501f9e Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 18:34:30 +0200 Subject: [PATCH 100/108] [IMP] Kanban group title wraps down bzr revid: fme@openerp.com-20120816163430-79rt857fc3ssx4mv --- addons/web_kanban/static/src/css/kanban.css | 1 - addons/web_kanban/static/src/css/kanban.sass | 1 - 2 files changed, 2 deletions(-) diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index 194d8fad146..d0e754b6224 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -69,7 +69,6 @@ text-shadow: 0 1px 0 white; } .openerp .oe_kanban_view .oe_kanban_group_title > span { - float: left; margin-right: 4px; } .openerp .oe_kanban_view .oe_kanban_column, .openerp .oe_kanban_view .oe_kanban_group_header { diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 69a4f1d2c97..af71d857d53 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -86,7 +86,6 @@ color: #333333 text-shadow: 0 1px 0 white > span - float: left margin-right: 4px .oe_kanban_column, .oe_kanban_group_header From 896b6991e69a091d69286dd63499f68db9defe6c Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 19:13:06 +0200 Subject: [PATCH 101/108] [IMP] Changed Kanban auto height algo bzr revid: fme@openerp.com-20120816171306-czxohcujj2byatld --- addons/web_kanban/static/src/js/kanban.js | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 8e6037a35de..483ffcfe0a0 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -520,19 +520,19 @@ instance.web_kanban.KanbanGroup = instance.web.Widget.extend({ return def; }, compute_cards_auto_height: function() { - // oe_kanban_auto_height is an empty class used by the kanban view in order - // to normalize height amongst kanban cards. (by group) - var self = this; - var min_height = 0; - var els = []; - _.each(this.records, function(r) { - var $e = r.$element.find('.oe_kanban_auto_height').first().css('min-height', 0); - if ($e.length) { - els.push($e[0]); - min_height = Math.max(min_height, $e.outerHeight()); - } - }); - $(els).css('min-height', min_height); + // oe_kanban_no_auto_height is an empty class used to disable this feature + if (!this.view.group_by) { + var min_height = 0; + var els = []; + _.each(this.records, function(r) { + var $e = r.$element.children(':first:not(.oe_kanban_no_auto_height)').css('min-height', 0); + if ($e.length) { + els.push($e[0]); + min_height = Math.max(min_height, $e.outerHeight()); + } + }); + $(els).css('min-height', min_height); + } }, destroy: function() { this._super(); From e31305114ba67be634864d4210ec500426f274de Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 19:13:39 +0200 Subject: [PATCH 102/108] [REM] Kanban autoheight is now the default behavior in non grouped mode bzr revid: fme@openerp.com-20120816171339-3sjyyoganuxbyg3s --- addons/project/project_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index eb17d446816..6dbcaebd045 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -229,7 +229,7 @@ -
      +
      í
        From df95ece675be59037c0d4fd046774fde16685988 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 19:15:21 +0200 Subject: [PATCH 103/108] [REM] Kanban autoheight is now the default behavior in non grouped mode bzr revid: fme@openerp.com-20120816171521-tp4s2eeyoy0lcyuj --- openerp/addons/base/module/module_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml index fb60bcd25fd..19f9dbdaa44 100644 --- a/openerp/addons/base/module/module_view.xml +++ b/openerp/addons/base/module/module_view.xml @@ -65,7 +65,7 @@ -
        +
        From 42dd77b782593dd997c8f08053e7a0eb2d9e17c0 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 19:39:50 +0200 Subject: [PATCH 104/108] [FIX] Debug: view_fields bzr revid: fme@openerp.com-20120816173950-9o6e0m2q7alfwh5d --- addons/web/static/src/js/data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 2372c6bbff9..10fd727e57a 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -706,7 +706,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend( /** @lends openerp. * @returns {$.Deferred} */ call_and_eval: function (method, args, domain_index, context_index, callback, error_callback) { - return this.rpc('/web/dataset/call', { + return instance.session.rpc('/web/dataset/call', { model: this.model, method: method, domain_id: domain_index == undefined ? null : domain_index, From 20392f65dc4b76d59b7e50b7c513f337c1ac5be0 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Thu, 16 Aug 2012 19:50:41 +0200 Subject: [PATCH 105/108] [FIX] preferences buttons bzr revid: al@openerp.com-20120816175041-3on3n3kxx3is9srh --- openerp/addons/base/res/res_users_view.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml index 283f6a2a02e..9c32d32287c 100644 --- a/openerp/addons/base/res/res_users_view.xml +++ b/openerp/addons/base/res/res_users_view.xml @@ -215,9 +215,10 @@
        -
        From b5d5b2f52c9a13b5c4c00f4c698357d32a81f420 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 16 Aug 2012 19:52:19 +0200 Subject: [PATCH 106/108] [FIX] Fixed view editor bzr revid: fme@openerp.com-20120816175219-v0fzkgpb0zog290j --- addons/web_view_editor/static/src/js/view_editor.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/addons/web_view_editor/static/src/js/view_editor.js b/addons/web_view_editor/static/src/js/view_editor.js index 5e04134d1ce..e52a51d125e 100644 --- a/addons/web_view_editor/static/src/js/view_editor.js +++ b/addons/web_view_editor/static/src/js/view_editor.js @@ -70,11 +70,10 @@ instance.web_view_editor.ViewEditor = instance.web.Widget.extend({ this.view_edit_dialog.on_close.add_last(function(){window.location.reload();}); this.main_view_id = this.parent.fields_view.view_id; this.action_manager = new instance.web.ActionManager(this); + this.action_manager.appendTo(this.view_edit_dialog.$element); $.when(this.action_manager.do_action(action)).then(function() { - var viewmanager = self.action_manager.inner_widget, - controller = viewmanager.views[viewmanager.active_view].controller; - self.action_manager.appendTo(self.view_edit_dialog.$element); - self.action_manager.renderElement(self.view_edit_dialog); + var viewmanager = self.action_manager.inner_widget; + var controller = viewmanager.views[viewmanager.active_view].controller; controller.on_loaded.add_last(function(){ $(controller.groups).bind({ 'selected': function(e, ids, records) { From 3378eed4ca74acbdbec796743311236e85aac9fc Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Fri, 17 Aug 2012 01:55:45 +0200 Subject: [PATCH 107/108] [FIX] action manager dont change url for dialog bzr revid: al@openerp.com-20120816235545-5af63loazx25fsbg --- addons/web/static/src/js/views.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 7f2a0b10bbe..0831d0c5f8e 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -159,7 +159,9 @@ instance.web.ActionManager = instance.web.Widget.extend({ //state = _.extend(this.inner_action.params || {}, state); } } - this.getParent().do_push_state(state); + if(!this.dialog) { + this.getParent().do_push_state(state); + } } }, do_load_state: function(state, warm) { From 281877d27470826b8b4925aaa1e79d14c95f963f Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Fri, 17 Aug 2012 02:09:35 +0200 Subject: [PATCH 108/108] [FIX] nosheet css fix, revert 3 lines from 2903, does anyone care ? bzr revid: al@openerp.com-20120817000935-ev1iqsmrolq3sjiq --- addons/web/static/src/css/base.css | 6 +++--- addons/web/static/src/css/base.sass | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 330885de21f..a39838070cb 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1926,9 +1926,9 @@ margin: 8px; } .openerp .oe_form_nosheet > header { - margin-top: -20px; - margin-left: -20px; - margin-right: -20px; + margin-top: -8px; + margin-left: -8px; + margin-right: -8px; } .openerp .oe_form header { position: relative; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index a1726e7e1a9..f1d1f2e9f1f 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1487,9 +1487,9 @@ $sheet-max-width: 860px .oe_form_nosheet margin: 8px .oe_form_nosheet > header - margin-top: -20px - margin-left: -20px - margin-right: -20px + margin-top: -8px + margin-left: -8px + margin-right: -8px // }}} // FormView.custom tags and classes {{{ .oe_form