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.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/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/auth_oauth/__init__.py b/addons/auth_oauth/__init__.py new file mode 100644 index 00000000000..c9acce48a16 --- /dev/null +++ b/addons/auth_oauth/__init__.py @@ -0,0 +1,3 @@ +import controllers +import auth_oauth +import res_users diff --git a/addons/auth_oauth/__openerp__.py b/addons/auth_oauth/__openerp__.py new file mode 100644 index 00000000000..6c79bba2151 --- /dev/null +++ b/addons/auth_oauth/__openerp__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# 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 +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'OAuth2 Authentication', + 'version': '1.0', + 'category': 'Tools', + 'description': """Allow users to login through Google OAuth2.""", + 'author': 'Victor Tabuenca', + 'maintainer': 'OpenERP s.a.', + 'website': 'http://www.openerp.com', + 'depends': ['base', 'web'], + 'data': [ + 'auth_oauth_data.xml' + ], + 'update_xml': [ + 'auth_oauth_view.xml' + ], + 'js': [ + 'static/src/js/auth_oauth.js', + ], + 'css': [ + 'static/lib/zocial/css/zocial.css', + ], + 'qweb': [ + 'static/src/xml/auth_oauth.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/addons/auth_oauth/auth_oauth.py b/addons/auth_oauth/auth_oauth.py new file mode 100644 index 00000000000..e258505868d --- /dev/null +++ b/addons/auth_oauth/auth_oauth.py @@ -0,0 +1,22 @@ +from openerp.osv import osv, fields + +class auth_oauth_providers(osv.osv): + """Class defining the configuration values of an OAuth2 provider""" + + _name = 'auth.oauth.provider' + _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('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'), + '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 new file mode 100644 index 00000000000..021a03b2742 --- /dev/null +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -0,0 +1,38 @@ + + + + + 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 + https://www.googleapis.com/oauth2/v1/userinfo + zocial google + Sign in with google + True + + + Twitter OAuth2 + 108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.twitterusercontent.com + https://api.twitter.com/oauth/request_token + + https://api.twitter.com/oauth/authorize + + zocial twitter + Sign in with twitter + True + + + diff --git a/addons/auth_oauth/auth_oauth_view.xml b/addons/auth_oauth/auth_oauth_view.xml new file mode 100644 index 00000000000..678c347c84d --- /dev/null +++ b/addons/auth_oauth/auth_oauth_view.xml @@ -0,0 +1,46 @@ + + + + + auth.oauth.provider.form + auth.oauth.provider + form + +
+ + + + + + + + + + + + + +
+
+
+ + auth.oauth.provider.list + auth.oauth.provider + tree + + + + + + + + + + Providers + auth.oauth.provider + form + tree,form + + +
+
diff --git a/addons/auth_oauth/controllers/__init__.py b/addons/auth_oauth/controllers/__init__.py new file mode 100644 index 00000000000..e11f9ba81bb --- /dev/null +++ b/addons/auth_oauth/controllers/__init__.py @@ -0,0 +1,3 @@ +import main + +# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py new file mode 100644 index 00000000000..28f550b4bda --- /dev/null +++ b/addons/auth_oauth/controllers/main.py @@ -0,0 +1,47 @@ +import logging + +import simplejson +import werkzeug.urls +import werkzeug.utils + +import openerp.modules.registry +import openerp.addons.web.controllers.main +import openerp.addons.web.common.http as openerpweb + +_logger = logging.getLogger(__name__) + +class OAuthController(openerpweb.Controller): + _cp_path = '/auth_oauth' + + @openerpweb.jsonrequest + def list_providers(self, req, dbname): + 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): + 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, provider, kw) + cr.commit() + return openerp.addons.web.controllers.main.login_and_redirect(req, *credentials) + except AttributeError: + # auth_signup is not installed + _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=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 new file mode 100644 index 00000000000..3dbab849942 --- /dev/null +++ b/addons/auth_oauth/res_users.py @@ -0,0 +1,70 @@ +import logging + +import urllib +import urllib2 +import simplejson + +import openerp +from openerp.osv import osv, fields + +_logger = logging.getLogger(__name__) + +class res_users(osv.Model): + _inherit = 'res.users' + + _columns = { + '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), + } + + def auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None): + params = urllib.urlencode({'access_token':access_token}) + url = endpoint + '?' + params + f = urllib2.urlopen(url) + response = f.read() + return simplejson.loads(response) + + 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') + 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_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}) + else: + # New user + new_user = { + 'name': name, + 'login': login, + 'user_email': login, + 'oauth_provider_id': 1, + 'oauth_uid': oauth_uid, + 'oauth_access_token': access_token, + 'active': True, + } + self.auth_signup_create(cr, uid, new_user) + return credentials + + def check_credentials(self, cr, uid, password): + try: + 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 + +# 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_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 00000000000..94809d3a57f Binary files /dev/null and b/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.ttf differ 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 00000000000..1d6c4abc9d9 Binary files /dev/null and b/addons/auth_oauth/static/lib/zocial/css/zocial-regular-webfont.woff differ 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/js/auth_oauth.js b/addons/auth_oauth/static/src/js/auth_oauth.js new file mode 100644 index 00000000000..eae423fae1f --- /dev/null +++ b/addons/auth_oauth/static/src/js/auth_oauth.js @@ -0,0 +1,64 @@ +openerp.auth_oauth = function(instance) { + var QWeb = instance.web.qweb; + + instance.web.Login = instance.web.Login.extend({ + start: function(parent, params) { + var d = this._super.apply(this, arguments); + 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."); + } 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); + }, + 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: state, + }; + var url = p.auth_endpoint + '?' + $.param(params); + window.location = url; + }, + }); + + instance.web.WebClient = instance.web.WebClient.extend({ + start: function() { + this._super.apply(this, arguments); + var params = $.deparam(window.location.hash.substring(1)); + // alert(JSON.stringify(params)); + if (params.hasOwnProperty('access_token')) { + var url = "/auth_oauth/signin" + '?' + $.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 new file mode 100644 index 00000000000..c2693804e59 --- /dev/null +++ b/addons/auth_oauth/static/src/xml/auth_oauth.xml @@ -0,0 +1,9 @@ + + + + + +
+
+
+
diff --git a/addons/auth_openid/controllers/main.py b/addons/auth_openid/controllers/main.py index dc81730daf2..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,12 +169,11 @@ class OpenIDController(openerpweb.Controller): display_identifier = info.getDisplayIdentifier() session['status'] = info.status - user_id = None if info.status == consumer.SUCCESS: dbname = session['dbname'] - with utils.cursor(dbname) as cr: - registry = RegistryManager.get(dbname) + registry = RegistryManager.get(dbname) + with registry.cursor() as cr: Modules = registry.get('ir.module.module') installed = Modules.search_count(cr, 1, ['&', ('name', '=', 'auth_openid'), ('state', '=', 'installed')]) == 1 @@ -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/res_users.py b/addons/auth_openid/res_users.py index 506c7e1c094..9b02ce42991 100644 --- a/addons/auth_openid/res_users.py +++ b/addons/auth_openid/res_users.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# 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 @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## +from openerp.modules.registry import RegistryManager from openerp.osv import osv, fields import openerp.exceptions import tools @@ -64,7 +65,7 @@ class res_users(osv.osv): if result: return result else: - with utils.cursor(db) as cr: + with RegistryManager.get(db).cursor() as cr: cr.execute("""UPDATE res_users SET login_date=now() AT TIME ZONE 'UTC' WHERE login=%s AND openid_key=%s AND active=%s RETURNING id""", @@ -73,14 +74,13 @@ class res_users(osv.osv): cr.commit() return res[0] if res else False - def check(self, db, uid, passwd): try: return super(res_users, self).check(db, uid, passwd) except openerp.exceptions.AccessDenied: if not passwd: raise - with utils.cursor(db) as cr: + with RegistryManager.get(db).cursor() as cr: cr.execute('''SELECT COUNT(1) FROM res_users WHERE id=%s 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); } }); diff --git a/addons/auth_openid/static/src/xml/auth_openid.xml b/addons/auth_openid/static/src/xml/auth_openid.xml index 34894555668..aa61d161657 100644 --- a/addons/auth_openid/static/src/xml/auth_openid.xml +++ b/addons/auth_openid/static/src/xml/auth_openid.xml @@ -3,7 +3,7 @@ - + - - - - - - - - - - - - - - - - - - +
  • Google Apps Domain
  • @@ -54,7 +37,7 @@
    - + this.each(function() { var $i = $(this), dp = $i.find('input').attr('name'); diff --git a/addons/auth_openid/utils.py b/addons/auth_openid/utils.py index 444b5eeaa05..806198f99c2 100644 --- a/addons/auth_openid/utils.py +++ b/addons/auth_openid/utils.py @@ -1,8 +1,8 @@ #!/usr/bin/env python ############################################################################## -# +# # OpenERP, Open Source Management Solution -# 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 @@ -15,11 +15,9 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## -from contextlib import contextmanager -from openerp.modules.registry import RegistryManager KEY_LENGTH = 16 @@ -35,14 +33,4 @@ SREG2AX = { # from http://www.axschema.org/types/#sreg 'timezone': 'http://axschema.org/pref/timezone', } - -@contextmanager -def cursor(db): - cr = RegistryManager.get(db).db.cursor() - try: - yield cr - finally: - cr.close() - - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_signup/__init__.py b/addons/auth_signup/__init__.py index 1a120e52d18..2331ae629a8 100644 --- a/addons/auth_signup/__init__.py +++ b/addons/auth_signup/__init__.py @@ -19,5 +19,6 @@ # ############################################################################## +import controllers import res_config -import auth_signup +import res_users diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py index 227d5f1017d..765a36ef252 100644 --- a/addons/auth_signup/__openerp__.py +++ b/addons/auth_signup/__openerp__.py @@ -27,16 +27,13 @@ 'category': 'Authentication', 'website': 'http://www.openerp.com', 'installable': True, - 'depends': ['auth_anonymous', 'base_setup'], + 'depends': ['base_setup'], 'data': [ 'res_config.xml', ], 'js': [ 'static/src/js/auth_signup.js', ], - 'css': [ - 'static/src/css/auth_signup.css', - ], 'qweb': [ 'static/src/xml/auth_signup.xml', ], diff --git a/addons/auth_signup/auth_signup.py b/addons/auth_signup/auth_signup.py deleted file mode 100644 index 96d1ea132de..00000000000 --- a/addons/auth_signup/auth_signup.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# 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 -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see -# -############################################################################## - -from openerp.osv import osv, fields - -class signup_signup(osv.TransientModel): - _name = 'auth.signup' - - # TODO add captcha - _columns = { - 'name': fields.char('Name', size=64), - 'email': fields.char('Email', size=64), - 'password': fields.char('Password', size=64), - } - - def create(self, cr, uid, values, context=None): - # NOTE here, invalid values raises exceptions to avoid storing - # sensitive data into the database (which then are available to anyone) - - new_user = { - 'name': values['name'], - 'login': values['email'], - 'email': values['email'], - 'password': values['password'], - 'active': True, - } - - user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0) - if user_template_id: - self.pool.get('res.users').copy(cr, 1, user_template_id, new_user, context=context) - else: - self.pool.get('res.users').create(cr, 1, new_user, context=context) - - # Dont store anything - return 0 diff --git a/addons/auth_signup/controllers/__init__.py b/addons/auth_signup/controllers/__init__.py new file mode 100644 index 00000000000..e11f9ba81bb --- /dev/null +++ b/addons/auth_signup/controllers/__init__.py @@ -0,0 +1,3 @@ +import main + +# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py new file mode 100644 index 00000000000..0ed2151d12b --- /dev/null +++ b/addons/auth_signup/controllers/main.py @@ -0,0 +1,34 @@ +import logging + +import werkzeug.urls + +from openerp.modules.registry import RegistryManager +from openerp.addons.web.controllers.main import login_and_redirect +import openerp.addons.web.common.http as openerpweb + +_logger = logging.getLogger(__name__) + +class OpenIDController(openerpweb.Controller): + _cp_path = '/auth_signup' + + @openerpweb.httprequest + def signup(self, req, dbname, name, login, password): + url = '/' + registry = RegistryManager.get(dbname) + with registry.cursor() as cr: + try: + Users = registry.get('res.users') + credentials = Users.auth_signup(cr, 1, name, login, password) + cr.commit() + return login_and_redirect(req, *credentials) + except AttributeError: + # auth_signup is not installed + _logger.exception('attribute error when signup') + url = "/#action=auth_signup&error=NA" # Not Available + except Exception: + # signup error + _logger.exception('error when signup') + url = "/#action=auth_signup&error=UE" # Unexcpected Error + return werkzeug.utils.redirect(url) + +# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py new file mode 100644 index 00000000000..1d77a3f9473 --- /dev/null +++ b/addons/auth_signup/res_users.py @@ -0,0 +1,46 @@ +import openerp +from openerp.osv import osv + +class res_users(osv.Model): + _inherit = 'res.users' + + def auth_signup_create(self, cr, uid, new_user, context=None): + # new_user: + # login + # email + # name (optional) + # partner_id (optional) + # groups (optional) + # sign (for partner_id and groups) + # + user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0) + if user_template_id: + self.pool.get('res.users').copy(cr, 1, user_template_id, new_user, context=context) + else: + self.pool.get('res.users').create(cr, 1, new_user, context=context) + + def auth_signup(self, cr, uid, name, login, password, context=None): + r = (cr.dbname, login, password) + 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 + +# diff --git a/addons/auth_signup/static/src/css/auth_signup.css b/addons/auth_signup/static/src/css/auth_signup.css deleted file mode 100644 index 1e8d7d9d8d8..00000000000 --- a/addons/auth_signup/static/src/css/auth_signup.css +++ /dev/null @@ -1,9 +0,0 @@ -.openerp .oe_login .oe_login_pane ul.oe_signup a { - color: #eeeeee; - margin: 0 8px; -} -.openerp .oe_login .oe_login_pane ul.oe_signup a:hover { - text-decoration: underline; -} - - diff --git a/addons/auth_signup/static/src/js/auth_signup.js b/addons/auth_signup/static/src/js/auth_signup.js index 9dda111f044..2ab13aa7598 100644 --- a/addons/auth_signup/static/src/js/auth_signup.js +++ b/addons/auth_signup/static/src/js/auth_signup.js @@ -1,65 +1,38 @@ openerp.auth_signup = function(instance) { + instance.auth_signup = instance.auth_signup || {}; var _t = instance.web._t; instance.web.Login.include({ start: function() { var self = this; - this.$('a.oe_signup').click(function() { - var db = self.$("form [name=db]").val(); - if (!db) { - self.do_warn(_t("Login"), _t("No database selected!")); - return false; - } - - var cnx = instance.session; - if (cnx.session_is_valid()) { - self._signup(); - } else { - cnx.session_authenticate(db, 'anonymous', 'anonymous', true).then(function() { - self._signup(); - }).fail(function(error, event) { - console.log(error); - // cannot log as anonymous or auth_signup not installed - self.do_warn(_t('Sign Up'), _.str.sprintf(_t('Signup functionnality is not available for database %s'), db), true); - }); - } + var dbname = self.$("form [name=db]").val(); + self.do_action({ + type: 'ir.actions.client', + tag: 'auth_signup.signup', + params: {'dbname': dbname}, + target: 'new', + name: 'Sign up' + }); return true; }); return this._super(); - }, - - _signup: function() { - this.do_action({ - type: 'ir.actions.client', - tag: 'auth_signup.signup', - target: 'new', - name: 'Sign up' - }); - } }); - instance.auth_signup = instance.auth_signup || {}; instance.auth_signup.Signup = instance.web.Widget.extend({ template: 'auth_signup.signup', - init: function() { - this._super.apply(this, arguments); - this.dataset = new instance.web.DataSet(this, 'auth.signup'); + init: function(parent, params) { + this.params = params; + return this._super(); }, start: function() { var self = this; - this.$('input[type=password]').change(function() { + this.$('input[name=password_confirmation]').keyup(function() { var v = $(this).val(); - var e = !_.isEmpty(v); - if (e) { - e =_.all(self.$('input[type=password]'), function(i) { - return $(i).val() === v; - }); - } var $b = self.$('button'); - if (e) { + if (_.isEmpty(v) || self.$('input[name=password]').val() === v) { $b.removeAttr('disabled'); } else { $b.attr('disabled', 'disabled'); @@ -70,36 +43,18 @@ openerp.auth_signup = function(instance) { if(ev) { ev.preventDefault(); } - var name = self.$('input[name=name]').val(); - var email = self.$('input[name=email]').val(); - var password = self.$('input[name=password]').val(); - - self.dataset.create({ - name: name, - email: email, - password: password - }, function() { - self.do_action({ - type: 'ir.actions.client', - tag: 'login', - params: { - db: instance.session.db, - login: email, - password: password, - login_successful: function() { - self.do_action('home'); - } - } - }); - }); - return false; - + var params = { + dbname : self.params.dbname, + name: self.$('input[name=name]').val(), + login: self.$('input[name=email]').val(), + password: self.$('input[name=password]').val(), + }; + var url = "/auth_signup/signup?" + $.param(params); + window.location = url; }); - return $.when(this._super()); + return this._super(); } - }); instance.web.client_actions.add("auth_signup.signup", "instance.auth_signup.Signup"); - }; diff --git a/addons/auth_signup/static/src/xml/auth_signup.xml b/addons/auth_signup/static/src/xml/auth_signup.xml index 92343d96281..c2eec846d15 100644 --- a/addons/auth_signup/static/src/xml/auth_signup.xml +++ b/addons/auth_signup/static/src/xml/auth_signup.xml @@ -14,10 +14,10 @@
    - Name = - Email = - Password = - Confirmation = + Name =
    + Email =
    + Password =
    + Confirmation =
    diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index b57d7dcb2c7..9776d66b839 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -232,6 +232,7 @@
    +
    diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index a0ec728a4d8..146417a7abb 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_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/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 9b187ed4417..e47e0cafcad 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.py b/addons/crm/crm_phonecall.py index 7a544da5dc1..e2c2a2b02da 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/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 1639d075f3e..2f885e09b61 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -152,6 +152,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 c550d36016c..bfed55b17cf 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.py b/addons/hr_holidays/hr_holidays.py index 0134fc79fdd..e888f9b5c3b 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -364,15 +364,11 @@ class hr_holidays(osv.osv): dom = ['|'] + dom + [ ('state','=','validate1') ] return dom - 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_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_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 40cdda25529..89ce641b271 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -136,6 +136,7 @@
    +
    @@ -182,6 +183,7 @@
    +
    diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 2e5b7b6403e..0ace2574d65 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_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/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 507874a6fda..548b4f93ead 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/__init__.py b/addons/mail/__init__.py index 54e20a6a9ef..49ee9e18995 100644 --- a/addons/mail/__init__.py +++ b/addons/mail/__init__.py @@ -22,9 +22,10 @@ import mail_alias import mail_message import mail_mail +import mail_followers import mail_thread import mail_group -import mail_subscription +import ir_needaction import res_partner import res_users import report diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py index fea3d603f6a..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', @@ -102,9 +102,11 @@ The main features of the module are: 'js': [ 'static/lib/jquery.expander/jquery.expander.js', 'static/src/js/mail.js', + 'static/src/js/mail_followers.js', ], 'qweb': [ 'static/src/xml/mail.xml', + 'static/src/xml/mail_followers.xml', ], } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/mail/mail_subscription.py b/addons/mail/mail_followers.py similarity index 89% rename from addons/mail/mail_subscription.py rename to addons/mail/mail_followers.py index f5ca21f0fa1..24ab4a56b4d 100644 --- a/addons/mail/mail_subscription.py +++ b/addons/mail/mail_followers.py @@ -22,17 +22,20 @@ from osv import osv from osv import fields -class mail_subscription(osv.osv): - """ - 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) """ - _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, @@ -43,10 +46,8 @@ class mail_subscription(osv.osv): ondelete='cascade', required=True, select=1), } -class mail_notification(osv.osv): - """ - mail_notification is a relational table modeling messages pushed to users. - :param: read: not used currently +class mail_notification(osv.Model): + """ mail_notification is a relational table modeling messages pushed to users. """ _name = 'mail.notification' _rec_name = 'id' diff --git a/addons/mail/mail_subscription_view.xml b/addons/mail/mail_followers_view.xml similarity index 61% rename from addons/mail/mail_subscription_view.xml rename to addons/mail/mail_followers_view.xml index 19e31cf8fc9..62e93430675 100644 --- a/addons/mail/mail_subscription_view.xml +++ b/addons/mail/mail_followers_view.xml @@ -2,16 +2,14 @@ - - - - mail.subscription.tree - mail.subscription + + + mail.followers.tree + mail.followers + tree 10 - + @@ -19,26 +17,22 @@ - - + mail.notification.tree mail.notification 10 - + - - - Subscriptions - mail.subscription + + Followers + mail.followers form tree,form @@ -50,9 +44,9 @@ tree,form - - --> + + --> ) +# 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,22 +24,30 @@ 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) + 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) partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id - subs = self.pool.get('mail.subscription') + 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, [ - ('partner_id','=',partner_id),('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 - diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index 8f1430fbc56..1dac0ab9408 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -14,6 +14,8 @@ + +
    @@ -21,17 +23,16 @@
    -
    + @@ -80,6 +81,8 @@
    +
    diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index c8de4f20a2c..7d233345315 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -36,10 +36,61 @@ from tools.safe_eval import safe_eval as eval _logger = logging.getLogger(__name__) - def decode_header(message, header, separator=' '): return separator.join(map(decode,message.get_all(header, []))) +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: + - mail_followers.res_model = 'crm.lead' + """ + 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 \ + AND %(rel)s.res_model = %%s \ + %(where_c)s \ + %(order_by)s \ + %(limit)s \ + OFFSET %(offset)d' \ + % values + where_params = [model._name] + where_params + 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) + for act in values: + if not (isinstance(act, list) or isinstance(act, tuple)) or not act: + continue + if act[0] == 0: + 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)) + 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)) + 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: + return super(many2many_reference, self).set(cr, model, id, name, values, user, context) class mail_thread(osv.Model): '''Mixin model, meant to be inherited by any model that needs to @@ -62,9 +113,9 @@ 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 WITH SUBTYPE / NEW FOLLOW MECHANISM ''' _name = 'mail.thread' _description = 'Email Thread' @@ -111,7 +162,7 @@ class mail_thread(osv.Model): return [('id','in',[])] _columns = { - 'message_is_subscriber': fields.function(_get_is_subscriber, + 'message_is_follower': fields.function(_get_is_follower, type='boolean', string='Is a Follower'), 'message_follower_ids': fields.one2many('mail.subscription', 'res_id', domain=lambda self: [('res_model','=',self._name)], @@ -135,29 +186,92 @@ 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) self.message_subscribe(cr, uid, [thread_id], [uid], context=context) return thread_id + # FP Note: not sure we need this? + #def write(self, cr, uid, ids, vals, context=None): + # """ Override of write to subscribe : + # - the writer + # - followers given by the monitored fields + # """ + # if isinstance(ids, (int, long)): + # ids = [ids] + # for id in ids: + # # 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'): + # 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 + 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) + 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. + :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, [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 + follower_ids = [] + for field in modified_fields: + # 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) + 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) + return self.message_subscribe_get_command(cr, uid, follower_ids, context=context) + #------------------------------------------------------ # mail.message wrappers and tools #------------------------------------------------------ @@ -166,7 +280,7 @@ 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 """ context = context or {} message_obj = self.pool.get('mail.message') @@ -873,10 +987,17 @@ class mail_thread(osv.Model): return True return False + 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_subscribe(self, cr, uid, ids, partner_ids=None, context=None): """ :param user_ids: a list of user_ids; if not set, subscribe uid instead + :param return: new value of followers, for Chatter """ subscription_obj = self.pool.get('mail.subscription') create_ids = [] @@ -892,17 +1013,15 @@ 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 """ - # Trying to unsubscribe somebody not in subscribers: returns False - # if special management is needed; allows to know that an automatically - # subscribed user tries to unsubscribe and allows to warn him to_unsubscribe_uids = [uid] if user_ids is None else user_ids - subscription_obj = self.pool.get('mail.subscription') - to_delete_sub_ids = subscription_obj.search(cr, uid, - ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('user_id', 'in', to_unsubscribe_uids)], context=context) - if not to_delete_sub_ids: - return False - return subscription_obj.unlink(cr, uid, to_delete_sub_ids, 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): + """ Generate the many2many command to remove followers. """ + return [(3, id) for id in follower_ids] #------------------------------------------------------ # Notification API 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/mail/res_users.py b/addons/mail/res_users.py index af045433936..e57b046622e 100644 --- a/addons/mail/res_users.py +++ b/addons/mail/res_users.py @@ -112,7 +112,8 @@ class res_users(osv.Model): company_name = user.company_id.name if user.company_id else _('the company') subject = '''%s has joined %s.''' % (user.name, company_name) body = '''Welcome to OpenERP !''' - return self.pool.get('res.partner').message_append_note(cr, user.id, [user.partner_id.id], + # TODO change 1 into user.id but catch errors + return self.pool.get('res.partner').message_append_note(cr, 1, [user.partner_id.id], subject=subject, body=body, type='comment', content_subtype='html', context=context) def write(self, cr, uid, ids, vals, context=None): @@ -151,10 +152,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): 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 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])] diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index c0317633aa7..7cee91e30e5 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -89,10 +89,14 @@ /* RecordThread /* ------------------------------------------------------------ */ -.openerp div.oe_mail_recthread { +.openerp .oe_form div.oe_chatter { overflow: auto; } +.openerp div.oe_mail_recthread { + /*overflow: auto;*/ +} + .openerp div.oe_mail_recthread_main { float: left; width: 560px; diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index ba8df2d1671..16404b28594 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -4,6 +4,8 @@ openerp.mail = function(session) { var mail = session.mail = {}; + openerp_mail_followers(session, mail); // import mail_followers.js + /** * ------------------------------------------------------------ * FormView @@ -838,32 +840,17 @@ openerp.mail = function(session) { this._super.apply(this, arguments); this.params = this.get_definition_options(); this.params.thread_level = this.params.thread_level || 0; - this.params.see_subscribers = true; - this.params.see_subscribers_options = this.params.see_subscribers_options || false; this.thread = null; this.ds = new session.web.DataSet(this, this.view.model); this.ds_users = new session.web.DataSet(this, 'res.users'); }, 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(); - 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) { - 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'); }) - .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); - this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); }) - .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) - .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); - this.reinit(); }, _check_visibility: function() { @@ -874,72 +861,20 @@ openerp.mail = function(session) { this._super.apply(this, arguments); }, - reinit: function() { - this.params.see_subscribers = true; - this.params.see_subscribers_options = this.params.see_subscribers_options || 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() { this._super.apply(this, arguments); - var self = this; - this.reinit(); if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { this.$element.find('.oe_mail_thread').hide(); return; } - // fetch followers - var fetch_sub_done = this.fetch_subscribers(); // create and render Thread widget this.$element.find('div.oe_mail_recthread_main').empty(); if (this.thread) this.thread.destroy(); this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid, 'thread_level': this.params.thread_level, 'show_post_comment': true, 'limit': 15}); var thread_done = this.thread.appendTo(this.$element.find('div.oe_mail_recthread_main')); - return fetch_sub_done && thread_done; - }, - - fetch_subscribers: function () { - return this.ds.call('message_read_subscribers', [[this.view.datarecord.id]]).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 + ')'); - _(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); - $(session.web.qweb.render('mail.record_thread.subscriber', {'record': record})).appendTo(user_list); - }); - if (self.is_subscriber) { - self.$element.find('button.oe_mail_button_follow').hide(); - self.$element.find('button.oe_mail_button_unfollow').show(); } - else { - self.$element.find('button.oe_mail_button_follow').show(); - self.$element.find('button.oe_mail_button_unfollow').hide(); } - }, - - do_follow: function () { - return this.ds.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('fetch_subscribers')); - }, - - do_unfollow: function () { - var self = this; - return this.ds.call('message_unsubscribe', [[this.view.datarecord.id]]).then(function (record) { - if (record == false) self.do_notify("Impossible to unsubscribe", "You are automatically subscribed to this record. You cannot unsubscribe."); - }).pipe(this.proxy('fetch_subscribers')); - }, - - 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'); } - else { this.$element.find('button.oe_mail_button_followers').html('Show followers'); } - this.$element.find('div.oe_mail_recthread_followers').toggle(); + return thread_done; }, }); @@ -976,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 = []; diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js new file mode 100644 index 00000000000..0e920f73041 --- /dev/null +++ b/addons/mail/static/src/js/mail_followers.js @@ -0,0 +1,122 @@ +openerp_mail_followers = function(session, mail) { + var _t = session.web._t, + _lt = session.web._lt; + + var mail_followers = session.mail_followers = {}; + + /** + * ------------------------------------------------------------ + * mail_followers Widget + * ------------------------------------------------------------ + * + * 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 the widget to registry */ + session.web.form.widgets.add('mail_followers', 'openerp.mail_followers.Followers'); + + mail_followers.Followers = session.web.form.AbstractField.extend({ + template: 'mail.followers', + + init: function() { + this._super.apply(this, arguments); + 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 + // 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(); + this.$element.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); }); + 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'); }) + .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); + this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); }) + .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) + .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; + 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_) { + this.reinit(); + if (! this.view.datarecord.id || + session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { + this.$element.find('div.oe_mail_recthread_aside').hide(); + return; + } + 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 the followers. + * TODO: replace the is_subscriber check by fields read */ + 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(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); + $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(user_list); + }); + if (this.is_subscriber) { + this.$element.find('button.oe_mail_button_follow').hide(); + this.$element.find('button.oe_mail_button_unfollow').show(); } + else { + 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 () { + 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'); } + else { this.$element.find('button.oe_mail_button_followers').html('Show followers'); } + this.$element.find('div.oe_mail_recthread_followers').toggle(); + }, + }); +}; diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 6959a179194..7f49b7f3943 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -61,28 +61,8 @@
    -
    -
    - - - -
    -
    -

    Followers

    -
      -
      -
      - -
    • - - -
    • - +
      +
      + + + +
      +
      + +

      +
      +
        +
        +
        + + +
      • + + +
      • + + diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 749d95c0c23..7b8442b4c46 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_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/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index f0eb7b57298..02cf1f19149 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/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 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.py b/addons/project/project.py index fcc340d94ec..1a8f8c8bfe5 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -514,13 +514,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_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 = {} @@ -1204,15 +1201,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_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): """ Override of the (void) default notification method. """ diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 28106828c9a..395a3c63219 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.py b/addons/project_issue/project_issue.py index 74298223ed3..5c440976b5c 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -499,13 +499,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_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. """ diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 88dfe38e195..6dc5ed946f1 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.py b/addons/purchase/purchase.py index bdb3a3e839a..1d904383718 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -734,12 +734,16 @@ class purchase_order(osv.osv): def needaction_domain_get(self, cr, uid, ids, context=None): return [('state','=','draft')] + 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/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 99f84a5be5b..0cdc8d2d206 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 2f07e65f6d5..2e9f4104342 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.py b/addons/sale/sale.py index 02e8159335b..27c744f0242 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1027,7 +1027,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): diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index c3ddda260d4..07abe9c4318 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 @@
        +