[ADD] Model API, reimplement DataSet/DataSetSearch on top of it (as much as possible)
TODO: traversal state API, removing even more method (e.g. completely remove DataSet.call in the Python API) bzr revid: xmo@openerp.com-20120227135626-yxqh0gc6jwrdkshs
This commit is contained in:
parent
b8efd17bbb
commit
e6f5d4c211
|
@ -819,11 +819,6 @@ class Menu(openerpweb.Controller):
|
|||
class DataSet(openerpweb.Controller):
|
||||
_cp_path = "/web/dataset"
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def fields(self, req, model):
|
||||
return {'fields': req.session.model(model).fields_get(False,
|
||||
req.session.eval_context(req.context))}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None):
|
||||
return self.do_search_read(req, model, fields, offset, limit, domain, sort)
|
||||
|
@ -859,7 +854,6 @@ class DataSet(openerpweb.Controller):
|
|||
if fields and fields == ['id']:
|
||||
# shortcut read if we only want the ids
|
||||
return {
|
||||
'ids': ids,
|
||||
'length': length,
|
||||
'records': [{'id': id} for id in ids]
|
||||
}
|
||||
|
@ -867,46 +861,10 @@ class DataSet(openerpweb.Controller):
|
|||
records = Model.read(ids, fields or False, context)
|
||||
records.sort(key=lambda obj: ids.index(obj['id']))
|
||||
return {
|
||||
'ids': ids,
|
||||
'length': length,
|
||||
'records': records
|
||||
}
|
||||
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def read(self, req, model, ids, fields=False):
|
||||
return self.do_search_read(req, model, ids, fields)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get(self, req, model, ids, fields=False):
|
||||
return self.do_get(req, model, ids, fields)
|
||||
|
||||
def do_get(self, req, model, ids, fields=False):
|
||||
""" Fetches and returns the records of the model ``model`` whose ids
|
||||
are in ``ids``.
|
||||
|
||||
The results are in the same order as the inputs, but elements may be
|
||||
missing (if there is no record left for the id)
|
||||
|
||||
:param req: the JSON-RPC2 request object
|
||||
:type req: openerpweb.JsonRequest
|
||||
:param model: the model to read from
|
||||
:type model: str
|
||||
:param ids: a list of identifiers
|
||||
:type ids: list
|
||||
:param fields: a list of fields to fetch, ``False`` or empty to fetch
|
||||
all fields in the model
|
||||
:type fields: list | False
|
||||
:returns: a list of records, in the same order as the list of ids
|
||||
:rtype: list
|
||||
"""
|
||||
Model = req.session.model(model)
|
||||
records = Model.read(ids, fields, req.session.eval_context(req.context))
|
||||
|
||||
record_map = dict((record['id'], record) for record in records)
|
||||
|
||||
return [record_map[id] for id in ids if record_map.get(id)]
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def load(self, req, model, id, fields):
|
||||
m = req.session.model(model)
|
||||
|
@ -916,23 +874,6 @@ class DataSet(openerpweb.Controller):
|
|||
value = r[0]
|
||||
return {'value': value}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def create(self, req, model, data):
|
||||
m = req.session.model(model)
|
||||
r = m.create(data, req.session.eval_context(req.context))
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def save(self, req, model, id, data):
|
||||
m = req.session.model(model)
|
||||
r = m.write([id], data, req.session.eval_context(req.context))
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def unlink(self, req, model, ids=()):
|
||||
Model = req.session.model(model)
|
||||
return Model.unlink(ids, req.session.eval_context(req.context))
|
||||
|
||||
def call_common(self, req, model, method, args, domain_id=None, context_id=None):
|
||||
has_domain = domain_id is not None and domain_id < len(args)
|
||||
has_context = context_id is not None and context_id < len(args)
|
||||
|
@ -1008,19 +949,7 @@ class DataSet(openerpweb.Controller):
|
|||
|
||||
@openerpweb.jsonrequest
|
||||
def exec_workflow(self, req, model, id, signal):
|
||||
r = req.session.exec_workflow(model, id, signal)
|
||||
return {'result': r}
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def default_get(self, req, model, fields):
|
||||
Model = req.session.model(model)
|
||||
return Model.default_get(fields, req.session.eval_context(req.context))
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def name_search(self, req, model, search_str, domain=[], context={}):
|
||||
m = req.session.model(model)
|
||||
r = m.name_search(search_str+'%', domain, '=ilike', context)
|
||||
return {'result': r}
|
||||
return req.session.exec_workflow(model, id, signal)
|
||||
|
||||
class DataGroup(openerpweb.Controller):
|
||||
_cp_path = "/web/group"
|
||||
|
|
|
@ -452,7 +452,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
|
|||
* setting the correct session id and session context in the parameter
|
||||
* objects
|
||||
*
|
||||
* @param {String} url RPC endpoint
|
||||
* @param {Object} url RPC endpoint
|
||||
* @param {Object} params call parameters
|
||||
* @param {Function} success_callback function to execute on RPC call success
|
||||
* @param {Function} error_callback function to execute on RPC call failure
|
||||
|
|
|
@ -9,15 +9,167 @@ openerp.web.data = function(openerp) {
|
|||
* @returns {String} SQL-like sorting string (``ORDER BY``) clause
|
||||
*/
|
||||
openerp.web.serialize_sort = function (criterion) {
|
||||
return _.map(criterion,
|
||||
function (criteria) {
|
||||
if (criteria[0] === '-') {
|
||||
return criteria.slice(1) + ' DESC';
|
||||
}
|
||||
return criteria + ' ASC';
|
||||
}).join(', ');
|
||||
return _.map(criterion,
|
||||
function (criteria) {
|
||||
if (criteria[0] === '-') {
|
||||
return criteria.slice(1) + ' DESC';
|
||||
}
|
||||
return criteria + ' ASC';
|
||||
}).join(', ');
|
||||
};
|
||||
|
||||
openerp.web.Query = openerp.web.Class.extend({
|
||||
init: function (model, fields) {
|
||||
this._model = model;
|
||||
this._fields = fields;
|
||||
this._filter = [];
|
||||
this._context = {};
|
||||
this._limit = false;
|
||||
this._offset = 0;
|
||||
this._order_by = [];
|
||||
},
|
||||
clone: function (to_set) {
|
||||
to_set = to_set || {};
|
||||
var q = new openerp.web.Query(this._model, this._fields);
|
||||
q._context = this._context;
|
||||
q._filter = this._filter;
|
||||
q._limit = this._limit;
|
||||
q._offset = this._offset;
|
||||
q._order_by = this._order_by;
|
||||
|
||||
for(var key in to_set) {
|
||||
if (!to_set.hasOwnProperty(key)) { continue; }
|
||||
switch(key) {
|
||||
case 'filter':
|
||||
q._filter = new openerp.web.CompoundDomain(
|
||||
q._filter, to_set.filter);
|
||||
break;
|
||||
case 'context':
|
||||
q._context = new openerp.web.CompoundContext(
|
||||
q._context, to_set.context);
|
||||
break;
|
||||
case 'limit':
|
||||
case 'offset':
|
||||
case 'order_by':
|
||||
q['_' + key] = to_set[key];
|
||||
}
|
||||
}
|
||||
return q;
|
||||
},
|
||||
_execute: function () {
|
||||
var self = this;
|
||||
return openerp.connection.rpc('/web/dataset/search_read', {
|
||||
model: this._model.name,
|
||||
fields: this._fields || false,
|
||||
domain: this._model.domain(this._filter),
|
||||
context: this._model.context(this._context),
|
||||
offset: this._offset,
|
||||
limit: this._limit,
|
||||
sort: openerp.web.serialize_sort(this._order_by)
|
||||
}).pipe(function (results) {
|
||||
self._count = results.length;
|
||||
return results.records;
|
||||
}, null);
|
||||
},
|
||||
first: function () {
|
||||
var self = this;
|
||||
return this.clone({limit: 1})._execute().pipe(function (records) {
|
||||
delete self._count;
|
||||
if (records.length) { return records[0]; }
|
||||
return null;
|
||||
});
|
||||
},
|
||||
all: function () {
|
||||
return this._execute();
|
||||
},
|
||||
context: function (context) {
|
||||
if (!context) { return this; }
|
||||
return this.clone({context: context});
|
||||
},
|
||||
count: function () {
|
||||
if (this._count) { return $.when(this._count); }
|
||||
return this.model.call(
|
||||
'search_count', [this._filter], {
|
||||
context: this._model.context(this._context)});
|
||||
},
|
||||
filter: function (domain) {
|
||||
if (!domain) { return this; }
|
||||
return this.clone({filter: domain});
|
||||
},
|
||||
limit: function (limit) {
|
||||
return this.clone({limit: limit});
|
||||
},
|
||||
offset: function (offset) {
|
||||
return this.clone({offset: offset});
|
||||
},
|
||||
order_by: function () {
|
||||
if (arguments.length === 0) { return this; }
|
||||
return this.clone({order_by: _.toArray(arguments)});
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.Model = openerp.web.CallbackEnabled.extend({
|
||||
init: function (model_name, context, domain) {
|
||||
this._super();
|
||||
this.name = model_name;
|
||||
this._context = context || {};
|
||||
this._domain = domain || [];
|
||||
},
|
||||
/*
|
||||
* @deprecated does not allow to specify kwargs, directly use call() instead
|
||||
*/
|
||||
get_func: function (method_name) {
|
||||
var self = this;
|
||||
return function () {
|
||||
return self.call(method_name, _.toArray(arguments));
|
||||
};
|
||||
},
|
||||
call: function (method, args, kwargs) {
|
||||
args = args || [];
|
||||
kwargs = kwargs || {};
|
||||
return openerp.connection.rpc('/web/dataset/call_kw', {
|
||||
model: this.name,
|
||||
method: method,
|
||||
args: args,
|
||||
kwargs: kwargs
|
||||
});
|
||||
},
|
||||
exec_workflow: function (id, signal) {
|
||||
return openerp.connection.rpc('/web/dataset/exec_workflow', {
|
||||
model: this.name,
|
||||
id: id,
|
||||
signal: signal
|
||||
});
|
||||
},
|
||||
query: function (fields) {
|
||||
return new openerp.web.Query(this, fields);
|
||||
},
|
||||
domain: function (domain) {
|
||||
return new openerp.web.CompoundDomain(
|
||||
this._domain, domain || []);
|
||||
},
|
||||
context: function (context) {
|
||||
return new openerp.web.CompoundContext(
|
||||
openerp.connection.user_context, this._context, context || {});
|
||||
},
|
||||
/**
|
||||
* Button action caller, needs to perform cleanup if an action is returned
|
||||
* from the button (parsing of context and domain, and fixup of the views
|
||||
* collection for act_window actions)
|
||||
*
|
||||
* FIXME: remove when evaluator integrated
|
||||
*/
|
||||
call_button: function (method, args) {
|
||||
return this.rpc('/web/dataset/call_button', {
|
||||
model: this.model,
|
||||
method: method,
|
||||
domain_id: null,
|
||||
context_id: args.length - 1,
|
||||
args: args || []
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{
|
||||
/**
|
||||
* Management interface between views and grouped collections of OpenERP
|
||||
|
@ -249,6 +401,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
this.context = context || {};
|
||||
this.index = null;
|
||||
this._sort = [];
|
||||
this._model = new openerp.web.Model(model, context);
|
||||
},
|
||||
previous: function () {
|
||||
this.index -= 1;
|
||||
|
@ -296,13 +449,10 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
read_ids: function (ids, fields, options) {
|
||||
var options = options || {};
|
||||
return this.rpc('/web/dataset/get', {
|
||||
model: this.model,
|
||||
ids: ids,
|
||||
fields: fields,
|
||||
context: this.get_context(options.context)
|
||||
});
|
||||
// TODO: reorder results to match ids list
|
||||
return this._model.call('read',
|
||||
[ids, fields || false],
|
||||
{context: this._model.context(options.context)});
|
||||
},
|
||||
/**
|
||||
* Read a slice of the records represented by this DataSet, based on its
|
||||
|
@ -315,7 +465,14 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
read_slice: function (fields, options) {
|
||||
return null;
|
||||
var self = this;
|
||||
options = options || {};
|
||||
return this._model.query(fields)
|
||||
.limit(options.limit || false)
|
||||
.offset(options.offset || 0)
|
||||
.all().then(function (records) {
|
||||
self.ids = _(records).pluck('id');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Reads the current dataset record (from its index)
|
||||
|
@ -325,18 +482,13 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
read_index: function (fields, options) {
|
||||
var def = $.Deferred();
|
||||
if (_.isEmpty(this.ids)) {
|
||||
def.reject();
|
||||
} else {
|
||||
fields = fields || false;
|
||||
this.read_ids([this.ids[this.index]], fields, options).then(function(records) {
|
||||
def.resolve(records[0]);
|
||||
}, function() {
|
||||
def.reject.apply(def, arguments);
|
||||
});
|
||||
}
|
||||
return def.promise();
|
||||
options = options || {};
|
||||
// not very good
|
||||
return this._model.query(fields)
|
||||
.offset(this.index).first().pipe(function (record) {
|
||||
if (!record) { return $.Deferred().reject().promise(); }
|
||||
return record;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Reads default values for the current model
|
||||
|
@ -346,12 +498,9 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
default_get: function(fields, options) {
|
||||
var options = options || {};
|
||||
return this.rpc('/web/dataset/default_get', {
|
||||
model: this.model,
|
||||
fields: fields,
|
||||
context: this.get_context(options.context)
|
||||
});
|
||||
options = options || {};
|
||||
return this._model.call('default_get',
|
||||
[fields], {context: this._model.context(options.context)});
|
||||
},
|
||||
/**
|
||||
* Creates a new record in db
|
||||
|
@ -362,11 +511,10 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
create: function(data, callback, error_callback) {
|
||||
return this.rpc('/web/dataset/create', {
|
||||
model: this.model,
|
||||
data: data,
|
||||
context: this.get_context()
|
||||
}, callback, error_callback);
|
||||
return this._model.call('create',
|
||||
[data], {context: this._model.context()})
|
||||
.pipe(function (r) { return {result: r}; })
|
||||
.then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* Saves the provided data in an existing db record
|
||||
|
@ -379,12 +527,10 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
*/
|
||||
write: function (id, data, options, callback, error_callback) {
|
||||
options = options || {};
|
||||
return this.rpc('/web/dataset/save', {
|
||||
model: this.model,
|
||||
id: id,
|
||||
data: data,
|
||||
context: this.get_context(options.context)
|
||||
}, callback, error_callback);
|
||||
return this._model.call('write',
|
||||
[[id], data], {context: this._model.context(options.context)})
|
||||
.pipe(function (r) { return {result: r}})
|
||||
.then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* Deletes an existing record from the database
|
||||
|
@ -394,9 +540,9 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @param {Function} error_callback function called in case of deletion error
|
||||
*/
|
||||
unlink: function(ids, callback, error_callback) {
|
||||
var self = this;
|
||||
return this.call_and_eval("unlink", [ids, this.get_context()], null, 1,
|
||||
callback, error_callback);
|
||||
return this._model.call('unlink',
|
||||
[ids], {context: this._model.context()})
|
||||
.then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* Calls an arbitrary RPC method
|
||||
|
@ -408,11 +554,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
call: function (method, args, callback, error_callback) {
|
||||
return this.rpc('/web/dataset/call', {
|
||||
model: this.model,
|
||||
method: method,
|
||||
args: args || []
|
||||
}, callback, error_callback);
|
||||
return this._model.call(method, args).then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* Calls an arbitrary method, with more crazy
|
||||
|
@ -446,13 +588,8 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
call_button: function (method, args, callback, error_callback) {
|
||||
return this.rpc('/web/dataset/call_button', {
|
||||
model: this.model,
|
||||
method: method,
|
||||
domain_id: null,
|
||||
context_id: args.length - 1,
|
||||
args: args || []
|
||||
}, callback, error_callback);
|
||||
return this._model.call_button(method, args)
|
||||
.then(callback, error_callback);
|
||||
},
|
||||
/**
|
||||
* Fetches the "readable name" for records, based on intrinsic rules
|
||||
|
@ -462,7 +599,9 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
name_get: function(ids, callback) {
|
||||
return this.call_and_eval('name_get', [ids, this.get_context()], null, 1, callback);
|
||||
return this._model.call('name_get',
|
||||
[ids], {context: this._model.context()})
|
||||
.then(callback);
|
||||
},
|
||||
/**
|
||||
*
|
||||
|
@ -474,29 +613,30 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
|||
* @returns {$.Deferred}
|
||||
*/
|
||||
name_search: function (name, domain, operator, limit, callback) {
|
||||
return this.call_and_eval('name_search',
|
||||
[name || '', domain || false, operator || 'ilike', this.get_context(), limit || 0],
|
||||
1, 3, callback);
|
||||
return this._model.call('name_search', [], {
|
||||
name: name || '',
|
||||
args: domain || false,
|
||||
operator: operator || 'ilike',
|
||||
context: this._model.context(),
|
||||
limit: limit || 0
|
||||
}).then(callback);
|
||||
},
|
||||
/**
|
||||
* @param name
|
||||
* @param callback
|
||||
*/
|
||||
name_create: function(name, callback) {
|
||||
return this.call_and_eval('name_create', [name, this.get_context()], null, 1, callback);
|
||||
return this._model.call('name_create',
|
||||
[name], {context: this._model.context()})
|
||||
.then(callback);
|
||||
},
|
||||
exec_workflow: function (id, signal, callback) {
|
||||
return this.rpc('/web/dataset/exec_workflow', {
|
||||
model: this.model,
|
||||
id: id,
|
||||
signal: signal
|
||||
}, callback);
|
||||
return this._model.exec_workflow(id, signal)
|
||||
.pipe(function (result) { return { result: result }; })
|
||||
.then(callback);
|
||||
},
|
||||
get_context: function(request_context) {
|
||||
if (request_context) {
|
||||
return new openerp.web.CompoundContext(this.context, request_context);
|
||||
}
|
||||
return this.context;
|
||||
return this._model.context(request_context);
|
||||
},
|
||||
/**
|
||||
* Reads or changes sort criteria on the dataset.
|
||||
|
@ -573,11 +713,9 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
|
|||
init: function(parent, model, context, domain) {
|
||||
this._super(parent, model, context);
|
||||
this.domain = domain || [];
|
||||
this.offset = 0;
|
||||
this._length;
|
||||
// subset records[offset:offset+limit]
|
||||
// is it necessary ?
|
||||
this._length = null;
|
||||
this.ids = [];
|
||||
this._model = new openerp.web.Model(model, context, domain);
|
||||
},
|
||||
/**
|
||||
* Read a slice of the records represented by this DataSet, based on its
|
||||
|
@ -594,27 +732,20 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
|
|||
read_slice: function (fields, options) {
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var offset = options.offset || 0;
|
||||
return this.rpc('/web/dataset/search_read', {
|
||||
model: this.model,
|
||||
fields: fields || false,
|
||||
domain: this.get_domain(options.domain),
|
||||
context: this.get_context(options.context),
|
||||
sort: this.sort(),
|
||||
offset: offset,
|
||||
limit: options.limit || false
|
||||
}).pipe(function (result) {
|
||||
self.ids = result.ids;
|
||||
self.offset = offset;
|
||||
self._length = result.length;
|
||||
return result.records;
|
||||
var q = this._model.query(fields || false)
|
||||
.filter(options.domain)
|
||||
.context(options.context)
|
||||
.offset(options.offset || 0)
|
||||
.limit(options.limit || false);
|
||||
q = q.order_by.apply(q, this._sort);
|
||||
return q.all().then(function (records) {
|
||||
// FIXME: not sure about that one, *could* have discarded count
|
||||
q.count().then(function (count) { this._length = count; });
|
||||
self.ids = _(records).pluck('id');
|
||||
});
|
||||
},
|
||||
get_domain: function (other_domain) {
|
||||
if (other_domain) {
|
||||
return new openerp.web.CompoundDomain(this.domain, other_domain);
|
||||
}
|
||||
return this.domain;
|
||||
this._model.domain(other_domain);
|
||||
},
|
||||
unlink: function(ids, callback, error_callback) {
|
||||
var self = this;
|
||||
|
@ -841,34 +972,6 @@ openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({
|
|||
on_unlink: function(ids) {}
|
||||
});
|
||||
|
||||
openerp.web.Model = openerp.web.CallbackEnabled.extend({
|
||||
init: function(model_name) {
|
||||
this._super();
|
||||
this.model_name = model_name;
|
||||
},
|
||||
rpc: function() {
|
||||
var c = openerp.connection;
|
||||
return c.rpc.apply(c, arguments);
|
||||
},
|
||||
/*
|
||||
* deprecated because it does not allow to specify kwargs, directly use call() instead
|
||||
*/
|
||||
get_func: function(method_name) {
|
||||
var self = this;
|
||||
return function() {
|
||||
return self.call(method_name, _.toArray(arguments), {});
|
||||
};
|
||||
},
|
||||
call: function (method, args, kwargs) {
|
||||
return this.rpc('/web/dataset/call_kw', {
|
||||
model: this.model_name,
|
||||
method: method,
|
||||
args: args,
|
||||
kwargs: kwargs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.CompoundContext = openerp.web.Class.extend({
|
||||
init: function () {
|
||||
this.__ref = "compound_context";
|
||||
|
|
|
@ -11,13 +11,15 @@ $(document).ready(function () {
|
|||
ds.ids = [10, 20, 30, 40, 50];
|
||||
ds.index = 2;
|
||||
t.expect(ds.read_index(['a', 'b', 'c']), function (result) {
|
||||
strictEqual(result.method, 'read');
|
||||
strictEqual(result.method, 'search');
|
||||
strictEqual(result.model, 'some.model');
|
||||
|
||||
strictEqual(result.args.length, 3);
|
||||
deepEqual(result.args[0], [30]);
|
||||
deepEqual(result.args[1], ['a', 'b', 'c']);
|
||||
deepEqual(result.args[2], context_());
|
||||
strictEqual(result.args.length, 5);
|
||||
deepEqual(result.args[0], []);
|
||||
strictEqual(result.args[1], 2);
|
||||
strictEqual(result.args[2], 1);
|
||||
strictEqual(result.args[3], false);
|
||||
deepEqual(result.args[4], context_());
|
||||
|
||||
ok(_.isEmpty(result.kwargs));
|
||||
});
|
||||
|
@ -29,13 +31,12 @@ $(document).ready(function () {
|
|||
strictEqual(result.method, 'default_get');
|
||||
strictEqual(result.model, 'some.model');
|
||||
|
||||
strictEqual(result.args.length, 2);
|
||||
strictEqual(result.args.length, 1);
|
||||
deepEqual(result.args[0], ['a', 'b', 'c']);
|
||||
console.log(result.args[1]);
|
||||
console.log(context_({foo: 'bar'}));
|
||||
deepEqual(result.args[1], context_({foo: 'bar'}));
|
||||
|
||||
ok(_.isEmpty(result.kwargs));
|
||||
deepEqual(result.kwargs, {
|
||||
context: context_({foo: 'bar'})
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('create', function (openerp) {
|
||||
|
@ -43,11 +44,12 @@ $(document).ready(function () {
|
|||
t.expect(ds.create({foo: 1, bar: 2}), function (r) {
|
||||
strictEqual(r.method, 'create');
|
||||
|
||||
strictEqual(r.args.length, 2);
|
||||
strictEqual(r.args.length, 1);
|
||||
deepEqual(r.args[0], {foo: 1, bar: 2});
|
||||
deepEqual(r.args[1], context_());
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
deepEqual(r.kwargs, {
|
||||
context: context_()
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('write', function (openerp) {
|
||||
|
@ -55,12 +57,12 @@ $(document).ready(function () {
|
|||
t.expect(ds.write(42, {foo: 1}), function (r) {
|
||||
strictEqual(r.method, 'write');
|
||||
|
||||
strictEqual(r.args.length, 3);
|
||||
strictEqual(r.args.length, 2);
|
||||
deepEqual(r.args[0], [42]);
|
||||
deepEqual(r.args[1], {foo: 1});
|
||||
deepEqual(r.args[2], context_());
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
deepEqual(r.kwargs, {
|
||||
context: context_()
|
||||
});
|
||||
});
|
||||
// FIXME: can't run multiple sessions in the same test(), fucks everything up
|
||||
// t.expect(ds.write(42, {foo: 1}, { context: {lang: 'bob'} }), function (r) {
|
||||
|
@ -73,11 +75,11 @@ $(document).ready(function () {
|
|||
t.expect(ds.unlink([42]), function (r) {
|
||||
strictEqual(r.method, 'unlink');
|
||||
|
||||
strictEqual(r.args.length, 2);
|
||||
strictEqual(r.args.length, 1);
|
||||
deepEqual(r.args[0], [42]);
|
||||
deepEqual(r.args[1], context_());
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
deepEqual(r.kwargs, {
|
||||
context: context_()
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('call', function (openerp) {
|
||||
|
@ -96,11 +98,11 @@ $(document).ready(function () {
|
|||
t.expect(ds.name_get([1, 2], null), function (r) {
|
||||
strictEqual(r.method, 'name_get');
|
||||
|
||||
strictEqual(r.args.length, 2);
|
||||
strictEqual(r.args.length, 1);
|
||||
deepEqual(r.args[0], [1, 2]);
|
||||
deepEqual(r.args[1], context_());
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
deepEqual(r.kwargs, {
|
||||
context: context_()
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('name_search, name', function (openerp) {
|
||||
|
@ -108,15 +110,14 @@ $(document).ready(function () {
|
|||
t.expect(ds.name_search('bob'), function (r) {
|
||||
strictEqual(r.method, 'name_search');
|
||||
|
||||
strictEqual(r.args.length, 5);
|
||||
strictEqual(r.args[0], 'bob');
|
||||
// domain
|
||||
deepEqual(r.args[1], []);
|
||||
strictEqual(r.args[2], 'ilike');
|
||||
deepEqual(r.args[3], context_());
|
||||
strictEqual(r.args[4], 0);
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
strictEqual(r.args.length, 0);
|
||||
deepEqual(r.kwargs, {
|
||||
name: 'bob',
|
||||
args: false,
|
||||
operator: 'ilike',
|
||||
context: context_(),
|
||||
limit: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('name_search, domain & operator', function (openerp) {
|
||||
|
@ -124,16 +125,14 @@ $(document).ready(function () {
|
|||
t.expect(ds.name_search(0, [['foo', '=', 3]], 'someop'), function (r) {
|
||||
strictEqual(r.method, 'name_search');
|
||||
|
||||
strictEqual(r.args.length, 5);
|
||||
strictEqual(r.args[0], '');
|
||||
// domain
|
||||
deepEqual(r.args[1], [['foo', '=', 3]]);
|
||||
strictEqual(r.args[2], 'someop');
|
||||
deepEqual(r.args[3], context_());
|
||||
// limit
|
||||
strictEqual(r.args[4], 0);
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
strictEqual(r.args.length, 0);
|
||||
deepEqual(r.kwargs, {
|
||||
name: '',
|
||||
args: [['foo', '=', 3]],
|
||||
operator: 'someop',
|
||||
context: context_(),
|
||||
limit: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
t.test('exec_workflow', function (openerp) {
|
||||
|
@ -231,14 +230,14 @@ $(document).ready(function () {
|
|||
t.expect(ds.name_search('foo', domain, 'ilike', 0), function (r) {
|
||||
strictEqual(r.method, 'name_search');
|
||||
|
||||
strictEqual(r.args.length, 5);
|
||||
strictEqual(r.args[0], 'foo');
|
||||
deepEqual(r.args[1], [['model_id', '=', 'qux']]);
|
||||
strictEqual(r.args[2], 'ilike');
|
||||
deepEqual(r.args[3], context_());
|
||||
strictEqual(r.args[4], 0);
|
||||
|
||||
ok(_.isEmpty(r.kwargs));
|
||||
strictEqual(r.args.length, 0);
|
||||
deepEqual(r.kwargs, {
|
||||
name: 'foo',
|
||||
args: [['model_id', '=', 'qux']],
|
||||
operator: 'ilike',
|
||||
context: context_(),
|
||||
limit: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
API changes from OpenERP Web 6.1 to 6.2
|
||||
=======================================
|
||||
|
||||
DataSet -> Model
|
||||
----------------
|
||||
|
||||
The 6.1 ``DataSet`` API has been deprecated in favor of the smaller
|
||||
and more orthogonal :doc:`Model </rpc>` API, which more closely
|
||||
matches the API in OpenERP Web's Python side and in OpenObject addons
|
||||
and removes most stateful behavior of DataSet.
|
||||
|
||||
Migration guide
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Rationale
|
||||
~~~~~~~~~
|
||||
|
||||
Renaming
|
||||
|
||||
The name *DataSet* exists in the CS community consciousness, and
|
||||
(as its name implies) it's a set of data (often fetched from a
|
||||
database, maybe lazily). OpenERP Web's dataset behaves very
|
||||
differently as it does not store (much) data (only a bunch of ids
|
||||
and just enough state to break things). The name "Model" matches
|
||||
the one used on the Python side for the task of building an RPC
|
||||
proxy to OpenERP objects.
|
||||
|
||||
API simplification
|
||||
|
||||
``DataSet`` has a number of methods which serve as little more
|
||||
than shortcuts, or are there due to domain and context evaluation
|
||||
issues in 6.1.
|
||||
|
||||
The shortcuts really add little value, and OpenERP Web embeds a
|
||||
restricted Python evaluator (in javascript) meaning most of the
|
||||
context and domain parsing & evaluation can be moved to the
|
||||
javascript code and does not require cooperative RPC bridging.
|
|
@ -8,6 +8,17 @@ Welcome to OpenERP Web's documentation!
|
|||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog-6.2
|
||||
|
||||
async
|
||||
rpc
|
||||
|
||||
Older stuff
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
Outside the box: network interactions
|
||||
=====================================
|
||||
|
||||
Building static displays is all nice and good and allows for neat
|
||||
effects (and sometimes you're given data to display from third parties
|
||||
so you don't have to make any effort), but a point generally comes
|
||||
where you'll want to talk to the world and make some network requests.
|
||||
|
||||
OpenERP Web provides two primary APIs to handle this, a low-level
|
||||
JSON-RPC based API communicating with the Python section of OpenERP
|
||||
Web (and of your addon, if you have a Python part) and a high-level
|
||||
API above that allowing your code to talk directly to the OpenERP
|
||||
server, using familiar-looking calls.
|
||||
|
||||
All networking APIs are :doc:`asynchronous </async>`. As a result, all
|
||||
of them will return :js:class:`Deferred` objects (whether they resolve
|
||||
those with values or not). Understanding how those work before before
|
||||
moving on is probably necessary.
|
||||
|
||||
High-level API: calling into OpenERP models
|
||||
-------------------------------------------
|
||||
|
||||
Access to OpenERP object methods (made available through XML-RPC from
|
||||
the server) is done via the :js:class:`openerp.web.Model` class. This
|
||||
class maps ontwo the OpenERP server objects via two primary methods,
|
||||
:js:func:`~openerp.web.Model.call` and
|
||||
:js:func:`~openerp.web.Model.query`.
|
||||
|
||||
:js:func:`~openerp.web.Model.call` is a direct mapping to the
|
||||
corresponding method of the OpenERP server object.
|
||||
|
||||
:js:func:`~openerp.web.Model.query` is a shortcut for a builder-style
|
||||
iterface to searches (``search`` + ``read`` in OpenERP RPC terms). It
|
||||
returns a :js:class:`~openerp.web.Query` object which is immutable but
|
||||
allows building new :js:class:`~openerp.web.Query` instances from the
|
||||
first one, adding new properties or modifiying the parent object's.
|
||||
|
||||
The query is only actually performed when calling one of the query
|
||||
serialization methods, :js:func:`~openerp.web.Query.all` and
|
||||
:js:func:`~openerp.web.Query.first`. These methods will perform a new
|
||||
RPC query every time they are called.
|
||||
|
||||
.. js:class:: openerp.web.Model(name)
|
||||
|
||||
.. js:attribute:: openerp.web.Model.name
|
||||
|
||||
name of the OpenERP model this object is bound to
|
||||
|
||||
.. js:function:: openerp.web.Model.call(method, args, kwargs)
|
||||
|
||||
Calls the ``method`` method of the current model, with the
|
||||
provided positional and keyword arguments.
|
||||
|
||||
:param String method: method to call over rpc on the
|
||||
:js:attr:`~openerp.web.Model.name`
|
||||
:param Array<> args: positional arguments to pass to the
|
||||
method, optional
|
||||
:param Object<> kwargs: keyword arguments to pass to the
|
||||
method, optional
|
||||
:rtype: Deferred<>
|
||||
|
||||
.. js:function:: openerp.web.Model.query(fields)
|
||||
|
||||
:param Array<String> fields: list of fields to fetch during
|
||||
the search
|
||||
:returns: a :js:class:`~openerp.web.Query` object
|
||||
representing the search to perform
|
||||
|
||||
.. js:class:: openerp.web.Query(fields)
|
||||
|
||||
The first set of methods is the "fetching" methods. They perform
|
||||
RPC queries using the internal data of the object they're called
|
||||
on.
|
||||
|
||||
.. js:function:: openerp.web.Query.all()
|
||||
|
||||
Fetches the result of the current
|
||||
:js:class:`~openerp.web.Query` object's search.
|
||||
|
||||
:rtype: Deferred<Array<>>
|
||||
|
||||
.. js:function:: openerp.web.Query.first()
|
||||
|
||||
Fetches the **first** result of the current
|
||||
:js:class:`~openerp.web.Query`, or ``null`` if the current
|
||||
:js:class:`~openerp.web.Query` does have any result.
|
||||
|
||||
:rtype: Deferred<Object | null>
|
||||
|
||||
.. js:function:: openerp.web.Query.count()
|
||||
|
||||
Fetches the number of records the current
|
||||
:js:class:`~openerp.web.Query` would retrieve.
|
||||
|
||||
:rtype: Deferred<Number>
|
||||
|
||||
The second set of methods is the "mutator" methods, they create a
|
||||
**new** :js:class:`~openerp.web.Query` object with the relevant
|
||||
(internal) attribute either augmented or replaced.
|
||||
|
||||
.. js:function:: openerp.web.Query.context(ctx)
|
||||
|
||||
Adds the provided ``ctx`` to the query, on top of any existing
|
||||
context
|
||||
|
||||
.. js:function:: openerp.web.Query.filter(domain)
|
||||
|
||||
Adds the provided domain to the query, this domain is
|
||||
``AND``-ed to the existing query domain.
|
||||
|
||||
.. js:function:: opeenrp.web.Query.offset(offset)
|
||||
|
||||
Sets the provided offset on the query. The new offset
|
||||
*replaces* the old one.
|
||||
|
||||
.. js:function:: openerp.web.Query.limit(limit)
|
||||
|
||||
Sets the provided limit on the query. The new limit *replaces*
|
||||
the old one.
|
||||
|
||||
.. js:function:: openerp.web.Query.order_by(fields…)
|
||||
|
||||
Overrides the model's natural order with the provided field
|
||||
specifications. Behaves much like Django's `QuerySet.order_by
|
||||
<https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by>`_:
|
||||
|
||||
* Takes 1..n field names, in order of most to least importance
|
||||
(the first field is the first sorting key). Fields are
|
||||
provided as strings.
|
||||
|
||||
* A field specifies an ascending order, unless it is prefixed
|
||||
with the minus sign "``-``" in which case the field is used
|
||||
in the descending order
|
||||
|
||||
Divergences from Django's sorting include a lack of random sort
|
||||
(``?`` field) and the inability to "drill down" into relations
|
||||
for sorting.
|
||||
|
||||
Low-level API: RPC calls to Python side
|
||||
---------------------------------------
|
||||
|
Loading…
Reference in New Issue