├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── bower.json ├── dist ├── odoo.js └── odoo.min.js ├── gulp └── build.js ├── gulpfile.js ├── package.json └── src ├── app └── app.js └── components └── odoo ├── jsonRpc-service.js └── jsonRpc.spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | .sass-cache/ 4 | .tmp/ 5 | *.swp 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true, 21 | "validthis": true, 22 | "globals": { 23 | "angular": false, 24 | // Angular Mocks 25 | "inject": false, 26 | // JASMINE 27 | "describe": false, 28 | "it": false, 29 | "before": false, 30 | "beforeEach": false, 31 | "after": false, 32 | "afterEach": false, 33 | "expect": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: gulp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### angular-odoo 2 | 3 | Call Odoo webservices from AngularJS 4 | 5 | 6 | Why 7 | === 8 | 9 | Odoo is not HTTP friendly : every request shoud be POST, session_id should be added in the body, there some other stuff which should be added in each request... 10 | 11 | This module gives you an abstraction and friendly methods in order to communicate with Odoo. 12 | 13 | 14 | Requirements 15 | === 16 | 17 | * OpenERP 7 or Odoo 8 18 | * Angular > 1.4 19 | 20 | 21 | Install 22 | === 23 | 24 | Prefered method: 25 | 26 | bower install angular-odoo 27 | 28 | Alternative : 29 | 30 | Download dist/odoo.js or dist/odoo.min.js 31 | 32 | Include 33 | === 34 | 35 | Add the script to your page : 36 | 37 | ```html 38 | 39 | ``` 40 | 41 | Add the module __odoo__ to your applicaiton: 42 | ```js 43 | angular.module('yourApplication', ['odoo']); 44 | ``` 45 | 46 | Use in your services 47 | === 48 | 49 | Add __jsonRpc__ as a dependency. 50 | 51 | ```js 52 | angular.module('loginCtrl', ['$scope', 'jsonRpc', function($scope, jsonRpc) { 53 | 54 | jsonRpc.getDbList().then(function (result) { 55 | //get databases list 56 | $scope.dbs = result; 57 | }); 58 | 59 | $scope.login = function(creds) { 60 | jsonRpc.login(creds.db, creds.username, creds.password).then(function () { 61 | //login successfull redirect here 62 | }, function(reason) { 63 | //display error 64 | }); 65 | }; 66 | }]); 67 | 68 | ``` 69 | 70 | 71 | High level functions : 72 | 73 | * login 74 | * isLoggedIn 75 | * logout 76 | * searchRead 77 | * getSessionInfo 78 | * getServerInfo 79 | * getDbList 80 | * syncDataImport 81 | * syncImportObject 82 | * call 83 | 84 | 85 | Please read src/components/odoo/jsonRPC-service.js for code and detailled documentation. 86 | 87 | 88 | At [Akretion](http://akretion.com), we write Angular / Ionic applications and use this lib in all our devs when Odoo is the backend. 89 | 90 | 91 | Tests 92 | === 93 | 94 | There is some tests in jsonRpc.spec.js 95 | 96 | 97 | Contributors 98 | === 99 | 100 | * [Hparfr](https://github.com/hparfr) 101 | * [Sebastienbeau](https://github.com/sebastienbeau) 102 | * [Guewen](https://github.com/guewen) 103 | * [FranzPoize](https://github.com/FranzPoize) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odoo", 3 | "version": "0.1.3", 4 | "dependencies": { 5 | "angular": "~1.4.3" 6 | }, 7 | "devDependencies": { 8 | "angular-mocks": "~1.4.3", 9 | "jasmine": "~2.2.1" 10 | }, 11 | "main": "dist/odoo.js" 12 | } 13 | -------------------------------------------------------------------------------- /dist/odoo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('odoo', []); 3 | 4 | 'use strict'; 5 | angular.module('odoo').provider('jsonRpc', function jsonRpcProvider() { 6 | 7 | this.odooRpc = { 8 | odoo_server: "", 9 | uniq_id_counter: 0, 10 | context: {'lang': 'fr_FR'}, 11 | shouldManageSessionId: false, //try without first 12 | errorInterceptors: [] 13 | }; 14 | 15 | var preflightPromise = null; 16 | 17 | this.$get = ["$http", "$q", "$timeout", function($http, $q, $timeout) { 18 | 19 | var odooRpc = this.odooRpc; 20 | 21 | /** 22 | * login 23 | * update cookie (session_id) in both cases 24 | * @return promise 25 | * resolve promise if credentials ok 26 | * reject promise if credentials ko (with {title: wrong_login}) 27 | * reject promise in other cases (http issues, server error) 28 | */ 29 | odooRpc.login = function(db, login, password) { 30 | var params = { 31 | db : db, 32 | login : login, 33 | password : password 34 | }; 35 | 36 | return odooRpc.sendRequest('/web/session/authenticate', params).then(function(result) { 37 | if (!result.uid) { 38 | cookies.delete_sessionId(); 39 | return $q.reject({ 40 | title: 'wrong_login', 41 | message:"Username and password don't match", 42 | fullTrace: result 43 | }); 44 | } 45 | odooRpc.context = result.user_context; 46 | cookies.set_sessionId(result.session_id); 47 | return result; 48 | }); 49 | }; 50 | 51 | /** 52 | * check if logged in or not 53 | * @param force 54 | * if false -> check the cookies and return boolean 55 | * if true -> check with the server if still connected return promise 56 | * @return boolean || promise 57 | * 58 | */ 59 | odooRpc.isLoggedIn = function (force) { 60 | if (!force) 61 | return cookies.get_sessionId().length > 0; 62 | 63 | return odooRpc.getSessionInfo().then(function (result) { 64 | cookies.set_sessionId(result.session_id); 65 | return !!(result.uid); 66 | }); 67 | }; 68 | 69 | /** 70 | * logout (delete cookie) 71 | * @param force 72 | * if true try to connect with falsy ids 73 | * @return null || promise 74 | */ 75 | odooRpc.logout = function (force) { 76 | cookies.delete_sessionId(); 77 | if (force) 78 | return odooRpc.getSessionInfo().then(function (r) { //get db from sessionInfo 79 | if (r.db) 80 | return odooRpc.login(r.db, '', ''); 81 | }); 82 | return $q.when(); 83 | }; 84 | 85 | odooRpc.searchRead = function(model, domain, fields) { 86 | var params = { 87 | model: model, 88 | domain: domain, 89 | fields: fields 90 | } 91 | return odooRpc.sendRequest('/web/dataset/search_read', params); 92 | }; 93 | 94 | odooRpc.getSessionInfo = function(model, method, args, kwargs) { 95 | return odooRpc.sendRequest('/web/session/get_session_info', {}); 96 | }; 97 | 98 | odooRpc.getServerInfo = function(model, method, args, kwargs) { 99 | return odooRpc.sendRequest('/web/webclient/version_info', {}); 100 | }; 101 | 102 | odooRpc.getDbList = function() { 103 | return odooRpc.sendRequest('/web/database/get_list', {}); 104 | }; 105 | odooRpc.syncDataImport = function(model, func_key, base_domain, filter_domain, limit, object) { 106 | return odooRpc.call(model, 'get_sync_data', [ 107 | func_key, object.timekey, base_domain, filter_domain, limit 108 | ], {}).then(function(result) { 109 | //if (object.timekey === result.timekey) TODO: add mutlidomain before uncomment 110 | // return; //no change since last run 111 | object.timekey = result.timekey; 112 | 113 | angular.forEach(result.remove_ids, function(id) { 114 | delete object.data[id]; 115 | }); 116 | 117 | if (Object.keys(result.data).length) { 118 | angular.merge(object.data, result.data); ///merge deeply old with new 119 | return odooRpc.syncDataImport(model, func_key, base_domain, filter_domain, limit, object); 120 | } 121 | }); 122 | }; 123 | 124 | odooRpc.syncImportObject = function(params) { 125 | /* params = { 126 | model: 'odoo.model', 127 | func_key: 'my_function_key', 128 | domain: [], 129 | limit: 50, 130 | interval: 5000, 131 | } 132 | 133 | When an error happens, the sync cycle is interrupted. 134 | 135 | An optional parameter 'onErrorRetry' can be specified. If its value is 136 | true, then the sync cycle will continue on the next interval even when 137 | errors occur. For a more fine-grained control over the retries, 138 | 'onErrorRetry' could also be a function, taking the error as argument. 139 | It should call 'nextSync()' on the synchronized object's API to delay 140 | the next sync iteration. 141 | 142 | Example: 143 | 144 | params = { 145 | ... 146 | onErrorRetry: function(sync, err) { 147 | if(shouldRetry(err)) { 148 | sync.nextSync(); 149 | } 150 | } 151 | } 152 | 153 | return a synchronized object where you can access 154 | to the data using object.data 155 | */ 156 | var stop = false; 157 | var watchers = []; 158 | var object = { 159 | data: {}, 160 | timekey: null, 161 | stopCallback: function () { 162 | stop = true; 163 | }, 164 | watch: function(fun) { 165 | watchers.push(fun); 166 | }, 167 | nextSync: nextSync 168 | }; 169 | 170 | function nextSync(interval) { 171 | if(!stop) { 172 | $timeout(sync, interval || params.interval); 173 | } 174 | } 175 | 176 | function runWatchers(data) { 177 | watchers.forEach(function (fun) { 178 | fun(object); 179 | }); 180 | } 181 | 182 | var errorCallback = null; 183 | if(angular.isFunction(params.onErrorRetry)) { 184 | errorCallback = function(err) { params.onErrorRetry(object, err); }; 185 | } else if(params.onErrorRetry) { 186 | errorCallback = function(err) { nextSync(); }; 187 | } 188 | 189 | function sync() { 190 | 191 | odooRpc.syncDataImport( 192 | params.model, 193 | params.func_key, 194 | params.base_domain, 195 | params.filter_domain, 196 | params.limit, 197 | object) 198 | .then(nextSync) 199 | .then(runWatchers) 200 | .catch(errorCallback); 201 | } 202 | sync(); 203 | 204 | return object; 205 | }; 206 | 207 | odooRpc.call = function(model, method, args, kwargs) { 208 | 209 | kwargs = kwargs || {}; 210 | kwargs.context = kwargs.context || {}; 211 | angular.extend(kwargs.context, odooRpc.context); 212 | 213 | var params = { 214 | model: model, 215 | method: method, 216 | args: args, 217 | kwargs: kwargs, 218 | }; 219 | return odooRpc.sendRequest('/web/dataset/call_kw', params); 220 | }; 221 | 222 | 223 | /** 224 | * base function 225 | */ 226 | odooRpc.sendRequest = function(url, params) { 227 | 228 | /** (internal) build request for $http 229 | * keep track of uniq_id_counter 230 | * add session_id in the request (for Odoo v7 only) 231 | */ 232 | function buildRequest(url, params) { 233 | odooRpc.uniq_id_counter += 1; 234 | if (odooRpc.shouldManageSessionId) 235 | params.session_id = cookies.get_sessionId(); 236 | 237 | var json_data = { 238 | jsonrpc: '2.0', 239 | method: 'call', 240 | params: params, //payload 241 | }; 242 | var headers = { 243 | 'Content-Type': 'application/json', 244 | 'X-Openerp-Session-Id': cookies.get_sessionId() 245 | } 246 | return { 247 | 'method' : 'POST', 248 | 'url' : odooRpc.odoo_server + url, 249 | 'data' : JSON.stringify(json_data), 250 | 'headers': headers, 251 | 'id': ("r" + odooRpc.uniq_id_counter), 252 | }; 253 | } 254 | 255 | /** (internal) Odoo do some error handling and doesn't care 256 | * about HTTP response code 257 | * catch errors codes here and reject 258 | * @param response $http promise 259 | * @return promise 260 | * if no error : response.data ($http.config & header stripped) 261 | * if error : reject with a custom errorObj 262 | */ 263 | function handleOdooErrors(response) { 264 | if (!response.data.error) 265 | return response.data; 266 | 267 | var error = response.data.error; 268 | var errorObj = { 269 | title: '', 270 | message:'', 271 | fullTrace: error 272 | }; 273 | 274 | if (error.code === 200 && error.message === "Odoo Server Error" && error.data.name === "werkzeug.exceptions.NotFound") { 275 | errorObj.title = 'page_not_found'; 276 | errorObj.message = 'HTTP Error'; 277 | } else if ( (error.code === 100 && error.message === "Odoo Session Expired") || //v8 278 | (error.code === 300 && error.message === "OpenERP WebClient Error" && error.data.debug.match("SessionExpiredException")) //v7 279 | ) { 280 | errorObj.title ='session_expired'; 281 | cookies.delete_sessionId(); 282 | } else if ( (error.message === "Odoo Server Error" && /FATAL: database "(.+)" does not exist/.test(error.data.message))) { 283 | errorObj.title = "database_not_found"; 284 | errorObj.message = error.data.message; 285 | } else if ( (error.data.name === "openerp.exceptions.AccessError")) { 286 | errorObj.title = 'AccessError'; 287 | errorObj.message = error.data.message; 288 | } else { 289 | var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); 290 | if (split.length > 1) { 291 | error.type = split.shift(); 292 | error.data.fault_code = error.data.fault_code.substr(error.type.length + 4); 293 | } 294 | 295 | if (error.code === 200 && error.type) { 296 | errorObj.title = error.type; 297 | errorObj.message = error.data.fault_code.replace(/\n/g, "
"); 298 | } else { 299 | errorObj.title = error.message; 300 | errorObj.message = error.data.debug.replace(/\n/g, "
"); 301 | } 302 | } 303 | odooRpc.errorInterceptors.forEach(function (i) { 304 | i(errorObj); 305 | }); 306 | return $q.reject(errorObj) 307 | } 308 | 309 | /** 310 | * (internal) 311 | * catch HTTP response code (not handled by Odoo ie Error 500, 404) 312 | * @params $http rejected promise 313 | * @return promise 314 | */ 315 | function handleHttpErrors(reason) { 316 | var errorObj = {title:'http', fullTrace: reason, message:'HTTP Error'}; 317 | odooRpc.errorInterceptors.forEach(function (i) { 318 | i(errorObj); 319 | }); 320 | return $q.reject(errorObj); 321 | } 322 | 323 | /** 324 | * (internal) wrapper around $http for handling errors and build request 325 | */ 326 | function http(url, params) { 327 | var req = buildRequest(url, params); 328 | return $http(req).then(handleOdooErrors, handleHttpErrors); 329 | } 330 | 331 | /** (internal) determine if session_id shoud be managed by this lib 332 | * more info: 333 | * in v7 session_id is returned by the server in the payload 334 | * and it should be added in each request's paylaod. 335 | * it's 336 | * 337 | * in v8 session_id is set as a cookie by the server 338 | * therefor the browser send it on each request automatically 339 | * 340 | * in both case, we keep session_id as a cookie to be compliant with other odoo web clients 341 | * 342 | */ 343 | function preflight() { 344 | //preflightPromise is a kind of cache and is set only if the request succeed 345 | return preflightPromise || http('/web/webclient/version_info', {}).then(function (reason) { 346 | odooRpc.shouldManageSessionId = (reason.result.server_serie < "8"); //server_serie is string like "7.01" 347 | preflightPromise = $q.when(); //runonce 348 | }); 349 | } 350 | 351 | return preflight().then(function () { 352 | return http(url, params).then(function(response) { 353 | var subRequests = []; 354 | if (response.result.type === "ir.actions.act_proxy") { 355 | angular.forEach(response.result.action_list, function(action) { 356 | subRequests.push($http.post(action['url'], action['params'])); 357 | }); 358 | return $q.all(subRequests); 359 | } else 360 | return response.result; 361 | }); 362 | }); 363 | }; 364 | 365 | return odooRpc; 366 | }]; 367 | 368 | var cookies = (function() { 369 | var session_id; //cookies doesn't work with Android Default Browser / Ionic 370 | return { 371 | delete_sessionId: function() { 372 | session_id = null; 373 | document.cookie = 'session_id=; expires=Thu, 01 Jan 1970 00:00:00 GMT'; 374 | }, 375 | get_sessionId: function () { 376 | return document.cookie.split('; ') 377 | .filter(function (x) { return x.indexOf('session_id') === 0; }) 378 | .map(function (x) { return x.split('=')[1]; }) 379 | .pop() || session_id || ""; 380 | }, 381 | set_sessionId: function (val) { 382 | document.cookie = 'session_id=' + val; 383 | session_id = val; 384 | } 385 | }; 386 | }()); 387 | }); 388 | 389 | -------------------------------------------------------------------------------- /dist/odoo.min.js: -------------------------------------------------------------------------------- 1 | "use strict";angular.module("odoo",[]); 2 | "use strict";angular.module("odoo").provider("jsonRpc",function(){this.odooRpc={odoo_server:"",uniq_id_counter:0,context:{lang:"fr_FR"},shouldManageSessionId:!1,errorInterceptors:[]};var e=null;this.$get=["$http","$q","$timeout",function(n,o,s){var r=this.odooRpc;return r.login=function(e,n,s){var i={db:e,login:n,password:s};return r.sendRequest("/web/session/authenticate",i).then(function(e){return e.uid?(r.context=e.user_context,t.set_sessionId(e.session_id),e):(t.delete_sessionId(),o.reject({title:"wrong_login",message:"Username and password don't match",fullTrace:e}))})},r.isLoggedIn=function(e){return e?r.getSessionInfo().then(function(e){return t.set_sessionId(e.session_id),!!e.uid}):t.get_sessionId().length>0},r.logout=function(e){return t.delete_sessionId(),e?r.getSessionInfo().then(function(e){return e.db?r.login(e.db,"",""):void 0}):o.when()},r.searchRead=function(e,t,n){var o={model:e,domain:t,fields:n};return r.sendRequest("/web/dataset/search_read",o)},r.getSessionInfo=function(){return r.sendRequest("/web/session/get_session_info",{})},r.getServerInfo=function(){return r.sendRequest("/web/webclient/version_info",{})},r.getDbList=function(){return r.sendRequest("/web/database/get_list",{})},r.syncDataImport=function(e,t,n,o,s,i){return r.call(e,"get_sync_data",[t,i.timekey,n,o,s],{}).then(function(a){return i.timekey=a.timekey,angular.forEach(a.remove_ids,function(e){delete i.data[e]}),Object.keys(a.data).length?(angular.merge(i.data,a.data),r.syncDataImport(e,t,n,o,s,i)):void 0})},r.syncImportObject=function(e){function t(t){i||s(o,t||e.interval)}function n(){a.forEach(function(e){e(u)})}function o(){r.syncDataImport(e.model,e.func_key,e.base_domain,e.filter_domain,e.limit,u).then(t).then(n).catch(c)}var i=!1,a=[],u={data:{},timekey:null,stopCallback:function(){i=!0},watch:function(e){a.push(e)},nextSync:t},c=null;return angular.isFunction(e.onErrorRetry)?c=function(t){e.onErrorRetry(u,t)}:e.onErrorRetry&&(c=function(){t()}),o(),u},r.call=function(e,t,n,o){o=o||{},o.context=o.context||{},angular.extend(o.context,r.context);var s={model:e,method:t,args:n,kwargs:o};return r.sendRequest("/web/dataset/call_kw",s)},r.sendRequest=function(s,i){function a(e,n){r.uniq_id_counter+=1,r.shouldManageSessionId&&(n.session_id=t.get_sessionId());var o={jsonrpc:"2.0",method:"call",params:n},s={"Content-Type":"application/json","X-Openerp-Session-Id":t.get_sessionId()};return{method:"POST",url:r.odoo_server+e,data:JSON.stringify(o),headers:s,id:"r"+r.uniq_id_counter}}function u(e){if(!e.data.error)return e.data;var n=e.data.error,s={title:"",message:"",fullTrace:n};if(200===n.code&&"Odoo Server Error"===n.message&&"werkzeug.exceptions.NotFound"===n.data.name)s.title="page_not_found",s.message="HTTP Error";else if(100===n.code&&"Odoo Session Expired"===n.message||300===n.code&&"OpenERP WebClient Error"===n.message&&n.data.debug.match("SessionExpiredException"))s.title="session_expired",t.delete_sessionId();else if("Odoo Server Error"===n.message&&/FATAL: database "(.+)" does not exist/.test(n.data.message))s.title="database_not_found",s.message=n.data.message;else if("openerp.exceptions.AccessError"===n.data.name)s.title="AccessError",s.message=n.data.message;else{var i=(""+n.data.fault_code).split("\n")[0].split(" -- ");i.length>1&&(n.type=i.shift(),n.data.fault_code=n.data.fault_code.substr(n.type.length+4)),200===n.code&&n.type?(s.title=n.type,s.message=n.data.fault_code.replace(/\n/g,"
")):(s.title=n.message,s.message=n.data.debug.replace(/\n/g,"
"))}return r.errorInterceptors.forEach(function(e){e(s)}),o.reject(s)}function c(e){var t={title:"http",fullTrace:e,message:"HTTP Error"};return r.errorInterceptors.forEach(function(e){e(t)}),o.reject(t)}function d(e,t){var o=a(e,t);return n(o).then(u,c)}function l(){return e||d("/web/webclient/version_info",{}).then(function(t){r.shouldManageSessionId=t.result.server_serie<"8",e=o.when()})}return l().then(function(){return d(s,i).then(function(e){var t=[];return"ir.actions.act_proxy"===e.result.type?(angular.forEach(e.result.action_list,function(e){t.push(n.post(e.url,e.params))}),o.all(t)):e.result})})},r}];var t=function(){var e;return{delete_sessionId:function(){e=null,document.cookie="session_id=; expires=Thu, 01 Jan 1970 00:00:00 GMT"},get_sessionId:function(){return document.cookie.split("; ").filter(function(e){return 0===e.indexOf("session_id")}).map(function(e){return e.split("=")[1]}).pop()||e||""},set_sessionId:function(t){document.cookie="session_id="+t,e=t}}}()}); -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var bump = require('gulp-bump'); 5 | var git = require('gulp-git'); 6 | var paths = gulp.paths; 7 | 8 | var $ = require('gulp-load-plugins')({ 9 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del'] 10 | }); 11 | 12 | gulp.task('build-lib', [], function () { 13 | return gulp.src([ 14 | paths.src + '/app/app.js', 15 | paths.src + '/components/**/*.js' 16 | ]) 17 | .pipe($.ignore.exclude('*.spec.js')) 18 | .pipe($.ngAnnotate()) 19 | .pipe($.concat('odoo.js')) 20 | .pipe(gulp.dest(paths.dist + '/')) 21 | .pipe($.size({ title: paths.dist + '/', showFiles: true })); 22 | }); 23 | 24 | gulp.task('build-lib-min', [], function () { 25 | return gulp.src([ 26 | paths.src + '/app/app.js', 27 | paths.src + '/components/**/*.js' 28 | ]) 29 | .pipe($.ignore.exclude('*.spec.js')) 30 | .pipe($.ngAnnotate()) 31 | .pipe($.uglify({preserveComments:$.uglifySaveLicense})) 32 | .pipe($.concat('odoo.min.js')) 33 | .pipe(gulp.dest(paths.dist + '/')) 34 | .pipe($.size({ title: paths.dist + '/', showFiles: true })); 35 | }); 36 | 37 | gulp.task('bump', function() { 38 | gulp.src(['./bower.json', './package.json']) 39 | .pipe(bump()) 40 | .pipe(git.add()) 41 | .pipe(gulp.dest('./')); 42 | }); 43 | 44 | gulp.task('tag', function() { 45 | var pkg = require('../package.json'); 46 | var message = 'Release ' + pkg.version; 47 | return gulp.src('./') 48 | .pipe(git.add()) 49 | .pipe(git.commit(message)) 50 | .on('end', function (e) { 51 | console.log('on end', e); 52 | git.tag(pkg.version, message) 53 | }); 54 | }); 55 | 56 | gulp.task('clean', function (done) { 57 | $.del([paths.dist + '/', paths.tmp + '/'], done); 58 | }); 59 | 60 | gulp.task('build', ['build-lib', 'build-lib-min']); 61 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | gulp.paths = { 6 | src: 'src', 7 | dist: 'dist', 8 | tmp: '.tmp', 9 | e2e: 'e2e' 10 | }; 11 | 12 | require('require-dir')('./gulp'); 13 | 14 | gulp.task('default', ['clean'], function () { 15 | gulp.start('build'); 16 | }); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odoo", 3 | "version": "0.1.3", 4 | "dependencies": { 5 | "gulp-bump": "^0.3.1", 6 | "gulp-git": "^1.2.0" 7 | }, 8 | "devDependencies": { 9 | "gulp": "~3.8.10", 10 | "gulp-concat": "~2.5.2", 11 | "del": "~0.1.3", 12 | "gulp-size": "~1.1.0", 13 | "require-dir": "~0.1.0", 14 | "gulp-load-plugins": "~0.7.1", 15 | "gulp-ng-annotate": "~0.3.6", 16 | "gulp-uglify": "~1.0.1", 17 | "uglify-save-license": "~0.4.1", 18 | "gulp-ignore": "~1.2.1" 19 | }, 20 | "engines": { 21 | "node": ">=0.10.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('odoo', []); 3 | -------------------------------------------------------------------------------- /src/components/odoo/jsonRpc-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('odoo').provider('jsonRpc', function jsonRpcProvider() { 3 | 4 | this.odooRpc = { 5 | odoo_server: "", 6 | uniq_id_counter: 0, 7 | odooVersion: '', 8 | context: {'lang': 'fr_FR'}, 9 | shouldManageSessionId: false, //try without first for v7 10 | errorInterceptors: [] 11 | }; 12 | 13 | var preflightPromise = null; 14 | 15 | this.$get = function($http, $q, $timeout) { 16 | 17 | var odooRpc = this.odooRpc; 18 | 19 | /** 20 | * login 21 | * update cookie (session_id) in both cases 22 | * @return promise 23 | * resolve promise if credentials ok 24 | * reject promise if credentials ko (with {title: wrong_login}) 25 | * reject promise in other cases (http issues, server error) 26 | */ 27 | odooRpc.login = function(db, login, password) { 28 | var params = { 29 | db : db, 30 | login : login, 31 | password : password 32 | }; 33 | 34 | return odooRpc.sendRequest('/web/session/authenticate', params).then(function(result) { 35 | if (!result.uid) { 36 | cookies.delete_sessionId(); 37 | return $q.reject({ 38 | title: 'wrong_login', 39 | message:"Username and password don't match", 40 | fullTrace: result 41 | }); 42 | } 43 | odooRpc.context = result.user_context; 44 | cookies.set_sessionId(result.session_id); 45 | return result; 46 | }); 47 | }; 48 | 49 | /** 50 | * check if logged in or not 51 | * @param force 52 | * if false -> check the cookies and return boolean 53 | * if true -> check with the server if still connected return promise 54 | * @return boolean || promise 55 | * 56 | */ 57 | odooRpc.isLoggedIn = function (force) { 58 | if (!force) 59 | return cookies.get_sessionId().length > 0; 60 | 61 | return odooRpc.getSessionInfo().then(function (result) { 62 | cookies.set_sessionId(result.session_id); 63 | return !!(result.uid); 64 | }); 65 | }; 66 | 67 | /** 68 | * logout (delete cookie) 69 | * @param force 70 | * if true try to connect with falsy ids 71 | * @return null || promise 72 | */ 73 | odooRpc.logout = function (force) { 74 | cookies.delete_sessionId(); 75 | if (force) 76 | return odooRpc.getSessionInfo().then(function (r) { //get db from sessionInfo 77 | if (r.db) 78 | return odooRpc.login(r.db, '', ''); 79 | }); 80 | return $q.when(); 81 | }; 82 | 83 | odooRpc.searchRead = function(model, domain, fields) { 84 | var params = { 85 | model: model, 86 | domain: domain, 87 | fields: fields 88 | } 89 | return odooRpc.sendRequest('/web/dataset/search_read', params); 90 | }; 91 | 92 | odooRpc.getSessionInfo = function(model, method, args, kwargs) { 93 | return odooRpc.sendRequest('/web/session/get_session_info', {}); 94 | }; 95 | 96 | odooRpc.getServerInfo = function(model, method, args, kwargs) { 97 | return odooRpc.sendRequest('/web/webclient/version_info', {}); 98 | }; 99 | 100 | odooRpc.getDbList = function() { 101 | if (odooRpc.odooVersion[0] == "7" || odooRpc.odooVersion[0] == "8") 102 | return odooRpc.sendRequest('/web/database/get_list', {}); 103 | return odooRpc.callJson('db', 'list', {}); 104 | }; 105 | odooRpc.syncDataImport = function(model, func_key, base_domain, filter_domain, limit, object) { 106 | return odooRpc.call(model, 'get_sync_data', [ 107 | func_key, object.timekey, base_domain, filter_domain, limit 108 | ], {}).then(function(result) { 109 | //if (object.timekey === result.timekey) TODO: add mutlidomain before uncomment 110 | // return; //no change since last run 111 | object.timekey = result.timekey; 112 | 113 | angular.forEach(result.remove_ids, function(id) { 114 | delete object.data[id]; 115 | }); 116 | 117 | if (Object.keys(result.data).length) { 118 | angular.merge(object.data, result.data); ///merge deeply old with new 119 | return odooRpc.syncDataImport(model, func_key, base_domain, filter_domain, limit, object); 120 | } 121 | }); 122 | }; 123 | 124 | odooRpc.syncImportObject = function(params) { 125 | /* params = { 126 | model: 'odoo.model', 127 | func_key: 'my_function_key', 128 | domain: [], 129 | limit: 50, 130 | interval: 5000, 131 | } 132 | 133 | When an error happens, the sync cycle is interrupted. 134 | 135 | An optional parameter 'onErrorRetry' can be specified. If its value is 136 | true, then the sync cycle will continue on the next interval even when 137 | errors occur. For a more fine-grained control over the retries, 138 | 'onErrorRetry' could also be a function, taking the error as argument. 139 | It should call 'nextSync()' on the synchronized object's API to delay 140 | the next sync iteration. 141 | 142 | Example: 143 | 144 | params = { 145 | ... 146 | onErrorRetry: function(sync, err) { 147 | if(shouldRetry(err)) { 148 | sync.nextSync(); 149 | } 150 | } 151 | } 152 | 153 | return a synchronized object where you can access 154 | to the data using object.data 155 | */ 156 | var stop = false; 157 | var watchers = []; 158 | var object = { 159 | data: {}, 160 | timekey: null, 161 | stopCallback: function () { 162 | stop = true; 163 | }, 164 | watch: function(fun) { 165 | watchers.push(fun); 166 | }, 167 | nextSync: nextSync 168 | }; 169 | 170 | function nextSync(interval) { 171 | if(!stop) { 172 | $timeout(sync, interval || params.interval); 173 | } 174 | } 175 | 176 | function runWatchers(data) { 177 | watchers.forEach(function (fun) { 178 | fun(object); 179 | }); 180 | } 181 | 182 | var errorCallback = null; 183 | if(angular.isFunction(params.onErrorRetry)) { 184 | errorCallback = function(err) { params.onErrorRetry(object, err); }; 185 | } else if(params.onErrorRetry) { 186 | errorCallback = function(err) { nextSync(); }; 187 | } 188 | 189 | function sync() { 190 | 191 | odooRpc.syncDataImport( 192 | params.model, 193 | params.func_key, 194 | params.base_domain, 195 | params.filter_domain, 196 | params.limit, 197 | object) 198 | .then(nextSync) 199 | .then(runWatchers) 200 | .catch(errorCallback); 201 | } 202 | sync(); 203 | 204 | return object; 205 | }; 206 | 207 | odooRpc.call = function(model, method, args, kwargs) { 208 | 209 | kwargs = kwargs || {}; 210 | kwargs.context = kwargs.context || {}; 211 | angular.extend(kwargs.context, odooRpc.context); 212 | 213 | var params = { 214 | model: model, 215 | method: method, 216 | args: args, 217 | kwargs: kwargs, 218 | }; 219 | return odooRpc.sendRequest('/web/dataset/call_kw', params); 220 | }; 221 | 222 | odooRpc.callJson = function(service, method, args) { 223 | var params = { 224 | service: service, 225 | method: method, 226 | args: args, 227 | }; 228 | return odooRpc.sendRequest('/jsonrpc', params); 229 | } 230 | 231 | /** 232 | * base function 233 | */ 234 | odooRpc.sendRequest = function(url, params) { 235 | 236 | /** (internal) build request for $http 237 | * keep track of uniq_id_counter 238 | * add session_id in the request (for Odoo v7 only) 239 | */ 240 | function buildRequest(url, params) { 241 | odooRpc.uniq_id_counter += 1; 242 | if (odooRpc.shouldManageSessionId) 243 | params.session_id = cookies.get_sessionId(); 244 | 245 | var json_data = { 246 | jsonrpc: '2.0', 247 | method: 'call', 248 | params: params, //payload 249 | }; 250 | var headers = { 251 | 'Content-Type': 'application/json', 252 | 'X-Openerp-Session-Id': cookies.get_sessionId() 253 | } 254 | return { 255 | 'method' : 'POST', 256 | 'url' : odooRpc.odoo_server + url, 257 | 'data' : JSON.stringify(json_data), 258 | 'headers': headers, 259 | 'id': ("r" + odooRpc.uniq_id_counter), 260 | }; 261 | } 262 | 263 | /** (internal) Odoo do some error handling and doesn't care 264 | * about HTTP response code 265 | * catch errors codes here and reject 266 | * @param response $http promise 267 | * @return promise 268 | * if no error : response.data ($http.config & header stripped) 269 | * if error : reject with a custom errorObj 270 | */ 271 | function handleOdooErrors(response) { 272 | if (!response.data.error) 273 | return response.data; 274 | 275 | var error = response.data.error; 276 | var errorObj = { 277 | title: '', 278 | message:'', 279 | fullTrace: error 280 | }; 281 | 282 | if (error.code === 200 && error.message === "Odoo Server Error" && error.data.name === "werkzeug.exceptions.NotFound") { 283 | errorObj.title = 'page_not_found'; 284 | errorObj.message = 'HTTP Error'; 285 | } else if ( (error.code === 100 && error.message === "Odoo Session Expired") || //v8 286 | (error.code === 300 && error.message === "OpenERP WebClient Error" && error.data.debug.match("SessionExpiredException")) //v7 287 | ) { 288 | errorObj.title ='session_expired'; 289 | cookies.delete_sessionId(); 290 | } else if ( (error.message === "Odoo Server Error" && /FATAL: database "(.+)" does not exist/.test(error.data.message))) { 291 | errorObj.title = "database_not_found"; 292 | errorObj.message = error.data.message; 293 | } else if ( (error.data.name === "openerp.exceptions.AccessError")) { 294 | errorObj.title = 'AccessError'; 295 | errorObj.message = error.data.message; 296 | } else { 297 | var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); 298 | if (split.length > 1) { 299 | error.type = split.shift(); 300 | error.data.fault_code = error.data.fault_code.substr(error.type.length + 4); 301 | } 302 | 303 | if (error.code === 200 && error.type) { 304 | errorObj.title = error.type; 305 | errorObj.message = error.data.fault_code.replace(/\n/g, "
"); 306 | } else { 307 | errorObj.title = error.message; 308 | errorObj.message = error.data.debug.replace(/\n/g, "
"); 309 | } 310 | } 311 | odooRpc.errorInterceptors.forEach(function (i) { 312 | i(errorObj); 313 | }); 314 | return $q.reject(errorObj) 315 | } 316 | 317 | /** 318 | * (internal) 319 | * catch HTTP response code (not handled by Odoo ie Error 500, 404) 320 | * @params $http rejected promise 321 | * @return promise 322 | */ 323 | function handleHttpErrors(reason) { 324 | var errorObj = {title:'http', fullTrace: reason, message:'HTTP Error'}; 325 | odooRpc.errorInterceptors.forEach(function (i) { 326 | i(errorObj); 327 | }); 328 | return $q.reject(errorObj); 329 | } 330 | 331 | /** 332 | * (internal) wrapper around $http for handling errors and build request 333 | */ 334 | function http(url, params) { 335 | var req = buildRequest(url, params); 336 | return $http(req).then(handleOdooErrors, handleHttpErrors); 337 | } 338 | 339 | /** (internal) determine if session_id shoud be managed by this lib 340 | * more info: 341 | * in v7 session_id is returned by the server in the payload 342 | * and it should be added in each request's paylaod. 343 | * it's 344 | * 345 | * in v8 session_id is set as a cookie by the server 346 | * therefor the browser send it on each request automatically 347 | * 348 | * in both case, we keep session_id as a cookie to be compliant with other odoo web clients 349 | * 350 | */ 351 | function preflight() { 352 | //preflightPromise is a kind of cache and is set only if the request succeed 353 | return preflightPromise || http('/web/webclient/version_info', {}).then(function (reason) { 354 | odooRpc.odooVersion = reason.result.server_serie; 355 | odooRpc.shouldManageSessionId = (odooRpc.odooVersion[0] == "7"); //server_serie is string like "7.01" 356 | preflightPromise = $q.when(); //runonce 357 | }); 358 | } 359 | 360 | return preflight().then(function () { 361 | return http(url, params).then(function(response) { 362 | var subRequests = []; 363 | if (response.result.type === "ir.actions.act_proxy") { 364 | angular.forEach(response.result.action_list, function(action) { 365 | subRequests.push($http.post(action['url'], action['params'])); 366 | }); 367 | return $q.all(subRequests); 368 | } else 369 | return response.result; 370 | }); 371 | }); 372 | }; 373 | 374 | return odooRpc; 375 | }; 376 | 377 | var cookies = (function() { 378 | var session_id; //cookies doesn't work with Android Default Browser / Ionic 379 | return { 380 | delete_sessionId: function() { 381 | session_id = null; 382 | document.cookie = 'session_id=; expires=Thu, 01 Jan 1970 00:00:00 GMT'; 383 | }, 384 | get_sessionId: function () { 385 | return document.cookie.split('; ') 386 | .filter(function (x) { return x.indexOf('session_id') === 0; }) 387 | .map(function (x) { return x.split('=')[1]; }) 388 | .pop() || session_id || ""; 389 | }, 390 | set_sessionId: function (val) { 391 | document.cookie = 'session_id=' + val; 392 | session_id = val; 393 | } 394 | }; 395 | }()); 396 | }); 397 | 398 | -------------------------------------------------------------------------------- /src/components/odoo/jsonRpc.spec.js: -------------------------------------------------------------------------------- 1 | describe("jsonRpc tests", function() { 2 | 3 | var $httpBackend; 4 | var jsonRpc; 5 | 6 | beforeEach(module('odoo')); 7 | 8 | beforeEach(inject(function(_jsonRpc_) { 9 | jsonRpc = _jsonRpc_; 10 | })); 11 | 12 | beforeEach(inject(function(_$httpBackend_) { 13 | $httpBackend = _$httpBackend_ 14 | })); 15 | 16 | afterEach(function() { 17 | $httpBackend.verifyNoOutstandingExpectation(); 18 | $httpBackend.verifyNoOutstandingRequest(); 19 | }); 20 | function fail(a) { 21 | expect(true).toBe(false); 22 | } 23 | function success() { 24 | expect(true).toBe(true); 25 | } 26 | 27 | function set_version_info7() { 28 | $httpBackend.whenPOST('/web/webclient/version_info').respond({ 29 | jsonrpc:"2.0", 30 | id: null, 31 | result: {"server_serie": "7.0", "server_version_info": [7, 0, 0, "final", 0], "server_version": "7.0", "protocol_version": 1} 32 | }); 33 | } 34 | function set_version_info8() { 35 | $httpBackend.whenPOST('/web/webclient/version_info').respond({ 36 | jsonrpc:"2.0", 37 | id: null, 38 | result: {"server_serie": "8.0", "server_version_info": [8, 0, 0, "final", 0], "server_version": "8.0", "protocol_version": 1} 39 | }); 40 | } 41 | 42 | describe("session expiration", function () { 43 | function success(reason) { 44 | expect(reason.title).toEqual("session_expired"); 45 | } 46 | it("session expired v7", function () { 47 | set_version_info7(); 48 | 49 | $httpBackend.whenPOST('/web/session/get_session_info').respond ({ 50 | jsonrpc:"2.0", 51 | id: null, 52 | error: { 53 | message: "OpenERP WebClient Error", 54 | code: 300, 55 | data : { 56 | debug: "Client Traceback (most recent call last): File \"/workspace/parts/odoo/addons/web/http.py\", line 204, in dispatch response[\"result\"] = method(self, **self.params) File \"/workspace/parts/odoo/addons/web/controllers/main.py\", line 1133, in call_kw return self._call_kw(req, model, method, args, kwargs) File \"/workspace/parts/odoo/addons/web/controllers/main.py\", line 1125, in _call_kw return getattr(req.session.model(model), method)(*args, **kwargs) File \"/workspace/parts/odoo/addons/web/session.py\", line 158, in model raise SessionExpiredException(\"Session expired\")SessionExpiredException: Session expired", 57 | type: "client_exception" 58 | } 59 | } 60 | }); 61 | jsonRpc.sendRequest('/web/session/get_session_info', {}).then(fail, success);; 62 | $httpBackend.flush(); 63 | }); 64 | 65 | it("session expired v8", function () { 66 | set_version_info8(); 67 | 68 | $httpBackend.whenPOST('/web/session/get_session_info').respond({ 69 | jsonrpc:"2.0", 70 | id: null, 71 | error: { 72 | message: "Odoo Session Expired", 73 | code: 100 74 | }, 75 | data: { 76 | debug: "Traceback (most recent call last): File \"/workspace/parts/odoo/openerp/http.py\", line 530, in _handle_exception return super(JsonRequest, self)._handle_exception(exception) File \"/workspace/parts/odoo/openerp/addons/base/ir/ir_http.py\", line 160, in _dispatch auth_method = self._authenticate(func.routing[\"auth\"]) File \"/workspace/parts/odoo/openerp/addons/base/ir/ir_http.py\", line 93, in _authenticate getattr(self, \"_auth_method_%s\" % auth_method)() File \"/workspace/parts/odoo/openerp/addons/base/ir/ir_http.py\", line 70, in _auth_method_user raise http.SessionExpiredException(\"Session expired\")SessionExpiredException: Session expired", 77 | message: "Session expired", 78 | name:"openerp.http.SessionExpiredException", 79 | arguments: ["Session expired"] 80 | } 81 | }); 82 | jsonRpc.sendRequest('/web/session/get_session_info', {}).then(fail, success);; 83 | $httpBackend.flush(); 84 | }); 85 | }); 86 | 87 | describe("login fail (wrong credentials)", function () { 88 | function success(reason) { 89 | expect(reason.title).toEqual("wrong_login"); 90 | } 91 | it("should reject wrong login v7", function () { 92 | set_version_info7(); 93 | 94 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 95 | jsonrpc:"2.0", 96 | id: null, 97 | result: { username: "admin", user_context: {}, uid: false, db: "db", company_id: null, session_id: "7a97f880c0374c02507b09e478cffb5be4df2ef8" } 98 | }); 99 | jsonRpc.login().then(fail, success); 100 | $httpBackend.flush(); 101 | }); 102 | 103 | it("wrong login v8", function () { 104 | set_version_info8(); 105 | $httpBackend.whenPOST('/web/session/authenticate').respond ({ 106 | jsonrpc:"2.0", 107 | id: null, 108 | result: { username: "admin", user_context: {}, uid: false, db: "db", company_id: null, session_id: "5699c4cd07f37e9fa8eaf5b63af8545020f25278" } 109 | }); 110 | jsonRpc.login().then(fail, success);; 111 | $httpBackend.flush(); 112 | }); 113 | }); 114 | 115 | describe("server issue on login", function () { 116 | function success(reason) { 117 | expect(reason.message).toEqual("HTTP Error"); 118 | } 119 | it("should handle 404", function () { 120 | set_version_info7(); 121 | $httpBackend.whenPOST('/web/session/authenticate').respond(404, "Not found"); 122 | jsonRpc.login().then(fail, success); 123 | $httpBackend.flush(); 124 | }); 125 | it("should handle odoo 404", function () { 126 | set_version_info8(); 127 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 128 | jsonrpc: "2.0", 129 | id: null, 130 | error: {message: "Odoo Server Error", code: 200, data: {debug: "Traceback (most recent call last):\n File \"/workspace/parts/odoo/openerp/http.py\", line 530, in _handle_exception\n return super(JsonRequest, self)._handle_exception(exception)\n File \"/workspace/parts/odoo/openerp/addons/base/ir/ir_http.py\", line 153, in _dispatch\n rule, arguments = self._find_handler(return_rule=True)\n File \"/workspace/parts/odoo/openerp/addons/base/ir/ir_http.py\", line 65, in _find_handler\n return self.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)\n File \"/home/ubuntu/.voodoo/shared/eggs/Werkzeug-0.10.4-py2.7.egg/werkzeug/routing.py\", line 1483, in match\n raise NotFound()\nNotFound: 404: Not Found\n", message: "", name: "werkzeug.exceptions.NotFound", arguments: []}}}); 131 | jsonRpc.login().then(fail, success); 132 | $httpBackend.flush(); 133 | }); 134 | it("should handle 500", function () { 135 | set_version_info7(); 136 | $httpBackend.whenPOST('/web/session/authenticate').respond(500, "Server error"); 137 | jsonRpc.login().then(fail, success); 138 | $httpBackend.flush(); 139 | }); 140 | }); 141 | 142 | 143 | describe("wrong db on login", function () { 144 | function success(reason) { 145 | console.log('message', reason); 146 | expect(reason.title).toEqual("database_not_found"); 147 | }/* 148 | it("should work with v7", function () { 149 | to be written 150 | });*/ 151 | it("should work with v8", function () { 152 | set_version_info8(); 153 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 154 | jsonrpc: "2.0", 155 | id: null, 156 | error: {message: "Odoo Server Error", code: 200, data: {debug: "\"Traceback (most recent call last): File \"/workspace/parts/odoo/openerp/http.py\", line 537, in _handle_exception return super(JsonRequest, self)._handle_exception(exception) File \"/workspace/parts/odoo/openerp/http.py\", line 574, in dispatch result = self._call_function(**self.params) File \"/workspace/parts/odoo/openerp/http.py\", line 310, in _call_function return checked_call(self.db, *args, **kwargs) File \"/workspace/parts/odoo/openerp/service/model.py\", line 113, in wrapper return f(dbname, *args, **kwargs) File \"/workspace/parts/odoo/openerp/http.py\", line 307, in checked_call return self.endpoint(*a, **kw) File \"/workspace/parts/odoo/openerp/http.py\", line 803, in __call__ return self.method(*args, **kw) File \"/workspace/parts/odoo/openerp/http.py\", line 403, in response_wrap response = f(*args, **kw) File \"/workspace/parts/odoo/addons/web/controllers/main.py\", line 796, in authenticate request.session.authenticate(db, login, password) File \"/workspace/parts/odoo/openerp/http.py\", line 956, in authenticate uid = dispatch_rpc('common', 'authenticate', [db, login, password, env]) File \"/workspace/parts/odoo/openerp/http.py\", line 115, in dispatch_rpc result = dispatch(method, params) File \"/workspace/parts/odoo/openerp/service/common.py\", line 26, in dispatch return fn(*params) File \"/workspace/parts/odoo/openerp/service/common.py\", line 37, in exp_authenticate res_users = openerp.registry(db)['res.users'] File \"/workspace/parts/odoo/openerp/__init__.py\", line 68, in registry return modules.registry.RegistryManager.get(database_name) File \"/workspace/parts/odoo/openerp/modules/registry.py\", line 339, in get update_module) File \"/workspace/parts/odoo/openerp/modules/registry.py\", line 356, in new registry = Registry(db_name) File \"/workspace/parts/odoo/openerp/modules/registry.py\", line 81, in __init__ cr = self.cursor() File \"/workspace/parts/odoo/openerp/modules/registry.py\", line 272, in cursor return self._db.cursor() File \"/workspace/parts/odoo/openerp/sql_db.py\", line 575, in cursor return Cursor(self.__pool, self.dbname, self.dsn, serialized=serialized) File \"/workspace/parts/odoo/openerp/sql_db.py\", line 181, in __init__ self._cnx = pool.borrow(dsn) File \"/workspace/parts/odoo/openerp/sql_db.py\", line 464, in _locked return fun(self, *args, **kwargs) File \"/workspace/parts/odoo/openerp/sql_db.py\", line 526, in borrow result = psycopg2.connect(dsn=dsn, connection_factory=PsycoConnection) File \"shared/eggs/psycopg2-2.6.1-py2.7-linux-x86_64.egg/psycopg2/__init__.py\", line 164, in connect conn = _connect(dsn, connection_factory=connection_factory, async=async)OperationalError: FATAL: database \"db\" does not exist", message: "FATAL: database \"db\" does not exist", name: "psycopg2.OperationalError", arguments: ["FATAL: database \"db\" does not exist"]}}}); 157 | jsonRpc.login().then(fail, success); 158 | $httpBackend.flush(); 159 | }); 160 | }); 161 | 162 | 163 | describe("login succeed", function () { 164 | function success(result) { 165 | expect(result.uid).toEqual(1); 166 | expect(result.username).toEqual('admin'); 167 | } 168 | it("should login with v7", function () { 169 | set_version_info7(); 170 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 171 | jsonrpc:"2.0", 172 | id: null, 173 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", company_id: null, session_id: "5699c4cd07f37e9fa8eaf5b63af8545020f25278" } 174 | }, { 175 | 'Set-Cookie': 'sid=e3a14bd882a848187e0611bbc51712a25db0fec7; Path=/' 176 | }); 177 | jsonRpc.login().then(success, fail);; 178 | $httpBackend.flush(); 179 | }); 180 | 181 | it("should login with v8", function () { 182 | set_version_info8(); 183 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 184 | jsonrpc:"2.0", 185 | id: null, 186 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", session_id: "5699c4cd07f37e9fa8eaf5b63af8545020f25278" } 187 | },{ 188 | 'Set-Cookie': 'session_id=7a97f880c0374c02507b09e478cffb5be4df2ef8; Expires=Thu, 23-Jul-2015 10:36:21 GMT; Max-Age=7776000; Path=/' 189 | }); 190 | jsonRpc.login().then(success, fail); 191 | $httpBackend.flush(); 192 | }); 193 | }); 194 | 195 | describe("v7/v8 specific", function () { 196 | var session_idToken = null; 197 | function success(result) { 198 | expect(result.uid).toEqual(1); 199 | } 200 | 201 | beforeEach(function () { 202 | session_idToken = "sendMePlease"+ Math.random(); 203 | 204 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 205 | jsonrpc:"2.0", 206 | id: null, 207 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", company_id: null, session_id: session_idToken } 208 | }); 209 | }); 210 | 211 | it("should take care of putting session_id in request.body — v7", function () { 212 | set_version_info7(); 213 | 214 | $httpBackend.whenPOST('/web/session/get_session_info', 215 | '{"jsonrpc":"2.0","method":"call","params":{"session_id":"'+ session_idToken +'"}}' 216 | ).respond({ 217 | jsonrpc:"2.0", 218 | id: null, 219 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", company_id: null, session_id: session_idToken } 220 | }); 221 | jsonRpc.login().then(function (a) { 222 | jsonRpc.sendRequest('/web/session/get_session_info', {}).then(success, fail); 223 | }); 224 | 225 | $httpBackend.flush(); 226 | }); 227 | 228 | 229 | it("should take care of NOT putting session_id in request.body - v8", function () { 230 | set_version_info8(); 231 | 232 | $httpBackend.whenPOST('/web/session/get_session_info', 233 | '{"jsonrpc":"2.0","method":"call","params":{}}' //session is_shouldn't be retransmitted 234 | ).respond({ 235 | jsonrpc:"2.0", 236 | id: null, 237 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", session_id: session_idToken } 238 | }); 239 | 240 | jsonRpc.login().then(function () { 241 | jsonRpc.sendRequest('/web/session/get_session_info', {}).then(success, fail); 242 | }); 243 | $httpBackend.flush(); 244 | }); 245 | }); 246 | 247 | describe("isLoggedIn ? ", function () { 248 | it("isLoggedIn with v7", function () { 249 | return success(); 250 | //continue here 251 | set_version_info7(); 252 | $httpBackend.whenPOST('/web/session/authenticate').respond({ 253 | jsonrpc:"2.0", 254 | id: null, 255 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", company_id: null, session_id: "5699c4cd07f37e9fa8eaf5b63af8545020f25278" } 256 | }, { 257 | 'Set-Cookie': 'sid=e3a14bd882a848187e0611bbc51712a25db0fec7; Path=/' 258 | }); 259 | 260 | $httpBackend.whenPOST('/web/session/get_session_info', 261 | '{"jsonrpc":"2.0","method":"call","params":{"session_id":"'+ session_idToken +'"}}' 262 | ).respond({ 263 | jsonrpc:"2.0", 264 | id: null, 265 | result: { username: "admin", user_context: { lang:"en_US", tz:"Europe/Brussels", uid:1}, uid: 1, db: "db", company_id: null, session_id: session_idToken } 266 | }); 267 | 268 | jsonRpc.isLoggedIn().then(function (r) { 269 | expect(r).toBe(false); //ensure not connected 270 | }, fail) 271 | .then(function () { 272 | return jsonRpc.login('db','admin','password'); 273 | }) //login 274 | .then(jsonRpc.isLoggedIn).then(function (r) { 275 | expect(r).toBe(true); //ensure connected 276 | }, fail); 277 | 278 | $httpBackend.flush(); 279 | }); 280 | }); 281 | }); --------------------------------------------------------------------------------