4 var instance = openerp;
8 * Serializes the sort criterion array of a dataset into a form which can be
9 * consumed by OpenERP's RPC APIs.
11 * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting
12 * @returns {String} SQL-like sorting string (``ORDER BY``) clause
14 instance.web.serialize_sort = function (criterion) {
15 return _.map(criterion,
17 if (criteria[0] === '-') {
18 return criteria.slice(1) + ' DESC';
20 return criteria + ' ASC';
24 instance.web.Query = instance.web.Class.extend({
25 init: function (model, fields) {
27 this._fields = fields;
34 clone: function (to_set) {
35 to_set = to_set || {};
36 var q = new instance.web.Query(this._model, this._fields);
37 q._context = this._context;
38 q._filter = this._filter;
39 q._limit = this._limit;
40 q._offset = this._offset;
41 q._order_by = this._order_by;
43 for(var key in to_set) {
44 if (!to_set.hasOwnProperty(key)) { continue; }
47 q._filter = new instance.web.CompoundDomain(
48 q._filter, to_set.filter);
51 q._context = new instance.web.CompoundContext(
52 q._context, to_set.context);
57 q['_' + key] = to_set[key];
62 _execute: function () {
64 return instance.session.rpc('/web/dataset/search_read', {
65 model: this._model.name,
66 fields: this._fields || false,
67 domain: instance.web.pyeval.eval('domains',
68 [this._model.domain(this._filter)]),
69 context: instance.web.pyeval.eval('contexts',
70 [this._model.context(this._context)]),
73 sort: instance.web.serialize_sort(this._order_by)
74 }).then(function (results) {
75 self._count = results.length;
76 return results.records;
80 * Fetches the first record matching the query, or null
82 * @returns {jQuery.Deferred<Object|null>}
86 return this.clone({limit: 1})._execute().then(function (records) {
88 if (records.length) { return records[0]; }
93 * Fetches all records matching the query
95 * @returns {jQuery.Deferred<Array<>>}
98 return this._execute();
101 * Fetches the number of records matching the query in the database
103 * @returns {jQuery.Deferred<Number>}
106 if (this._count !== undefined) { return $.when(this._count); }
107 return this._model.call(
108 'search_count', [this._filter], {
109 context: this._model.context(this._context)});
112 * Performs a groups read according to the provided grouping criterion
114 * @param {String|Array<String>} grouping
115 * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null}
117 group_by: function (grouping) {
118 var ctx = instance.web.pyeval.eval(
119 'context', this._model.context(this._context));
121 // undefined passed in explicitly (!)
122 if (_.isUndefined(grouping)) {
126 if (!(grouping instanceof Array)) {
127 grouping = _.toArray(arguments);
129 if (_.isEmpty(grouping) && !ctx['group_by_no_leaf']) {
134 return this._model.call('read_group', {
136 fields: _.uniq(grouping.concat(this._fields || [])),
137 domain: this._model.domain(this._filter),
139 offset: this._offset,
141 orderby: instance.web.serialize_sort(this._order_by) || false
142 }).then(function (results) {
143 return _(results).map(function (result) {
144 // FIX: querygroup initialization
145 result.__context = result.__context || {};
146 result.__context.group_by = result.__context.group_by || [];
147 _.defaults(result.__context, ctx);
148 return new instance.web.QueryGroup(
149 self._model.name, grouping[0], result);
154 * Creates a new query with the union of the current query's context and
157 * @param context context data to add to the query
158 * @returns {openerp.web.Query}
160 context: function (context) {
161 if (!context) { return this; }
162 return this.clone({context: context});
165 * Creates a new query with the union of the current query's filter and
168 * @param domain domain data to AND with the current query filter
169 * @returns {openerp.web.Query}
171 filter: function (domain) {
172 if (!domain) { return this; }
173 return this.clone({filter: domain});
176 * Creates a new query with the provided limit replacing the current
179 * @param {Number} limit maximum number of records the query should retrieve
180 * @returns {openerp.web.Query}
182 limit: function (limit) {
183 return this.clone({limit: limit});
186 * Creates a new query with the provided offset replacing the current
189 * @param {Number} offset number of records the query should skip before starting its retrieval
190 * @returns {openerp.web.Query}
192 offset: function (offset) {
193 return this.clone({offset: offset});
196 * Creates a new query with the provided ordering parameters replacing
197 * those of the current query
199 * @param {String...} fields ordering clauses
200 * @returns {openerp.web.Query}
202 order_by: function (fields) {
203 if (fields === undefined) { return this; }
204 if (!(fields instanceof Array)) {
205 fields = _.toArray(arguments);
207 if (_.isEmpty(fields)) { return this; }
208 return this.clone({order_by: fields});
212 instance.web.QueryGroup = instance.web.Class.extend({
213 init: function (model, grouping_field, read_group_group) {
214 // In cases where group_by_no_leaf and no group_by, the result of
215 // read_group has aggregate fields but no __context or __domain.
216 // Create default (empty) values for those so that things don't break
217 var fixed_group = _.extend(
218 {__context: {group_by: []}, __domain: []},
222 _(fixed_group).each(function (value, key) {
223 if (key.indexOf('__') === 0
224 || key === grouping_field
225 || key === grouping_field + '_count') {
228 aggregates[key] = value || 0;
231 this.model = new instance.web.Model(
232 model, fixed_group.__context, fixed_group.__domain);
234 var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;
235 var leaf_group = fixed_group.__context.group_by.length === 0;
237 folded: !!(fixed_group.__fold),
238 grouped_on: grouping_field,
239 // if terminal group (or no group) and group_by_no_leaf => use group.__count
241 value: fixed_group[grouping_field],
242 // A group is open-able if it's not a leaf in group_by_no_leaf mode
243 has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),
245 aggregates: aggregates
248 get: function (key) {
249 return this.attributes[key];
251 subgroups: function () {
252 return this.model.query().group_by(this.model.context().group_by);
255 return this.model.query.apply(this.model, arguments);
259 instance.web.Model.include({
261 new openerp.web.Model([session,] model_name[, context[, domain]])
263 @constructs instance.web.Model
264 @extends instance.web.Class
266 @param {openerp.web.Session} [session] The session object used to communicate with
268 @param {String} model_name name of the OpenERP model this object is bound to
269 @param {Object} [context]
270 @param {Array} [domain]
273 var session, model_name, context, domain;
274 var args = _.toArray(arguments);
276 session = args.pop();
277 if (session && ! (session instanceof openerp.web.Session)) {
278 model_name = session;
281 model_name = args.pop();
283 context = args.length > 0 ? args.pop() : null;
284 domain = args.length > 0 ? args.pop() : null;
286 this._super(session, model_name, context, domain);
287 this._context = context || {};
288 this._domain = domain || [];
290 session: function() {
292 return instance.session;
293 return this._super();
296 * @deprecated does not allow to specify kwargs, directly use call() instead
298 get_func: function (method_name) {
301 return self.call(method_name, _.toArray(arguments));
305 * Call a method (over RPC) on the bound OpenERP model.
307 * @param {String} method name of the method to call
308 * @param {Array} [args] positional arguments
309 * @param {Object} [kwargs] keyword arguments
310 * @param {Object} [options] additional options for the rpc() method
311 * @returns {jQuery.Deferred<>} call result
313 call: function (method, args, kwargs, options) {
315 kwargs = kwargs || {};
316 if (!_.isArray(args)) {
317 // call(method, kwargs)
321 instance.web.pyeval.ensure_evaluated(args, kwargs);
322 return this._super(method, args, kwargs, options);
325 * Fetches a Query instance bound to this model, for searching
327 * @param {Array<String>} [fields] fields to ultimately fetch during the search
328 * @returns {instance.web.Query}
330 query: function (fields) {
331 return new instance.web.Query(this, fields);
334 * Executes a signal on the designated workflow, on the bound OpenERP model
336 * @param {Number} id workflow identifier
337 * @param {String} signal signal to trigger on the workflow
339 exec_workflow: function (id, signal) {
340 return this.session().rpc('/web/dataset/exec_workflow', {
347 * Fetches the model's domain, combined with the provided domain if any
349 * @param {Array} [domain] to combine with the model's internal domain
350 * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
352 domain: function (domain) {
353 if (!domain) { return this._domain; }
354 return new instance.web.CompoundDomain(
355 this._domain, domain);
358 * Fetches the combination of the user's context and the domain context,
359 * combined with the provided context if any
361 * @param {Object} [context] to combine with the model's internal context
362 * @returns {instance.web.CompoundContext} The union of the user's context and the model's internal context, as well as the provided context if any. In that order.
364 context: function (context) {
365 return new instance.web.CompoundContext(
366 instance.session.user_context, this._context, context || {});
369 * Button action caller, needs to perform cleanup if an action is returned
370 * from the button (parsing of context and domain, and fixup of the views
371 * collection for act_window actions)
373 * FIXME: remove when evaluator integrated
375 call_button: function (method, args) {
376 instance.web.pyeval.ensure_evaluated(args, {});
377 return this.session().rpc('/web/dataset/call_button', {
380 // Should not be necessary anymore. Integrate remote in this?
382 context_id: args.length - 1,
388 instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
390 * Collection of OpenERP records, used to share records and the current selection between views.
392 * @constructs instance.web.DataSet
394 * @param {String} model the OpenERP model this dataset will manage
396 init: function(parent, model, context) {
397 instance.web.PropertiesMixin.init.call(this);
399 this.context = context || {};
402 this._model = new instance.web.Model(model, context);
404 previous: function () {
406 if (!this.ids.length) {
408 } else if (this.index < 0) {
409 this.index = this.ids.length - 1;
415 if (!this.ids.length) {
417 } else if (this.index >= this.ids.length) {
422 select_id: function(id) {
423 var idx = this.get_id_index(id);
431 get_id_index: function(id) {
432 for (var i=0, ii=this.ids.length; i<ii; i++) {
433 // Here we use type coercion because of the mess potentially caused by
434 // OpenERP ids fetched from the DOM as string. (eg: dhtmlxcalendar)
435 // OpenERP ids can be non-numeric too ! (eg: recursive events in calendar)
436 if (id == this.ids[i]) {
445 * @param {Array} ids identifiers of the records to read
446 * @param {Array} fields fields to read and return, by default all fields are returned
447 * @returns {$.Deferred}
449 read_ids: function (ids, fields, options) {
451 return $.Deferred().resolve([]);
453 options = options || {};
454 // TODO: reorder results to match ids list
455 return this._model.call('read',
456 [ids, fields || false],
457 {context: this.get_context(options.context)});
460 * Read a slice of the records represented by this DataSet, based on its
461 * domain and context.
463 * @param {Array} [fields] fields to read and return, by default all fields are returned
464 * @params {Object} [options]
465 * @param {Number} [options.offset=0] The index from which selected records should be returned
466 * @param {Number} [options.limit=null] The maximum number of records to return
467 * @returns {$.Deferred}
469 read_slice: function (fields, options) {
471 options = options || {};
472 return this._model.query(fields)
473 .limit(options.limit || false)
474 .offset(options.offset || 0)
475 .all().done(function (records) {
476 self.ids = _(records).pluck('id');
480 * Reads the current dataset record (from its index)
482 * @params {Array} [fields] fields to read and return, by default all fields are returned
483 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
484 * @returns {$.Deferred}
486 read_index: function (fields, options) {
487 options = options || {};
488 return this.read_ids([this.ids[this.index]], fields, options).then(function (records) {
489 if (_.isEmpty(records)) { return $.Deferred().reject().promise(); }
494 * Reads default values for the current model
496 * @param {Array} [fields] fields to get default values for, by default all defaults are read
497 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
498 * @returns {$.Deferred}
500 default_get: function(fields, options) {
501 options = options || {};
502 return this._model.call('default_get',
503 [fields], {context: this.get_context(options.context)});
506 * Creates a new record in db
508 * @param {Object} data field values to set on the new record
509 * @param {Object} options Dictionary that can contain the following keys:
510 * - readonly_fields: Values from readonly fields that were updated by
511 * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
512 * @returns {$.Deferred}
514 create: function(data, options) {
516 return this._model.call('create', [data], {
517 context: this.get_context()
518 }).done(function () {
519 self.trigger('dataset_changed', data, options);
523 * Saves the provided data in an existing db record
525 * @param {Number|String} id identifier for the record to alter
526 * @param {Object} data field values to write into the record
527 * @param {Object} options Dictionary that can contain the following keys:
528 * - context: The context to use in the server-side call.
529 * - readonly_fields: Values from readonly fields that were updated by
530 * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
531 * @returns {$.Deferred}
533 write: function (id, data, options) {
534 options = options || {};
536 return this._model.call('write', [[id], data], {
537 context: this.get_context(options.context)
538 }).done(function () {
539 self.trigger('dataset_changed', id, data, options);
543 * Deletes an existing record from the database
545 * @param {Number|String} ids identifier of the record to delete
547 unlink: function(ids) {
549 return this._model.call('unlink', [ids], {
550 context: this.get_context()
551 }).done(function () {
552 self.trigger('dataset_changed', ids);
556 * Calls an arbitrary RPC method
558 * @param {String} method name of the method (on the current model) to call
559 * @param {Array} [args] arguments to pass to the method
560 * @param {Function} callback
561 * @param {Function} error_callback
562 * @returns {$.Deferred}
564 call: function (method, args) {
565 return this._model.call(method, args);
568 * Calls a button method, usually returning some sort of action
570 * @param {String} method
571 * @param {Array} [args]
572 * @returns {$.Deferred}
574 call_button: function (method, args) {
575 return this._model.call_button(method, args);
578 * Fetches the "readable name" for records, based on intrinsic rules
581 * @returns {$.Deferred}
583 name_get: function(ids) {
584 return this._model.call('name_get', [ids], {context: this.get_context()});
588 * @param {String} name name to perform a search for/on
589 * @param {Array} [domain=[]] filters for the objects returned, OpenERP domain
590 * @param {String} [operator='ilike'] matching operator to use with the provided name value
591 * @param {Number} [limit=0] maximum number of matches to return
592 * @param {Function} callback function to call with name_search result
593 * @returns {$.Deferred}
595 name_search: function (name, domain, operator, limit) {
596 return this._model.call('name_search', {
598 args: domain || false,
599 operator: operator || 'ilike',
600 context: this._model.context(),
607 name_create: function(name) {
608 return this._model.call('name_create', [name], {context: this.get_context()});
610 exec_workflow: function (id, signal) {
611 return this._model.exec_workflow(id, signal);
613 get_context: function(request_context) {
614 return this._model.context(request_context);
617 * Reads or changes sort criteria on the dataset.
619 * If not provided with any argument, serializes the sort criteria to
620 * an SQL-like form usable by OpenERP's ORM.
622 * If given a field, will set that field as first sorting criteria or,
623 * if the field is already the first sorting criteria, will reverse it.
625 * @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria
626 * @param {Boolean} [force_reverse=false] forces inserting the field as DESC
627 * @returns {String|undefined}
629 sort: function (field, force_reverse) {
631 return instance.web.serialize_sort(this._sort);
633 var reverse = force_reverse || (this._sort[0] === field);
634 this._sort.splice.apply(
635 this._sort, [0, this._sort.length].concat(
636 _.without(this._sort, field, '-' + field)));
638 this._sort.unshift((reverse ? '-' : '') + field);
642 return this.ids.length;
644 alter_ids: function(n_ids) {
647 remove_ids: function (ids) {
648 this.alter_ids(_(this.ids).difference(ids));
650 add_ids: function(ids, at) {
651 var args = [at, 0].concat(_.difference(ids, this.ids));
652 this.ids.splice.apply(this.ids, args);
655 * Resequence records.
657 * @param {Array} ids identifiers of the records to resequence
658 * @returns {$.Deferred}
660 resequence: function (ids, options) {
661 options = options || {};
662 return instance.session.rpc('/web/dataset/resequence', {
665 context: instance.web.pyeval.eval(
666 'context', this.get_context(options.context)),
667 }).then(function (results) {
673 instance.web.DataSetStatic = instance.web.DataSet.extend({
674 init: function(parent, model, context, ids) {
676 this._super(parent, model, context);
678 this.ids = ids || [];
680 read_slice: function (fields, options) {
681 options = options || {};
682 fields = fields || {};
683 var offset = options.offset || 0,
684 limit = options.limit || false;
685 var end_pos = limit && limit !== -1 ? offset + limit : this.ids.length;
686 return this.read_ids(this.ids.slice(offset, end_pos), fields);
688 set_ids: function (ids) {
690 if (ids.length === 0) {
692 } else if (this.index >= ids.length - 1) {
693 this.index = ids.length - 1;
696 unlink: function(ids) {
697 this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
698 this.trigger('unlink', ids);
699 return $.Deferred().resolve({result: true});
703 instance.web.DataSetSearch = instance.web.DataSet.extend({
705 * @constructs instance.web.DataSetSearch
706 * @extends instance.web.DataSet
708 * @param {Object} parent
709 * @param {String} model
710 * @param {Object} context
711 * @param {Array} domain
713 init: function(parent, model, context, domain) {
714 this._super(parent, model, context);
715 this.domain = domain || [];
718 this._model = new instance.web.Model(model, context, domain);
721 * Read a slice of the records represented by this DataSet, based on its
722 * domain and context.
724 * @params {Object} options
725 * @param {Array} [options.fields] fields to read and return, by default all fields are returned
726 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
727 * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain
728 * @param {Number} [options.offset=0] The index from which selected records should be returned
729 * @param {Number} [options.limit=null] The maximum number of records to return
730 * @returns {$.Deferred}
732 read_slice: function (fields, options) {
733 options = options || {};
735 var q = this._model.query(fields || false)
736 .filter(options.domain)
737 .context(options.context)
738 .offset(options.offset || 0)
739 .limit(options.limit || false);
740 q = q.order_by.apply(q, this._sort);
742 return q.all().done(function (records) {
743 // FIXME: not sure about that one, *could* have discarded count
744 q.count().done(function (count) { self._length = count; });
745 self.ids = _(records).pluck('id');
748 get_domain: function (other_domain) {
749 return this._model.domain(other_domain);
751 alter_ids: function (ids) {
753 if (this.index !== null && this.index >= this.ids.length) {
754 this.index = this.ids.length > 0 ? this.ids.length - 1 : 0;
757 remove_ids: function (ids) {
758 var before = this.ids.length;
761 this._length -= (before - this.ids.length);
764 unlink: function(ids, callback, error_callback) {
766 return this._super(ids).done(function(result) {
767 self.remove_ids( ids);
768 self.trigger("dataset_changed", ids, callback, error_callback);
772 if (this._length !== null) {
775 return this._super();
779 instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
780 virtual_id_prefix: "one2many_v_id_",
783 this._super.apply(this, arguments);
785 this.last_default_get = {};
786 this.running_reads = [];
788 default_get: function(fields, options) {
790 return this._super(fields, options).done(function(res) {
791 self.last_default_get = res;
794 create: function(data, options) {
796 id:_.uniqueId(this.virtual_id_prefix),
797 values: _.extend({}, data, (options || {}).readonly_fields || {}),
798 defaults: this.last_default_get
800 this.to_create.push(_.extend(_.clone(cached), {values: _.clone(data)}));
801 this.cache.push(cached);
802 return $.Deferred().resolve(cached.id).promise();
804 write: function (id, data, options) {
806 var record = _.detect(this.to_create, function(x) {return x.id === id;});
807 record = record || _.detect(this.to_write, function(x) {return x.id === id;});
810 for (var k in data) {
811 if (record.values[k] === undefined || record.values[k] !== data[k]) {
816 $.extend(record.values, data);
819 record = {id: id, values: data};
820 self.to_write.push(record);
822 var cached = _.detect(this.cache, function(x) {return x.id === id;});
824 cached = {id: id, values: {}};
825 this.cache.push(cached);
827 $.extend(cached.values, _.extend({}, record.values, (options || {}).readonly_fields || {}));
829 this.trigger("dataset_changed", id, data, options);
830 return $.Deferred().resolve(true).promise();
832 unlink: function(ids, callback, error_callback) {
834 _.each(ids, function(id) {
835 if (! _.detect(self.to_create, function(x) { return x.id === id; })) {
836 self.to_delete.push({id: id});
839 this.to_create = _.reject(this.to_create, function(x) { return _.include(ids, x.id);});
840 this.to_write = _.reject(this.to_write, function(x) { return _.include(ids, x.id);});
841 this.cache = _.reject(this.cache, function(x) { return _.include(ids, x.id);});
842 this.set_ids(_.without.apply(_, [this.ids].concat(ids)));
843 this.trigger("dataset_changed", ids, callback, error_callback);
844 return $.async_when({result: true}).done(callback);
846 reset_ids: function(ids) {
852 this.delete_all = false;
853 _.each(_.clone(this.running_reads), function(el) {
857 read_ids: function (ids, fields, options) {
860 _.each(ids, function(id) {
861 var cached = _.detect(self.cache, function(x) {return x.id === id;});
862 var created = _.detect(self.to_create, function(x) {return x.id === id;});
864 _.each(fields, function(x) {if (cached.values[x] === undefined)
865 cached.values[x] = created.defaults[x] || false;});
867 if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined;}))
871 var return_records = function() {
872 var records = _.map(ids, function(id) {
873 var c = _.find(self.cache, function(c) {return c.id === id;});
874 return _.isUndefined(c) ? c : _.extend({}, c.values, {"id": id});
876 if (self.debug_mode) {
877 if (_.include(records, undefined)) {
878 throw "Record not correctly loaded";
881 var sort_fields = self._sort,
882 compare = function (v1, v2) {
883 return (v1 < v2) ? -1
887 // Array.sort is not necessarily stable. We must be careful with this because
888 // sorting an array where all items are considered equal is a worst-case that
889 // will randomize the array with an unstable sort! Therefore we must avoid
890 // sorting if there are no sort_fields (i.e. all items are considered equal)
891 // See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11
892 // http://code.google.com/p/v8/issues/detail?id=90
893 if (sort_fields.length) {
894 records.sort(function (a, b) {
895 return _.reduce(sort_fields, function (acc, field) {
896 if (acc) { return acc; }
898 if (field[0] === '-') {
900 field = field.slice(1);
902 //m2o should be searched based on value[1] not based whole value(i.e. [id, value])
903 if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){
904 return sign * compare(a[field][1], b[field][1]);
906 return sign * compare(a[field], b[field]);
910 return $.when(records);
912 if(to_get.length > 0) {
913 var def = $.Deferred();
914 self.running_reads.push(def);
915 def.always(function() {
916 self.running_reads = _.without(self.running_reads, def);
918 this._super(to_get, fields, options).then(function() {
919 def.resolve.apply(def, arguments);
921 def.reject.apply(def, arguments);
923 return def.then(function(records) {
924 _.each(records, function(record, index) {
925 var id = to_get[index];
926 var cached = _.detect(self.cache, function(x) {return x.id === id;});
928 self.cache.push({id: id, values: record});
930 // I assume cache value is prioritary
931 cached.values = _.defaults(_.clone(cached.values), record);
934 return return_records();
937 return return_records();
941 * Invalidates caching of a record in the dataset to ensure the next read
942 * of that record will hit the server.
944 * Of use when an action is going to remote-alter a record which will then
945 * need to be reloaded, e.g. action button.
947 * @param {Object} id record to remove from the BDS's cache
949 evict_record: function (id) {
950 // Don't evict records which haven't yet been saved: there is no more
951 // recent data on the server (and there potentially isn't any data),
952 // and this breaks the assumptions of other methods (that the data
953 // for new and altered records is both in the cache and in the to_write
954 // or to_create collection)
955 if (_(this.to_create.concat(this.to_write)).find(function (record) {
956 return record.id === id; })) {
959 for(var i=0, len=this.cache.length; i<len; ++i) {
960 var record = this.cache[i];
961 // if record we call the button upon is in the cache
962 if (record.id === id) {
963 // evict it so it gets reloaded from server
964 this.cache.splice(i, 1);
969 call_button: function (method, args) {
970 this.evict_record(args[0][0]);
971 return this._super(method, args);
973 exec_workflow: function (id, signal) {
974 this.evict_record(id);
975 return this._super(id, signal);
977 alter_ids: function(n_ids) {
979 this.trigger("dataset_changed", n_ids);
982 instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
984 instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({
986 this._super.apply(this, arguments);
987 this.create_function = null;
988 this.write_function = null;
989 this.read_function = null;
990 this.default_get_function = null;
991 this.unlink_function = null;
993 read_ids: function (ids, fields, options) {
994 if (this.read_function) {
995 return this.read_function(ids, fields, options, this._super);
997 return this._super.apply(this, arguments);
1000 default_get: function(fields, options) {
1001 if (this.default_get_function) {
1002 return this.default_get_function(fields, options, this._super);
1004 return this._super.apply(this, arguments);
1007 create: function(data, options) {
1008 if (this.create_function) {
1009 return this.create_function(data, options, this._super);
1011 return this._super.apply(this, arguments);
1014 write: function (id, data, options) {
1015 if (this.write_function) {
1016 return this.write_function(id, data, options, this._super);
1018 return this._super.apply(this, arguments);
1021 unlink: function(ids) {
1022 if (this.unlink_function) {
1023 return this.unlink_function(ids, this._super);
1025 return this._super.apply(this, arguments);
1030 instance.web.CompoundContext = instance.web.Class.extend({
1032 this.__ref = "compound_context";
1033 this.__contexts = [];
1034 this.__eval_context = null;
1036 _.each(arguments, function(x) {
1040 add: function (context) {
1041 this.__contexts.push(context);
1044 set_eval_context: function (eval_context) {
1045 this.__eval_context = eval_context;
1048 get_eval_context: function () {
1049 return this.__eval_context;
1052 return instance.web.pyeval.eval('context', this, undefined, {no_user_context: true});
1056 instance.web.CompoundDomain = instance.web.Class.extend({
1058 this.__ref = "compound_domain";
1059 this.__domains = [];
1060 this.__eval_context = null;
1062 _.each(arguments, function(x) {
1066 add: function(domain) {
1067 this.__domains.push(domain);
1070 set_eval_context: function(eval_context) {
1071 this.__eval_context = eval_context;
1074 get_eval_context: function() {
1075 return this.__eval_context;
1078 return instance.web.pyeval.eval('domain', this);
1082 instance.web.DropMisordered = instance.web.Class.extend({
1084 * @constructs instance.web.DropMisordered
1085 * @extends instance.web.Class
1087 * @param {Boolean} [failMisordered=false] whether mis-ordered responses should be failed or just ignored
1089 init: function (failMisordered) {
1090 // local sequence number, for requests sent
1092 // remote sequence number, seqnum of last received request
1094 this.failMisordered = failMisordered || false;
1097 * Adds a deferred (usually an async request) to the sequencer
1099 * @param {$.Deferred} deferred to ensure add
1100 * @returns {$.Deferred}
1102 add: function (deferred) {
1103 var res = $.Deferred();
1105 var self = this, seq = this.lsn++;
1106 deferred.done(function () {
1107 if (seq > self.rsn) {
1109 res.resolve.apply(res, arguments);
1110 } else if (self.failMisordered) {
1113 }).fail(function () {
1114 res.reject.apply(res, arguments);
1117 return res.promise();
1123 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: