[IMP] web: web.form_relational: Change widgets to use AbstractManyField. Remove custom insertion in the DataSet to improve the managment of cache and the odoo commands list. this.get('value') return the list of ids and virtual ids and get_value return a commands list.

This commit is contained in:
Christophe Matthieu
2015-04-15 13:31:20 +02:00
parent 145beaf73f
commit 7d3cda215a
2 changed files with 234 additions and 238 deletions
@@ -16,7 +16,7 @@ var ViewManager = require('web.ViewManager');
var _t = core._t;
var QWeb = core.qweb;
var commands = common.commands;
var COMMANDS = common.commands;
var list_widget_registry = core.list_widget_registry;
var M2ODialog = Dialog.extend({
@@ -498,103 +498,222 @@ var AddAnItemList = ListView.List.extend({
/**
* A Abstract field for one2many and many2many field
* init: add dataset
* set_value: compute comands list into ids
* For all fields on2many or many2many:
* - this.get('value') contains a list of ids and virtual ids
* - get_value() return an odoo write command list
*/
var AbstractManyField = common.AbstractField.extend({
init: function(field_manager, node) {
var self = this;
this._super(field_manager, node);
this.dataset = new One2ManyDataSet(this, this.field.relation);
this.dataset = new One2ManyDataSet(this, this.field.relation, this.build_context());
this.dataset.o2m = this;
this.dataset.parent_view = this.view;
this.dataset.child_name = this.name;
this.set('value', []);
this.starting_ids = [];
this.view.on("load_record", this, function () {
self.starting_ids = self.get('value');
});
this.dataset.on('dataset_changed', this, function() {
self.dataset_changed();
// the editable lists change the dataset without call AbstractManyField methods
if (!this.internal_dataset_changed) {
this.trigger("change:commands");
}
});
this.on("change:commands", this, function () {
this.set({'value': this.dataset.ids.slice()});
});
},
dataset_changed: function() {
this.trigger('changed_value');
},
/**
* Set value use command to convert tuple of commands in id list
*/
set_value: function(value_) {
this.dataset.reset_ids([]);
var ids = this.get_ids_from_command(value_);
this._super(ids);
set_value: function(ids) {
ids = (ids || []).slice();
if (_.find(ids, function(id) { return typeof(id) === "string"; } )) {
throw new Error("set_value of '"+this.name+"' must receive an list of ids without virtual ids.", ids);
}
if (_.find(ids, function(id) { return typeof(id) !== "number"; } )) {
return this.send_commands(ids);
}
this.dataset.reset_ids(ids);
if (this.dataset.index === null && this.dataset.ids.length > 0) {
this.dataset.index = 0;
}
this.dataset_changed();
this.set({'value': ids});
},
// return id for this commands instead of the object ('ALL' to activate all conversion)
get_ids_from_command_list: ['LINK_TO', 'REPLACE_WITH'],
get_ids_from_command: function (value_) {
internal_set_value: function(ids) {
if (_.isEqual(ids, this.get("value"))) {
return;
}
var tmp = this.no_rerender;
this.no_rerender = true;
this.data_replace(ids.slice());
this.no_rerender = tmp;
},
/*
*@value: data {object} contains all value to send to the db
* options {object} options sent to the dataset (like the default values)
*@return deferred resolve with the created virtual id
*/
data_create: function (data, options) {
return this.send_commands([COMMANDS.create(data)], options);
},
/*
*@value: id {int or string} id or virtual id of the record to update
* data {object} contains all value to send to the db
* options {object} options sent to the dataset
*@return deferred
*/
data_update: function (id, data, options) {
return this.send_commands([COMMANDS.update(id, data)], options);
},
/*
*@value: id {int or string} id or virtual id of the record to add
* options {object} options sent to the dataset
*@return deferred
*/
data_link: function (id, options) {
return this.send_commands([COMMANDS.link_to(id)], options);
},
/*
*@value: ids {array} list of ids or virtual ids of the record to add
* options {object} options sent to the dataset
*@return deferred
*/
data_link_multi: function (ids, options) {
return this.send_commands(_.map(ids, function (id) { return COMMANDS.link_to(id); }), options);
},
/*
*@value: id {int or string} id or virtual id of the record to unlink or delete (function of field type)
*@return deferred
*/
data_delete: function (id) {
return this.send_commands([COMMANDS.delete(id)]);
},
/*
*@value: ids {array} list of ids or virtual ids of the record who replace the previous list
* options {object} options sent to the dataset
*@return deferred
*/
data_replace: function (ids, options) {
return this.send_commands([COMMANDS.replace_with(ids)], options);
},
/*
*@value: ids {array} list of ids or virtual ids of the record to read
* fields {array} list of the field to read
* options {object} options sent to the dataset
*@return deferred resolve with the records
*/
data_read: function (ids, fields, options) {
return this.dataset.read_ids(ids, fields, options);
},
/**
*Compute the write command list into the dataset
*@value: command_list {array} command list
* options {object} options for the datasets (eg: the default values)
*@return : deferred
*/
send_commands: function (command_list, options) {
var self = this;
var ids;
var command_list = [];
if (this.get_ids_from_command_list && this.get_ids_from_command_list !== 'ALL') {
for (var i=0; i<this.get_ids_from_command_list.length; i++) {
command_list[i] = commands[this.get_ids_from_command_list[i]];
var def = $.Deferred();
var mutex = new utils.Mutex();
var dataset = this.dataset;
var res = true;
options = options || {};
this.internal_dataset_changed = true;
_.each(command_list, function(command) {
mutex.exec(function() {
switch (command[0]) {
case COMMANDS.CREATE:
var data = _.clone(command[2]);
delete data.id;
return dataset.create(data, options).then(function (id) {
dataset.ids.push(id);
res = id;
});
case COMMANDS.UPDATE:
return dataset.write(command[1], command[2], options);
case COMMANDS.DELETE:
return dataset.unlink(command[1]);
case COMMANDS.LINK_TO:
if (dataset.ids.indexOf(command[1]) === -1) {
return dataset.add_ids([command[1]], options);
}
return;
case COMMANDS.DELETE_ALL:
return dataset.reset_ids([]);
case COMMANDS.REPLACE_WITH:
dataset.ids = [];
return dataset.alter_ids(command[2], options);
default:
throw new Error("send_commands to '"+self.name+"' receive a non command value.", command_list);
}
});
});
mutex.exec(function () {
def.resolve(res);
self.internal_dataset_changed = true;
self.trigger("change:commands");
});
return def;
},
/**
*return list of commands: create and update (and delete_all if need) (function of the field type)
*/
get_value: function() {
var self = this,
is_one2many = this.field.type === "one2many",
starting_ids = this.starting_ids.slice(),
replace_with_ids = [],
add_ids = [],
command_list = [],
id, index, alter_order;
_.each(this.get('value'), function (id) {
index = starting_ids.indexOf(id);
if (index !== -1) {
starting_ids.splice(index, 1);
}
if (alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;})) {
command_list.push(COMMANDS.create(alter_order.values));
return;
}
if (alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;})) {
command_list.push(COMMANDS.update(alter_order.id, alter_order.values));
return;
}
if (!is_one2many || self.dataset.delete_all) {
replace_with_ids.push(id);
} else {
command_list.push(COMMANDS.link_to(id));
}
});
if ((!is_one2many || self.dataset.delete_all) && (replace_with_ids.length || starting_ids.length)) {
command_list.unshift(COMMANDS.replace_with(replace_with_ids));
}
value_ = value_ || [];
if(value_.length >= 1 && value_[0] instanceof Array) {
ids = [];
_.each(value_, function(command) {
var obj = {values: command[2]};
if (command_list.length && command.indexOf(command_list) === -1) {
ids.push(obj);
}
switch (command[0]) {
case commands.CREATE:
obj.id = _.uniqueId(self.dataset.virtual_id_prefix);
obj.defaults = {};
self.dataset.to_create.push(obj);
self.dataset.cache.push(_.extend(_.clone(obj), {values: _.clone(command[2])}));
ids.push(obj.id);
return;
case commands.UPDATE:
obj.id = command[1];
self.dataset.to_write.push(obj);
self.dataset.cache.push(_.extend(_.clone(obj), {values: _.clone(command[2])}));
ids.push(obj.id);
return;
case commands.DELETE:
self.dataset.to_delete.push({id: command[1]});
return;
case commands.LINK_TO:
ids.push(command[1]);
return;
case commands.DELETE_ALL:
self.dataset.delete_all = true;
return;
case commands.REPLACE_WITH:
ids = command[2];
return;
}
});
return ids;
} else if (value_.length >= 1 && typeof(value_[0]) === "object") {
ids = [];
this.dataset.delete_all = true;
_.each(value_, function(command) {
var obj = {values: command};
obj.id = _.uniqueId(self.dataset.virtual_id_prefix);
obj.defaults = {};
self.dataset.to_create.push(obj);
self.dataset.cache.push(_.clone(obj));
ids.push(obj.id);
});
return ids;
} else {
return value_;
}
}
_.each(starting_ids, function(id) {
if (is_one2many) {
command_list.push(COMMANDS.delete(id));
} else if (is_one2many && !self.dataset.delete_all) {
command_list.push(COMMANDS.unlink(id));
}
});
return command_list;
},
is_false: function() {
return _(this.get('value')).isEmpty();
},
});
@@ -606,10 +725,13 @@ var FieldOne2Many = AbstractManyField.extend({
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.form_last_update = $.Deferred();
this.init_form_last_update = this.form_last_update;
this.is_started = false;
this.set_value([]);
this.on('change:commands', this, function () {
if (this.is_started && !this.no_rerender) {
this.reload_current_view();
}
});
},
start: function() {
this._super.apply(this, arguments);
@@ -665,13 +787,6 @@ var FieldOne2Many = AbstractManyField.extend({
reorderable: false,
});
}
} else if (view.view_type === "form") {
if (self.get("effective_readonly")) {
view.view_type = 'form';
}
_.extend(view.options, {
not_interactible_on_create: true,
});
} else if (view.view_type === "kanban") {
_.extend(view.options, {
confirm_on_delete: false,
@@ -691,9 +806,6 @@ var FieldOne2Many = AbstractManyField.extend({
this.viewmanager = new One2ManyViewManager(this, this.dataset, views, {});
this.viewmanager.o2m = self;
var once = $.Deferred().done(function() {
self.init_form_last_update.resolve();
});
var def = $.Deferred().done(function() {
self.initial_is_loaded.resolve();
});
@@ -712,14 +824,6 @@ var FieldOne2Many = AbstractManyField.extend({
return true;
});
}
} else if (view_type === "form") {
if (self.get("effective_readonly")) {
$(".oe_form_buttons", controller.$el).children().remove();
}
controller.on("load_record", self, function(){
once.resolve();
});
controller.on('pager_action_executed',self,self.save_any_view);
} else if (view_type == "graph") {
self.reload_current_view();
}
@@ -745,15 +849,6 @@ var FieldOne2Many = AbstractManyField.extend({
var view = self.get_active_view();
if (view.type === "list") {
return view.controller.reload_content();
} else if (view.type === "form") {
if (self.dataset.index === null && self.dataset.ids.length >= 1) {
self.dataset.index = 0;
}
var act = function() {
return view.controller.do_show();
};
self.form_last_update = self.form_last_update.then(act, act);
return self.form_last_update;
} else if (view.controller.do_search) {
return view.controller.do_search(self.build_domain(), self.dataset.get_context(), []);
}
@@ -766,35 +861,12 @@ var FieldOne2Many = AbstractManyField.extend({
*/
return (this.viewmanager && this.viewmanager.active_view);
},
get_ids_from_command_list: 'ALL',
set_value: function(value_) {
this._super(value_);
if (this.is_started && !this.no_rerender) {
return this.reload_current_view();
} else {
return $.when();
this.reload_current_view();
}
},
get_value: function() {
var self = this;
if (!this.dataset)
return [];
var val = this.dataset.delete_all ? [commands.delete_all()] : [];
val = val.concat(_.map(this.dataset.ids, function(id) {
var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;});
if (alter_order) {
return commands.create(alter_order.values);
}
alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;});
if (alter_order) {
return commands.update(alter_order.id, alter_order.values);
}
return commands.link_to(id);
}));
return val.concat(_.map(
this.dataset.to_delete, function(x) {
return commands['delete'](x.id);}));
},
commit_value: function() {
return this.save_any_view();
},
@@ -846,11 +918,6 @@ var One2ManyListView = ListView.extend({
}));
this.on('edit:after', this, this.proxy('_after_edit'));
this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
this.records
.bind('add', this.proxy("changed_records"))
.bind('remove', this.proxy("changed_records"));
this.on('save:after', this, this.proxy("changed_records"));
},
start: function () {
var ret = this._super();
@@ -859,9 +926,6 @@ var One2ManyListView = ListView.extend({
.on('mousedown.handleButtons', 'table button, div a.oe_m2o_cm_button', this.proxy('_button_down'));
return ret;
},
changed_records: function () {
this.o2m.dataset_changed();
},
is_valid: function () {
var self = this;
if (!this.fields_view || !this.editable()){
@@ -906,13 +970,10 @@ var One2ManyListView = ListView.extend({
initial_view: "form",
alternative_form_view: self.o2m.field.views ? self.o2m.field.views.form : undefined,
create_function: function(data, options) {
return self.o2m.dataset.create(data, options).done(function(r) {
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
self.o2m.dataset.trigger("dataset_changed", r);
});
return self.o2m.data_create(data, options);
},
read_function: function() {
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
read_function: function(ids, fields, options) {
return self.o2m.data_read(ids, fields, options);
},
parent_view: self.o2m.view,
child_name: self.o2m.name,
@@ -932,15 +993,15 @@ var One2ManyListView = ListView.extend({
pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
title: _t("Open: ") + self.o2m.string,
write_function: function(id, data) {
return self.o2m.dataset.write(id, data, {}).done(function() {
return self.o2m.data_update(id, data, {}).done(function() {
self.o2m.reload_current_view();
});
},
alternative_form_view: self.o2m.field.views ? self.o2m.field.views.form : undefined,
parent_view: self.o2m.view,
child_name: self.o2m.name,
read_function: function() {
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
read_function: function(ids, fields, options) {
return self.o2m.data_read(ids, fields, options);
},
form_view_options: {'not_interactible_on_create':true},
readonly: !this.is_action_enabled('edit') || self.o2m.get("effective_readonly")
@@ -1103,21 +1164,18 @@ var One2ManyViewManager = ViewManager.extend({
pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
title: _t("Open: ") + self.o2m.string,
create_function: function(data, options) {
return self.o2m.dataset.create(data, options).done(function(r) {
self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
self.o2m.dataset.trigger("dataset_changed", r);
});
return self.o2m.data_create(data, options);
},
write_function: function(id, data, options) {
return self.o2m.dataset.write(id, data, {}).done(function() {
return self.o2m.data_update(id, data, {}).done(function() {
self.o2m.reload_current_view();
});
},
alternative_form_view: self.o2m.field.views ? self.o2m.field.views.form : undefined,
parent_view: self.o2m.view,
child_name: self.o2m.name,
read_function: function() {
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
read_function: function(ids, fields, options) {
return self.o2m.data_read(ids, fields, options);
},
form_view_options: {'not_interactible_on_create':true},
readonly: self.o2m.get("effective_readonly")
@@ -1230,13 +1288,6 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
}
});
},
is_false: function() {
return _(this.get("value")).isEmpty();
},
get_value: function() {
var tmp = [commands.replace_with(this.get("value"))];
return tmp;
},
get_search_blacklist: function() {
return this.get("value");
},
@@ -1244,9 +1295,7 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
return _.map(data, function(el) {return {name: el[1], id:el[0]};});
},
get_render_data: function(ids){
var self = this;
var dataset = new data.DataSetStatic(this, this.field.relation, self.build_context());
return dataset.name_get(ids);
return this.dataset.name_get(ids);
},
render_tag: function(data) {
var self = this;
@@ -1260,7 +1309,6 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
},
render_value: function() {
var self = this;
var dataset = new data.DataSetStatic(this, this.field.relation, self.build_context());
var values = self.get("value");
var handle_names = function(data) {
if (self.isDestroyed())
@@ -1291,7 +1339,7 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c
width: width,
minHeight: height
});
},
},
_search_create_popup: function() {
this.ignore_blur = true;
return common.CompletionFieldMixin._search_create_popup.apply(this, arguments);
@@ -1350,22 +1398,13 @@ var FieldMany2Many = AbstractManyField.extend(common.ReinitializeFieldMixin, {
this.list_view.destroy();
this.list_view = undefined;
},
get_value: function() {
return [commands.replace_with(this.get('value'))];
},
is_false: function () {
return _(this.get("value")).isEmpty();
},
render_value: function() {
var self = this;
this.dataset.set_ids(this.get("value"));
//this.dataset.set_ids(this.get('value') || []);
this.render_value_dm.add(this.is_loaded).then(function() {
return self.list_view.reload_content();
});
},
dataset_changed: function() {
this.internal_set_value(this.dataset.ids);
},
}
});
var Many2ManyDataSet = data.DataSetStatic.extend({
@@ -1399,17 +1438,7 @@ var Many2ManyListView = ListView.extend(/** @lends instance.web.form.Many2ManyLi
);
var self = this;
pop.on("elements_selected", self, function(element_ids) {
var reload = false;
_(element_ids).each(function (id) {
if(! _.detect(self.dataset.ids, function(x) {return x == id;})) {
self.dataset.set_ids(self.dataset.ids.concat([id]));
self.m2m_field.dataset_changed();
reload = true;
}
});
if (reload) {
self.reload_content();
}
return self.o2m.data_link_multi(element_ids);
});
},
do_activate_record: function(index, id) {
@@ -1420,7 +1449,10 @@ var Many2ManyListView = ListView.extend(/** @lends instance.web.form.Many2ManyLi
alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views.form : undefined,
readonly: this.getParent().get("effective_readonly")
});
pop.on('write_completed', self, self.reload_content);
pop.on('write_completed', self, function () {
self.dataset.evict_record(id);
self.reload_content();
});
},
do_button_action: function(name, id, callback) {
var self = this;
@@ -1467,9 +1499,6 @@ var FieldMany2ManyKanban = AbstractManyField.extend(common.CompletionFieldMixin,
});
});
},
get_value: function() {
return [commands.replace_with(this.get('value'))];
},
load_view: function() {
var self = this;
var Many2ManyKanbanView = core.view_registry.get('many2many_kanban');
@@ -1503,9 +1532,6 @@ var FieldMany2ManyKanban = AbstractManyField.extend(common.CompletionFieldMixin,
return self.kanban_view.do_search(self.build_domain(), self.dataset.get_context(), []);
});
},
dataset_changed: function() {
this.set({'value': this.dataset.ids});
},
open_popup: function(type, unused) {
if (type !== "form")
return;
@@ -1522,13 +1548,7 @@ var FieldMany2ManyKanban = AbstractManyField.extend(common.CompletionFieldMixin,
this.build_context()
);
pop.on("elements_selected", self, function(element_ids) {
_.each(element_ids, function(one_id) {
if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
self.dataset_changed();
self.render_value();
}
});
return self.data_link_multi(element_ids);
});
} else {
var id = self.dataset.ids[self.dataset.index];
@@ -1536,7 +1556,7 @@ var FieldMany2ManyKanban = AbstractManyField.extend(common.CompletionFieldMixin,
pop.show_element(self.field.relation, id, self.build_context(), {
title: _t("Open: ") + self.string,
write_function: function(id, data, options) {
return self.dataset.write(id, data, {}).done(function() {
return self.data_update(id, data, {}).done(function() {
self.render_value();
});
},
@@ -1577,9 +1597,6 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ
initialize_content: function() {
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
},
get_value: function() {
return [commands.replace_with(this.get("value"))];
},
get_file_url: function (attachment) {
return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment.id});
},
@@ -1709,7 +1726,6 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField
className: "oe_form_many2many_checkboxes",
init: function() {
this._super.apply(this, arguments);
this.set("value", {});
this.set("records", []);
this.field_manager.on("view_content_has_changed", this, function() {
var domain = new data.CompoundDomain(this.build_domain()).eval();
@@ -1735,38 +1751,18 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField
});
},
render_value: function() {
this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this, selected: this.get("value")}));
this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this}));
var inputs = this.$("input");
inputs.change(_.bind(this.from_dom, this));
if (this.get("effective_readonly"))
inputs.attr("disabled", "true");
},
from_dom: function() {
var new_value = {};
this.$("input").each(function() {
var elem = $(this);
new_value[elem.data("record-id")] = elem.is(":checked") ? true : undefined;
});
if (! _.isEqual(new_value, this.get("value")))
var new_value = this.$("input:checked").map(function() { return +$(this).data("record-id"); }).get();
if (! _.isEqual(new_value, this.get("value"))) {
this.internal_set_value(new_value);
},
set_value: function(value_) {
var value_ = this.get_ids_from_command(value_);
var formatted = {};
_.each(value_, function(el) {
formatted[JSON.stringify(el)] = true;
});
this.set({'value': formatted});
},
get_value: function() {
var value = _.filter(_.keys(this.get("value")), function(el) {
return this.get("value")[el];
}, this);
value = _.map(value, function(el) {
return JSON.parse(el);
});
return [commands.replace_with(value)];
},
}
}
});
@@ -1778,7 +1774,7 @@ core.form_widget_registry
.add('one2many', FieldOne2Many)
.add('one2many_list', FieldOne2Many)
.add('many2many_binary', FieldMany2ManyBinaryMultiFiles)
.add('many2many_checkboxes', FieldMany2ManyCheckBoxes)
.add('many2many_checkboxes', FieldMany2ManyCheckBoxes);
return {
FieldMany2ManyTags: FieldMany2ManyTags,
+2 -2
View File
@@ -1932,10 +1932,10 @@
<t t-name="FieldMany2ManyCheckBoxes">
<t t-foreach="widget.get('records')" t-as="record">
<div>
<t t-if="selected[record[0]]">
<t t-if="widget.get('value').indexOf(record[0]) !== -1">
<input type="checkbox" t-att-data-record-id="JSON.stringify(record[0])" checked="true"/>
</t>
<t t-if="! selected[record[0]]">
<t t-if="widget.get('value').indexOf(record[0]) === -1">
<input type="checkbox" t-att-data-record-id="JSON.stringify(record[0])"/>
</t>
<t t-esc="record[1]"/>