在前面测试通过odoo登录的功能,这次的问题重点是如何访问后台具体的业务类的接口呢?这次就以我们在odoo中安装的lunch模块为例,目标是获取lunch.alert的数据,如下图
具体过程接上次文章,继续完善OdooJsonRpc类的代码,首先是基础代码,这个是需要提供具体的model名称和具体方法,也是是一个很基础的方法,后台的odoo网站会利用类似C#反射的机制调用目标类的方法。
/** * Calls the method of that particular model * @param model Model name * @param method Method name of particular model * @param args Array of fields * @param kwargs Object */ public call(model: string, method: string, args: any, kwargs?: any) { kwargs = kwargs || {}; let params = { model: model, method: method, args: args, kwargs: kwargs == false ? {} : kwargs, context: this.getContext() }; return this.sendRequest("/web/dataset/call_kw", params); }
调用以上基础call方法的几个基本封装函数有如下几个,基本实现了对数据的CRUD功能,当然基本上是针对一条数据的操作
/** * Reads that perticular fields of that particular ID * @param model Model Name * @param id Id of that record which you want to read * @param mArgs Array of fields which you want to read of the particular id */ public read(model: string, id: number, mArgs: any): Promise<any> { let args = [ id, [mArgs] ] return this.call(model, 'read', args) } /** * Provide the name that you want to search * @param model Model name * @param name Name that you want to search */ public nameSearch(model: string, name: string): Promise<any> { let kwargs = { name: name, args: [], operator: "ilike", limit: 0 } return this.call(model, 'name_search', [], kwargs) } /** * Provide the IDs and you will get the names of that paticular IDs * @param model Model name * @param mArgs Array of IDs that you want to pass */ public nameGet(model: string, mArgs: any): Promise<any> { let args = [mArgs] return this.call(model, 'name_get', args) } /** * Create a new record * @param model Model name * @param mArgs Object of fields and value */ public createRecord(model: string, mArgs: any) { let args = [mArgs]; return this.call(model, "create", args, null) } /** * Delete the record of particular ID * @param model Model Name * @param id Id of record that you want to delete */ public deleteRecord(model: string, id: number) { let mArgs = [id] return this.call(model, "unlink", mArgs, null) } /** * Updates the record of particular ID * @param model Model Name * @param id Id of record that you want to update the. * @param mArgs The Object of fields and value that you want to update * (e.g) * let args = { * "name": "Mtfa" * } */ public updateRecord(model: string, id: number, mArgs: any) { let args = [ [id], mArgs ] return this.call(model, "write", args, null) }
针对单条数据的读取,这里还有另外一种方法,类似上面的read函数
/** * Loads all data of the paricular ID * @param model Model name * @param id Id of that particular data which you want to load */ public load(model: string, id: number): Promise<any> { let params = { model: model, id: id, fields: [], context: this.getContext() } return this.sendRequest("/web/dataset/load", params) }
还有对odoo多条件查询的情况,尤其还有分页的问题,这里也有这样一个很实用的分页查询的方法,这个在日后app的开发中会经常用到
/** * Fires query in particular model with fields and conditions * @param model Model name * @param domain Conditions that you want to fire on your query * (e.g) let domain = [ * ["id","=",11] * ] * @param fields Fields names which you want to bring from server * (e.g) let fields = [ * ["id","name","email"] * ] * @param limit limit of the record * @param offset * @param sort sorting order of data (e.g) let sort = "ascending" */ public searchRead(model: string, domain: any, fields: any, limit: number, offset: any, sort: string) { let params = { model: model, fields: fields, domain: domain, offset: offset, limit: limit, sort: sort, context: this.getContext() }; return this.sendRequest("/web/dataset/search_read", params); }
到此,访问odoo具体业务类的OdooJsonRpc类封装完毕,完整的代码在这里。接下来怎么获取我们的lunch.alert数据呢??
首先定义我们访问odoo业务类的成员变量,这里我们的model类是lunch.alert,需要获取的字段是message和id,其他参数我们暂使用默认的值
private lunchAlert = "lunch.alert"; private fields = ["message", "id"]; private domain = [] private sort = "" private limit = 0 private offset = 0 private items: Array<{ id: number, message: string }> = []
第二步是,与odoo后台网站交互获取数据的过程
private TestMyOdooModel() { this.odooRpc.searchRead(this.lunchAlert,this.domain, this.fields, this.limit, this.offset,this.sort) .then((lunchAlerts: any) => { let json = JSON.parse(lunchAlerts._body); if (!json.error) { let query = json["result"].records for (let i in query) { this.items.push ({ id: query[i].id, message: query[i].message }) } this.utils.presentAlert("Lunch Message", this.items[0].message,[{text: "Ok"}]) } else { this.utils.presentAlert("LunchAlert", "Parse lunch alert error",[{text: "Ok"}]) } }) }
如果正确获取数据,我们会弹出第一条数据对应的message.
编译打包成*.apk,在手机上测试,确实成功了,如下图所示。
由于这些都是测试阶段的代码,将来可能会改变,现阶段把这些代码全部放在这里。
测试部分的Page页面odooLogin.ts
import { Component } from '@angular/core'; import { AlertController, IonicPage, Loading, LoadingController, NavController, NavParams } from 'ionic-angular'; import { OdooJsonRpc } from '../../../../providers/baseService/Odoojsonrpc'; import { Utils } from "../../../../providers/baseService/Utils"; @IonicPage() @Component ({ selector: 'page-odooLogin', templateUrl: 'odooLogin.html', }) export class OdooLoginPage { private listForProtocol: Array<{ protocol: string}> = [] public perfectUrl: boolean = false public odooUrl public selectedProtocol private dbList: Array<{ dbName: string}> = [] private selectedDatabase private email private password constructor(public navCtrl: NavController, private alert: AlertController, public navParams: NavParams, private odooRpc: OdooJsonRpc, private loadingCtrl: LoadingController, private utils: Utils) { this.listForProtocol.push({ protocol: "http" }) this.listForProtocol.push({protocol: "https"}) } public checkUrl() { this.utils.presentLoading("Please Wait") this.odooRpc.init ({ odoo_server: this.selectedProtocol + "://" + this.odooUrl //http_auth: 'username:password' // optional }) this.odooRpc.getDbList().then((dbList: any) => { console.log(dbList) this.perfectUrl = true this.utils.dismissLoading() this.fillData(dbList) }). catch((err: any) => { console.log(err) this.utils.presentAlert("Error", "You Entered a wrong Odoo URL", [{ text: "Ok" }]) this.utils.dismissLoading() }); } public fillData(res: any) { let body = JSON.parse(res._body) let json = body['result']; this.dbList.length = 0; for (var key in json) { this.dbList.push({ dbName: json[key] }); } } private login() { this.utils.presentLoading("Please wait", 0, true) this.odooRpc.login(this.selectedDatabase, this.email, this.password) .then((res: any) => { let logiData: any = JSON.parse(res._body)["result"]; logiData.password = this.password localStorage.setItem("token", JSON.stringify(logiData)); //this.utils.dismissLoading() this.utils.presentAlert("Congratulation", "You login success",[{text: "Ok"}]) this.TestMyOdooModel() }). catch((err) => { this.utils.presentAlert("Error", "Username or password must be incorrect", [{ text: "Ok" }]) }); } private lunchAlert = "lunch.alert"; private fields = ["message", "id"]; private domain = [] private sort = "" private limit = 0 private offset = 0 private items: Array<{ id: number, message: string }> = [] private TestMyOdooModel() { this.odooRpc.searchRead(this.lunchAlert,this.domain, this.fields, this.limit, this.offset,this.sort) .then((lunchAlerts: any) => { let json = JSON.parse(lunchAlerts._body); if (!json.error) { let query = json["result"].records for (let i in query) { this.items.push ({ id: query[i].id, message: query[i].message }) } this.utils.presentAlert("Lunch Message", this.items[0].message,[{text: "Ok"}]) } else { this.utils.presentAlert("LunchAlert", "Parse lunch alert error",[{text: "Ok"}]) } }) } }
页面布局部分odooLogin.html
<ion-content class="background"> <ion-card> <ion-card-content> <div class="spacer" style="height: 10px;"></div> <ion-item> <ion-label style="color: #fff">Select Protocol</ion-label> <ion-select [(ngModel)]="selectedProtocol" style="color: #fff" name="dbNames"> <ion-option *ngFor="let item of listForProtocol" value="{{item.protocol}}">{{item.protocol}}</ion-option> </ion-select> </ion-item> <div class="spacer" style="height: 10px;"></div> <ion-item> <ion-input [(ngModel)]="odooUrl" type="url" name="odooUrl" placeholder="Odoo Url"></ion-input> </ion-item> <div class="spacer" style="height: 10px;"></div> <button ion-button block round outline color="light" (click)="checkUrl()" text-center> Check Url <ion-icon name="md-arrow-round-forward"></ion-icon> </button> <div [hidden]="!perfectUrl"> <form (ngSubmit)="login()" #registerForm="ngForm"> <div class="spacer" style="height: 10px;"></div> <ion-item> <ion-input type="email" [(ngModel)]="email" name="email" placeholder="Email" required></ion-input> </ion-item> <div class="spacer" style="height: 5px;"></div> <ion-item> <ion-input type="password" [(ngModel)]="password" name="pass" placeholder="Password" required></ion-input> </ion-item> <div class="spacer" style="height: 10px;"></div> <div class="spacer" style="height: 10px;"></div> <ion-item> <ion-label style="color: #fff">Select Database</ion-label> <ion-select [(ngModel)]="selectedDatabase" name="selectDatabase" style="color: #fff" required> <ion-option *ngFor="let item of dbList" value="{{item.dbName}}">{{item.dbName}}</ion-option> </ion-select> </ion-item> <button ion-button block round outline color="light" [disabled]="!registerForm.form.valid" (click)="signin()">Login</button> </form> </div> </ion-card-content> </ion-card> </ion-content
CSS效果部分odooLogin.scss
page-odooLogin { .background { height: 100%; 100%; background-size: cover !important; background-position: center center !important; background-image: url('../assets/imgs/mountain.jpg') } ion-card.card { margin-top: 30%; box-shadow: none; background: rgba(0, 0, 0, 0.5); border-radius: 5px; } a, p, ion-card-header.card-header { color: #fff!important; } .list > .item-block:first-child { border: medium none; } .item { margin-bottom: 10px; background: rgba(255, 255, 255, 0.5); border: medium none; .text-input, { color: #fff; } input::-moz-placeholder{ color: #fff!important; } input:-moz-placeholder { color: #fff!important; } *:-moz-placeholder{ color: #fff!important; } *:-ms-input-placeholder{ color: #fff!important; } *::-webkit-input-placeholder{ color: #fff!important; } } }
OdooJsonRpc部分Odoojsonrpc.ts
import { Injectable } from '@angular/core'; import 'rxjs/add/operator/toPromise'; import 'rxjs/Rx'; import { Headers, Http } from '@angular/http'; import { Utils } from './Utils'; @Injectable() export class OdooJsonRpc { private jsonRpcID: number = 0; private headers: Headers; private odoo_server: string; private http_auth: string; private list = "/web/database/list"; private get_list = "/web/database/get_list"; private jsonrpc = "/jsonrpc"; constructor(private http: Http, private utils: Utils) { this.http = http; } /** * Builds a request for odoo server * @param url Odoo Server URL * @param params Object */ private buildRequest(url: String, params: any) { this.jsonRpcID += 1; return JSON.stringify ({ jsonrpc: "2.0", method: "call", id: this.jsonRpcID, params: params, }); } /** * Returns the error message * @param response Error response from server */ public handleOdooErrors(response: any) { let err: string = response.error.data.message let msg = err.split(" ") let errMsg = msg[0] this.utils.presentAlert("Error", errMsg, [{ text: "Ok", role: "cancel" }]) } /** * Handles HTTP errors */ public handleHttpErrors(error: any) { return Promise.reject(error.message || error); } /** * Sends a JSON request to the odoo server * @param url Url of odoo * @param params Object */ public sendRequest(url: string, params: Object): Promise<any> { let options = this.buildRequest(url, params); this.headers = new Headers({ 'Content-Type': 'application/json; charset=utf-8', }); let result = this.http.post(this.odoo_server + url, options, { headers: this.headers }) .toPromise() return result; } public init(configs: any) { this.odoo_server = configs.odoo_server; this.http_auth = configs.http_auth || null; } public setOdooServer(odoo_server: string) { this.odoo_server = odoo_server; } public setHttpAuth(http_auth: string) { this.http_auth = http_auth; } /** * Gets the server info */ public getServerInfo() { return this.sendRequest("/web/webclient/version_info", {}); } /** * Gets the session info */ public getSessionInfo() { return this.sendRequest("/web/session/get_session_info", {}); } /** * Gets the Odoo Server Version Number */ public getServerVersionNumber(): Promise<number> { return this.getServerInfo().then((res: any): Promise<number> => { return new Promise<number>((resolve) => { resolve(JSON.parse(res._body)["result"]["server_version_info"][0]); }); }); } /** * Get the database list */ public getDbList(): Promise<string> { let dbParams = { context: {} } return this.getServerVersionNumber().then((data: number) => { if (data <= 8) { return this.sendRequest(this.get_list, dbParams); } else if (data == 9) { return this.sendRequest(this.jsonrpc, dbParams); } else { return this.sendRequest(this.list, dbParams); } }) } /** * Returns all modules that are installed in your database */ public modules(): Promise<string> { let params = { context: {} } return this.sendRequest("/web/session/modules", params) } /** * Login to the database * @param db Database name of odoo * @param login Username * @param password password */ public login(db: string, login: string, password: string) { let params = { db: db, login: login, password: password, base_location: this.odoo_server, context: {} }; return this.sendRequest("/web/session/authenticate", params) } /** * Check whether the session is live or not */ public check(): Promise<string> { let params = { context: this.getContext() } return this.sendRequest("/web/session/check", params) } /** * Destroy the session */ public destroy() { let params = { context: {} } return this.sendRequest("/web/session/destroy", params) } /** * Fires query in particular model with fields and conditions * @param model Model name * @param domain Conditions that you want to fire on your query * (e.g) let domain = [ * ["id","=",11] * ] * @param fields Fields names which you want to bring from server * (e.g) let fields = [ * ["id","name","email"] * ] * @param limit limit of the record * @param offset * @param sort sorting order of data (e.g) let sort = "ascending" */ public searchRead(model: string, domain: any, fields: any, limit: number, offset: any, sort: string) { let params = { model: model, fields: fields, domain: domain, offset: offset, limit: limit, sort: sort, context: this.getContext() }; return this.sendRequest("/web/dataset/search_read", params); } /** * Calls the method of that particular model * @param model Model name * @param method Method name of particular model * @param args Array of fields * @param kwargs Object */ public call(model: string, method: string, args: any, kwargs?: any) { kwargs = kwargs || {}; let params = { model: model, method: method, args: args, kwargs: kwargs == false ? {} : kwargs, context: this.getContext() }; return this.sendRequest("/web/dataset/call_kw", params); } /** * Reads that perticular fields of that particular ID * @param model Model Name * @param id Id of that record which you want to read * @param mArgs Array of fields which you want to read of the particular id */ public read(model: string, id: number, mArgs: any): Promise<any> { let args = [ id, [mArgs] ] return this.call(model, 'read', args) } /** * Loads all data of the paricular ID * @param model Model name * @param id Id of that particular data which you want to load */ public load(model: string, id: number): Promise<any> { let params = { model: model, id: id, fields: [], context: this.getContext() } return this.sendRequest("/web/dataset/load", params) } /** * Provide the name that you want to search * @param model Model name * @param name Name that you want to search */ public nameSearch(model: string, name: string): Promise<any> { let kwargs = { name: name, args: [], operator: "ilike", limit: 0 } return this.call(model, 'name_search', [], kwargs) } /** * Provide the IDs and you will get the names of that paticular IDs * @param model Model name * @param mArgs Array of IDs that you want to pass */ public nameGet(model: string, mArgs: any): Promise<any> { let args = [mArgs] return this.call(model, 'name_get', args) } /** * Create a new record * @param model Model name * @param mArgs Object of fields and value */ public createRecord(model: string, mArgs: any) { let args = [mArgs]; return this.call(model, "create", args, null) } /** * Delete the record of particular ID * @param model Model Name * @param id Id of record that you want to delete */ public deleteRecord(model: string, id: number) { let mArgs = [id] return this.call(model, "unlink", mArgs, null) } /** * Updates the record of particular ID * @param model Model Name * @param id Id of record that you want to update the. * @param mArgs The Object of fields and value that you want to update * (e.g) * let args = { * "name": "Mtfa" * } */ public updateRecord(model: string, id: number, mArgs: any) { let args = [ [id], mArgs ] return this.call(model, "write", args, null) } /** * Get the User Context from the response of odoo server */ private getContext() { let response = localStorage.getItem("token"); let jsonData = JSON.parse(response); let context = jsonData["user_context"]; return context; } }
通用类Utils.ts
import { Injectable } from "@angular/core"; import { AlertController, Loading, LoadingController, Toast, ToastController, ActionSheetController } from "ionic-angular"; @Injectable() export class Utils { private loading: Loading constructor(private alrtCtrl: AlertController, private loadingCtrl: LoadingController, private toastCtrl: ToastController, private actionSheetCtrl: ActionSheetController) { } public presentAlert(title: string, message: string, buttons: [{}], subtitle?: string, enableBackdropDismiss?: boolean, inputs?: [{}]): void { let alrt = this.alrtCtrl.create ({ title: title, subTitle: subtitle, message: message, buttons: buttons, enableBackdropDismiss: enableBackdropDismiss, inputs: inputs }) alrt.present() } public presentToast(message: string, duration?: number, dissmissOnPageChange?: boolean, position?: string, showCloseButton?: boolean, closeButtonText?: string): void { let toast = this.toastCtrl.create ({ message: message, position: position, dismissOnPageChange: dissmissOnPageChange, duration: duration, showCloseButton: showCloseButton, closeButtonText: closeButtonText }) toast.present() } public presentLoading(content: string, duration?: number, dissmissOnPageChange?: boolean, enableBackDropDismiss?: boolean, showBackDrop?: boolean, spinner?: string): void { this.loading = this.loadingCtrl.create ({ content: content, dismissOnPageChange: dissmissOnPageChange, duration: duration, enableBackdropDismiss: enableBackDropDismiss, showBackdrop: showBackDrop, spinner: spinner }) this.loading.present() } public dismissLoading(): void { this.loading.dismiss() } public presentActionSheet(buttons: [{}], title: string, subtitle?: string, enableBackdropDismiss?: boolean): void { let actionCtrl = this.actionSheetCtrl.create ({ buttons: buttons, subTitle: subtitle, title: title, enableBackdropDismiss: enableBackdropDismiss }) actionCtrl.present() } }