[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):
|
class DataSet(openerpweb.Controller):
|
||||||
_cp_path = "/web/dataset"
|
_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
|
@openerpweb.jsonrequest
|
||||||
def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None):
|
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)
|
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']:
|
if fields and fields == ['id']:
|
||||||
# shortcut read if we only want the ids
|
# shortcut read if we only want the ids
|
||||||
return {
|
return {
|
||||||
'ids': ids,
|
|
||||||
'length': length,
|
'length': length,
|
||||||
'records': [{'id': id} for id in ids]
|
'records': [{'id': id} for id in ids]
|
||||||
}
|
}
|
||||||
|
@ -867,46 +861,10 @@ class DataSet(openerpweb.Controller):
|
||||||
records = Model.read(ids, fields or False, context)
|
records = Model.read(ids, fields or False, context)
|
||||||
records.sort(key=lambda obj: ids.index(obj['id']))
|
records.sort(key=lambda obj: ids.index(obj['id']))
|
||||||
return {
|
return {
|
||||||
'ids': ids,
|
|
||||||
'length': length,
|
'length': length,
|
||||||
'records': records
|
'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
|
@openerpweb.jsonrequest
|
||||||
def load(self, req, model, id, fields):
|
def load(self, req, model, id, fields):
|
||||||
m = req.session.model(model)
|
m = req.session.model(model)
|
||||||
|
@ -916,23 +874,6 @@ class DataSet(openerpweb.Controller):
|
||||||
value = r[0]
|
value = r[0]
|
||||||
return {'value': value}
|
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):
|
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_domain = domain_id is not None and domain_id < len(args)
|
||||||
has_context = context_id is not None and context_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
|
@openerpweb.jsonrequest
|
||||||
def exec_workflow(self, req, model, id, signal):
|
def exec_workflow(self, req, model, id, signal):
|
||||||
r = req.session.exec_workflow(model, id, signal)
|
return 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}
|
|
||||||
|
|
||||||
class DataGroup(openerpweb.Controller):
|
class DataGroup(openerpweb.Controller):
|
||||||
_cp_path = "/web/group"
|
_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
|
* setting the correct session id and session context in the parameter
|
||||||
* objects
|
* objects
|
||||||
*
|
*
|
||||||
* @param {String} url RPC endpoint
|
* @param {Object} url RPC endpoint
|
||||||
* @param {Object} params call parameters
|
* @param {Object} params call parameters
|
||||||
* @param {Function} success_callback function to execute on RPC call success
|
* @param {Function} success_callback function to execute on RPC call success
|
||||||
* @param {Function} error_callback function to execute on RPC call failure
|
* @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
|
* @returns {String} SQL-like sorting string (``ORDER BY``) clause
|
||||||
*/
|
*/
|
||||||
openerp.web.serialize_sort = function (criterion) {
|
openerp.web.serialize_sort = function (criterion) {
|
||||||
return _.map(criterion,
|
return _.map(criterion,
|
||||||
function (criteria) {
|
function (criteria) {
|
||||||
if (criteria[0] === '-') {
|
if (criteria[0] === '-') {
|
||||||
return criteria.slice(1) + ' DESC';
|
return criteria.slice(1) + ' DESC';
|
||||||
}
|
}
|
||||||
return criteria + ' ASC';
|
return criteria + ' ASC';
|
||||||
}).join(', ');
|
}).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# */{
|
openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{
|
||||||
/**
|
/**
|
||||||
* Management interface between views and grouped collections of OpenERP
|
* 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.context = context || {};
|
||||||
this.index = null;
|
this.index = null;
|
||||||
this._sort = [];
|
this._sort = [];
|
||||||
|
this._model = new openerp.web.Model(model, context);
|
||||||
},
|
},
|
||||||
previous: function () {
|
previous: function () {
|
||||||
this.index -= 1;
|
this.index -= 1;
|
||||||
|
@ -296,13 +449,10 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
||||||
* @returns {$.Deferred}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
read_ids: function (ids, fields, options) {
|
read_ids: function (ids, fields, options) {
|
||||||
var options = options || {};
|
// TODO: reorder results to match ids list
|
||||||
return this.rpc('/web/dataset/get', {
|
return this._model.call('read',
|
||||||
model: this.model,
|
[ids, fields || false],
|
||||||
ids: ids,
|
{context: this._model.context(options.context)});
|
||||||
fields: fields,
|
|
||||||
context: this.get_context(options.context)
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Read a slice of the records represented by this DataSet, based on its
|
* 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}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
read_slice: function (fields, options) {
|
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)
|
* 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}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
read_index: function (fields, options) {
|
read_index: function (fields, options) {
|
||||||
var def = $.Deferred();
|
options = options || {};
|
||||||
if (_.isEmpty(this.ids)) {
|
// not very good
|
||||||
def.reject();
|
return this._model.query(fields)
|
||||||
} else {
|
.offset(this.index).first().pipe(function (record) {
|
||||||
fields = fields || false;
|
if (!record) { return $.Deferred().reject().promise(); }
|
||||||
this.read_ids([this.ids[this.index]], fields, options).then(function(records) {
|
return record;
|
||||||
def.resolve(records[0]);
|
});
|
||||||
}, function() {
|
|
||||||
def.reject.apply(def, arguments);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return def.promise();
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Reads default values for the current model
|
* Reads default values for the current model
|
||||||
|
@ -346,12 +498,9 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
||||||
* @returns {$.Deferred}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
default_get: function(fields, options) {
|
default_get: function(fields, options) {
|
||||||
var options = options || {};
|
options = options || {};
|
||||||
return this.rpc('/web/dataset/default_get', {
|
return this._model.call('default_get',
|
||||||
model: this.model,
|
[fields], {context: this._model.context(options.context)});
|
||||||
fields: fields,
|
|
||||||
context: this.get_context(options.context)
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Creates a new record in db
|
* Creates a new record in db
|
||||||
|
@ -362,11 +511,10 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
||||||
* @returns {$.Deferred}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
create: function(data, callback, error_callback) {
|
create: function(data, callback, error_callback) {
|
||||||
return this.rpc('/web/dataset/create', {
|
return this._model.call('create',
|
||||||
model: this.model,
|
[data], {context: this._model.context()})
|
||||||
data: data,
|
.pipe(function (r) { return {result: r}; })
|
||||||
context: this.get_context()
|
.then(callback, error_callback);
|
||||||
}, callback, error_callback);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Saves the provided data in an existing db record
|
* 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) {
|
write: function (id, data, options, callback, error_callback) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
return this.rpc('/web/dataset/save', {
|
return this._model.call('write',
|
||||||
model: this.model,
|
[[id], data], {context: this._model.context(options.context)})
|
||||||
id: id,
|
.pipe(function (r) { return {result: r}})
|
||||||
data: data,
|
.then(callback, error_callback);
|
||||||
context: this.get_context(options.context)
|
|
||||||
}, callback, error_callback);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Deletes an existing record from the database
|
* 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
|
* @param {Function} error_callback function called in case of deletion error
|
||||||
*/
|
*/
|
||||||
unlink: function(ids, callback, error_callback) {
|
unlink: function(ids, callback, error_callback) {
|
||||||
var self = this;
|
return this._model.call('unlink',
|
||||||
return this.call_and_eval("unlink", [ids, this.get_context()], null, 1,
|
[ids], {context: this._model.context()})
|
||||||
callback, error_callback);
|
.then(callback, error_callback);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Calls an arbitrary RPC method
|
* Calls an arbitrary RPC method
|
||||||
|
@ -408,11 +554,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
||||||
* @returns {$.Deferred}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
call: function (method, args, callback, error_callback) {
|
call: function (method, args, callback, error_callback) {
|
||||||
return this.rpc('/web/dataset/call', {
|
return this._model.call(method, args).then(callback, error_callback);
|
||||||
model: this.model,
|
|
||||||
method: method,
|
|
||||||
args: args || []
|
|
||||||
}, callback, error_callback);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Calls an arbitrary method, with more crazy
|
* Calls an arbitrary method, with more crazy
|
||||||
|
@ -446,13 +588,8 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
|
||||||
* @returns {$.Deferred}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
call_button: function (method, args, callback, error_callback) {
|
call_button: function (method, args, callback, error_callback) {
|
||||||
return this.rpc('/web/dataset/call_button', {
|
return this._model.call_button(method, args)
|
||||||
model: this.model,
|
.then(callback, error_callback);
|
||||||
method: method,
|
|
||||||
domain_id: null,
|
|
||||||
context_id: args.length - 1,
|
|
||||||
args: args || []
|
|
||||||
}, callback, error_callback);
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Fetches the "readable name" for records, based on intrinsic rules
|
* 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}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
name_get: function(ids, callback) {
|
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}
|
* @returns {$.Deferred}
|
||||||
*/
|
*/
|
||||||
name_search: function (name, domain, operator, limit, callback) {
|
name_search: function (name, domain, operator, limit, callback) {
|
||||||
return this.call_and_eval('name_search',
|
return this._model.call('name_search', [], {
|
||||||
[name || '', domain || false, operator || 'ilike', this.get_context(), limit || 0],
|
name: name || '',
|
||||||
1, 3, callback);
|
args: domain || false,
|
||||||
|
operator: operator || 'ilike',
|
||||||
|
context: this._model.context(),
|
||||||
|
limit: limit || 0
|
||||||
|
}).then(callback);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param name
|
* @param name
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
name_create: function(name, 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) {
|
exec_workflow: function (id, signal, callback) {
|
||||||
return this.rpc('/web/dataset/exec_workflow', {
|
return this._model.exec_workflow(id, signal)
|
||||||
model: this.model,
|
.pipe(function (result) { return { result: result }; })
|
||||||
id: id,
|
.then(callback);
|
||||||
signal: signal
|
|
||||||
}, callback);
|
|
||||||
},
|
},
|
||||||
get_context: function(request_context) {
|
get_context: function(request_context) {
|
||||||
if (request_context) {
|
return this._model.context(request_context);
|
||||||
return new openerp.web.CompoundContext(this.context, request_context);
|
|
||||||
}
|
|
||||||
return this.context;
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Reads or changes sort criteria on the dataset.
|
* 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) {
|
init: function(parent, model, context, domain) {
|
||||||
this._super(parent, model, context);
|
this._super(parent, model, context);
|
||||||
this.domain = domain || [];
|
this.domain = domain || [];
|
||||||
this.offset = 0;
|
this._length = null;
|
||||||
this._length;
|
|
||||||
// subset records[offset:offset+limit]
|
|
||||||
// is it necessary ?
|
|
||||||
this.ids = [];
|
this.ids = [];
|
||||||
|
this._model = new openerp.web.Model(model, context, domain);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Read a slice of the records represented by this DataSet, based on its
|
* 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) {
|
read_slice: function (fields, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
var offset = options.offset || 0;
|
var q = this._model.query(fields || false)
|
||||||
return this.rpc('/web/dataset/search_read', {
|
.filter(options.domain)
|
||||||
model: this.model,
|
.context(options.context)
|
||||||
fields: fields || false,
|
.offset(options.offset || 0)
|
||||||
domain: this.get_domain(options.domain),
|
.limit(options.limit || false);
|
||||||
context: this.get_context(options.context),
|
q = q.order_by.apply(q, this._sort);
|
||||||
sort: this.sort(),
|
return q.all().then(function (records) {
|
||||||
offset: offset,
|
// FIXME: not sure about that one, *could* have discarded count
|
||||||
limit: options.limit || false
|
q.count().then(function (count) { this._length = count; });
|
||||||
}).pipe(function (result) {
|
self.ids = _(records).pluck('id');
|
||||||
self.ids = result.ids;
|
|
||||||
self.offset = offset;
|
|
||||||
self._length = result.length;
|
|
||||||
return result.records;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
get_domain: function (other_domain) {
|
get_domain: function (other_domain) {
|
||||||
if (other_domain) {
|
this._model.domain(other_domain);
|
||||||
return new openerp.web.CompoundDomain(this.domain, other_domain);
|
|
||||||
}
|
|
||||||
return this.domain;
|
|
||||||
},
|
},
|
||||||
unlink: function(ids, callback, error_callback) {
|
unlink: function(ids, callback, error_callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -841,34 +972,6 @@ openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({
|
||||||
on_unlink: function(ids) {}
|
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({
|
openerp.web.CompoundContext = openerp.web.Class.extend({
|
||||||
init: function () {
|
init: function () {
|
||||||
this.__ref = "compound_context";
|
this.__ref = "compound_context";
|
||||||
|
|
|
@ -11,13 +11,15 @@ $(document).ready(function () {
|
||||||
ds.ids = [10, 20, 30, 40, 50];
|
ds.ids = [10, 20, 30, 40, 50];
|
||||||
ds.index = 2;
|
ds.index = 2;
|
||||||
t.expect(ds.read_index(['a', 'b', 'c']), function (result) {
|
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.model, 'some.model');
|
||||||
|
|
||||||
strictEqual(result.args.length, 3);
|
strictEqual(result.args.length, 5);
|
||||||
deepEqual(result.args[0], [30]);
|
deepEqual(result.args[0], []);
|
||||||
deepEqual(result.args[1], ['a', 'b', 'c']);
|
strictEqual(result.args[1], 2);
|
||||||
deepEqual(result.args[2], context_());
|
strictEqual(result.args[2], 1);
|
||||||
|
strictEqual(result.args[3], false);
|
||||||
|
deepEqual(result.args[4], context_());
|
||||||
|
|
||||||
ok(_.isEmpty(result.kwargs));
|
ok(_.isEmpty(result.kwargs));
|
||||||
});
|
});
|
||||||
|
@ -29,13 +31,12 @@ $(document).ready(function () {
|
||||||
strictEqual(result.method, 'default_get');
|
strictEqual(result.method, 'default_get');
|
||||||
strictEqual(result.model, 'some.model');
|
strictEqual(result.model, 'some.model');
|
||||||
|
|
||||||
strictEqual(result.args.length, 2);
|
strictEqual(result.args.length, 1);
|
||||||
deepEqual(result.args[0], ['a', 'b', 'c']);
|
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) {
|
t.test('create', function (openerp) {
|
||||||
|
@ -43,11 +44,12 @@ $(document).ready(function () {
|
||||||
t.expect(ds.create({foo: 1, bar: 2}), function (r) {
|
t.expect(ds.create({foo: 1, bar: 2}), function (r) {
|
||||||
strictEqual(r.method, 'create');
|
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[0], {foo: 1, bar: 2});
|
||||||
deepEqual(r.args[1], context_());
|
|
||||||
|
|
||||||
ok(_.isEmpty(r.kwargs));
|
deepEqual(r.kwargs, {
|
||||||
|
context: context_()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.test('write', function (openerp) {
|
t.test('write', function (openerp) {
|
||||||
|
@ -55,12 +57,12 @@ $(document).ready(function () {
|
||||||
t.expect(ds.write(42, {foo: 1}), function (r) {
|
t.expect(ds.write(42, {foo: 1}), function (r) {
|
||||||
strictEqual(r.method, 'write');
|
strictEqual(r.method, 'write');
|
||||||
|
|
||||||
strictEqual(r.args.length, 3);
|
strictEqual(r.args.length, 2);
|
||||||
deepEqual(r.args[0], [42]);
|
deepEqual(r.args[0], [42]);
|
||||||
deepEqual(r.args[1], {foo: 1});
|
deepEqual(r.args[1], {foo: 1});
|
||||||
deepEqual(r.args[2], context_());
|
deepEqual(r.kwargs, {
|
||||||
|
context: context_()
|
||||||
ok(_.isEmpty(r.kwargs));
|
});
|
||||||
});
|
});
|
||||||
// FIXME: can't run multiple sessions in the same test(), fucks everything up
|
// 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) {
|
// 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) {
|
t.expect(ds.unlink([42]), function (r) {
|
||||||
strictEqual(r.method, 'unlink');
|
strictEqual(r.method, 'unlink');
|
||||||
|
|
||||||
strictEqual(r.args.length, 2);
|
strictEqual(r.args.length, 1);
|
||||||
deepEqual(r.args[0], [42]);
|
deepEqual(r.args[0], [42]);
|
||||||
deepEqual(r.args[1], context_());
|
deepEqual(r.kwargs, {
|
||||||
|
context: context_()
|
||||||
ok(_.isEmpty(r.kwargs));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.test('call', function (openerp) {
|
t.test('call', function (openerp) {
|
||||||
|
@ -96,11 +98,11 @@ $(document).ready(function () {
|
||||||
t.expect(ds.name_get([1, 2], null), function (r) {
|
t.expect(ds.name_get([1, 2], null), function (r) {
|
||||||
strictEqual(r.method, 'name_get');
|
strictEqual(r.method, 'name_get');
|
||||||
|
|
||||||
strictEqual(r.args.length, 2);
|
strictEqual(r.args.length, 1);
|
||||||
deepEqual(r.args[0], [1, 2]);
|
deepEqual(r.args[0], [1, 2]);
|
||||||
deepEqual(r.args[1], context_());
|
deepEqual(r.kwargs, {
|
||||||
|
context: context_()
|
||||||
ok(_.isEmpty(r.kwargs));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.test('name_search, name', function (openerp) {
|
t.test('name_search, name', function (openerp) {
|
||||||
|
@ -108,15 +110,14 @@ $(document).ready(function () {
|
||||||
t.expect(ds.name_search('bob'), function (r) {
|
t.expect(ds.name_search('bob'), function (r) {
|
||||||
strictEqual(r.method, 'name_search');
|
strictEqual(r.method, 'name_search');
|
||||||
|
|
||||||
strictEqual(r.args.length, 5);
|
strictEqual(r.args.length, 0);
|
||||||
strictEqual(r.args[0], 'bob');
|
deepEqual(r.kwargs, {
|
||||||
// domain
|
name: 'bob',
|
||||||
deepEqual(r.args[1], []);
|
args: false,
|
||||||
strictEqual(r.args[2], 'ilike');
|
operator: 'ilike',
|
||||||
deepEqual(r.args[3], context_());
|
context: context_(),
|
||||||
strictEqual(r.args[4], 0);
|
limit: 0
|
||||||
|
});
|
||||||
ok(_.isEmpty(r.kwargs));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.test('name_search, domain & operator', function (openerp) {
|
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) {
|
t.expect(ds.name_search(0, [['foo', '=', 3]], 'someop'), function (r) {
|
||||||
strictEqual(r.method, 'name_search');
|
strictEqual(r.method, 'name_search');
|
||||||
|
|
||||||
strictEqual(r.args.length, 5);
|
strictEqual(r.args.length, 0);
|
||||||
strictEqual(r.args[0], '');
|
deepEqual(r.kwargs, {
|
||||||
// domain
|
name: '',
|
||||||
deepEqual(r.args[1], [['foo', '=', 3]]);
|
args: [['foo', '=', 3]],
|
||||||
strictEqual(r.args[2], 'someop');
|
operator: 'someop',
|
||||||
deepEqual(r.args[3], context_());
|
context: context_(),
|
||||||
// limit
|
limit: 0
|
||||||
strictEqual(r.args[4], 0);
|
});
|
||||||
|
|
||||||
ok(_.isEmpty(r.kwargs));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
t.test('exec_workflow', function (openerp) {
|
t.test('exec_workflow', function (openerp) {
|
||||||
|
@ -231,14 +230,14 @@ $(document).ready(function () {
|
||||||
t.expect(ds.name_search('foo', domain, 'ilike', 0), function (r) {
|
t.expect(ds.name_search('foo', domain, 'ilike', 0), function (r) {
|
||||||
strictEqual(r.method, 'name_search');
|
strictEqual(r.method, 'name_search');
|
||||||
|
|
||||||
strictEqual(r.args.length, 5);
|
strictEqual(r.args.length, 0);
|
||||||
strictEqual(r.args[0], 'foo');
|
deepEqual(r.kwargs, {
|
||||||
deepEqual(r.args[1], [['model_id', '=', 'qux']]);
|
name: 'foo',
|
||||||
strictEqual(r.args[2], 'ilike');
|
args: [['model_id', '=', 'qux']],
|
||||||
deepEqual(r.args[3], context_());
|
operator: 'ilike',
|
||||||
strictEqual(r.args[4], 0);
|
context: context_(),
|
||||||
|
limit: 0
|
||||||
ok(_.isEmpty(r.kwargs));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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:
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
changelog-6.2
|
||||||
|
|
||||||
|
async
|
||||||
|
rpc
|
||||||
|
|
||||||
|
Older stuff
|
||||||
|
-----------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
: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