Merge branch 'master-chintan-19072018' into 'master-patch-july-2018'
Added Upstream Patch for web, website See merge request flectra-hq/flectra!103
This commit is contained in:
commit
d0c65d6989
@ -1542,6 +1542,8 @@ class ExcelExport(ExportFormat, http.Controller):
|
||||
|
||||
if isinstance(cell_value, pycompat.string_types):
|
||||
cell_value = re.sub("\r", " ", pycompat.to_text(cell_value))
|
||||
# Excel supports a maximum of 32767 characters in each cell:
|
||||
cell_value = cell_value[:32767]
|
||||
elif isinstance(cell_value, datetime.datetime):
|
||||
cell_style = datetime_style
|
||||
elif isinstance(cell_value, datetime.date):
|
||||
|
@ -26,7 +26,7 @@ class Http(models.AbstractModel):
|
||||
"session_id": request.session.sid,
|
||||
"uid": request.session.uid,
|
||||
"is_system": request.env.user._is_system(),
|
||||
"is_superuser": request.env.user._is_superuser(),
|
||||
"is_superuser": request.env.user._is_superuser() if request.session.uid else False,
|
||||
"user_context": request.session.get_context() if request.session.uid else {},
|
||||
"db": request.session.db,
|
||||
"server_version": version_info.get('server_version'),
|
||||
@ -36,7 +36,7 @@ class Http(models.AbstractModel):
|
||||
"company_id": request.env.user.company_id.id if request.session.uid else None,
|
||||
"partner_id": request.env.user.partner_id.id if request.session.uid and request.env.user.partner_id else None,
|
||||
"user_companies": {'current_company': (user.company_id.id, user.company_id.name), 'allowed_companies': [(comp.id, comp.name) for comp in user.company_ids]} if display_switch_company_menu else False,
|
||||
"currencies": self.get_currencies(),
|
||||
"currencies": self.get_currencies() if request.session.uid else {},
|
||||
"web.base.url": self.env['ir.config_parameter'].sudo().get_param('web.base.url', default=''),
|
||||
}
|
||||
|
||||
|
@ -260,14 +260,15 @@ var QueryGroup = Class.extend({
|
||||
|
||||
var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count';
|
||||
var aggregates = {};
|
||||
_(fixed_group).each(function (value, key) {
|
||||
if (key.indexOf('__') === 0
|
||||
for (var key in fixed_group) {
|
||||
if (fixed_group.hasOwnProperty(key)) {
|
||||
if (!(key.indexOf('__') === 0
|
||||
|| _.contains(grouping_fields, key)
|
||||
|| (key === count_key)) {
|
||||
return;
|
||||
|| (key === count_key))) {
|
||||
aggregates[key] = fixed_group[key] || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
aggregates[key] = value || 0;
|
||||
});
|
||||
|
||||
this.model = new Model(
|
||||
model, fixed_group.__context, fixed_group.__domain);
|
||||
|
@ -814,7 +814,8 @@ var BasicModel = AbstractModel.extend({
|
||||
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options));
|
||||
},
|
||||
/**
|
||||
* Reload all data for a given resource
|
||||
* Reload all data for a given resource. At any time there is at most one
|
||||
* reload operation active.
|
||||
*
|
||||
* @param {string} id local id for a resource
|
||||
* @param {Object} [options]
|
||||
@ -823,64 +824,7 @@ var BasicModel = AbstractModel.extend({
|
||||
* @returns {Deferred<string>} resolves to the id of the resource
|
||||
*/
|
||||
reload: function (id, options) {
|
||||
options = options || {};
|
||||
var element = this.localData[id];
|
||||
|
||||
if (element.type === 'record') {
|
||||
if (!options.currentId && (('currentId' in options) || this.isNew(id))) {
|
||||
var params = {
|
||||
context: element.context,
|
||||
fieldsInfo: element.fieldsInfo,
|
||||
fields: element.fields,
|
||||
viewType: element.viewType,
|
||||
};
|
||||
return this._makeDefaultRecord(element.model, params);
|
||||
}
|
||||
if (!options.keepChanges) {
|
||||
this.discardChanges(id, {rollback: false});
|
||||
}
|
||||
} else if (element._changes) {
|
||||
delete element.tempLimitIncrement;
|
||||
_.each(element._changes, function (change) {
|
||||
delete change.isNew;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.context !== undefined) {
|
||||
element.context = options.context;
|
||||
}
|
||||
if (options.domain !== undefined) {
|
||||
element.domain = options.domain;
|
||||
}
|
||||
if (options.groupBy !== undefined) {
|
||||
element.groupedBy = options.groupBy;
|
||||
}
|
||||
if (options.limit !== undefined) {
|
||||
element.limit = options.limit;
|
||||
}
|
||||
if (options.offset !== undefined) {
|
||||
this._setOffset(element.id, options.offset);
|
||||
}
|
||||
if (options.loadMoreOffset !== undefined) {
|
||||
element.loadMoreOffset = options.loadMoreOffset;
|
||||
} else {
|
||||
// reset if not specified
|
||||
element.loadMoreOffset = 0;
|
||||
}
|
||||
if (options.currentId !== undefined) {
|
||||
element.res_id = options.currentId;
|
||||
}
|
||||
if (options.ids !== undefined) {
|
||||
element.res_ids = options.ids;
|
||||
element.count = element.res_ids.length;
|
||||
}
|
||||
if (element.type === 'record') {
|
||||
element.offset = _.indexOf(element.res_ids, element.res_id);
|
||||
}
|
||||
var loadOptions = _.pick(options, 'fieldNames', 'viewType');
|
||||
return this._load(element, loadOptions).then(function (result) {
|
||||
return result.id;
|
||||
});
|
||||
return this.mutex.exec(this._reload.bind(this, id, options));
|
||||
},
|
||||
/**
|
||||
* In some case, we may need to remove an element from a list, without going
|
||||
@ -2916,13 +2860,10 @@ var BasicModel = AbstractModel.extend({
|
||||
continue;
|
||||
}
|
||||
changes = this._generateChanges(relRecord, options);
|
||||
if (changes.id) {
|
||||
if (!this.isNew(relRecord.id)) {
|
||||
// the subrecord already exists in db
|
||||
delete changes.id;
|
||||
if (this.isNew(record.id)) {
|
||||
// if the main record is new, link the subrecord to it
|
||||
commands[fieldName].push(x2ManyCommands.link_to(relRecord.res_id));
|
||||
}
|
||||
delete changes.id;
|
||||
if (!_.isEmpty(changes)) {
|
||||
commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, changes));
|
||||
}
|
||||
@ -4008,6 +3949,76 @@ var BasicModel = AbstractModel.extend({
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Reload all data for a given resource
|
||||
*
|
||||
* @private
|
||||
* @param {string} id local id for a resource
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.keepChanges=false] if true, doesn't discard the
|
||||
* changes on the record before reloading it
|
||||
* @returns {Deferred<string>} resolves to the id of the resource
|
||||
*/
|
||||
_reload: function (id, options) {
|
||||
options = options || {};
|
||||
var element = this.localData[id];
|
||||
|
||||
if (element.type === 'record') {
|
||||
if (!options.currentId && (('currentId' in options) || this.isNew(id))) {
|
||||
var params = {
|
||||
context: element.context,
|
||||
fieldsInfo: element.fieldsInfo,
|
||||
fields: element.fields,
|
||||
viewType: element.viewType,
|
||||
};
|
||||
return this._makeDefaultRecord(element.model, params);
|
||||
}
|
||||
if (!options.keepChanges) {
|
||||
this.discardChanges(id, {rollback: false});
|
||||
}
|
||||
} else if (element._changes) {
|
||||
delete element.tempLimitIncrement;
|
||||
_.each(element._changes, function (change) {
|
||||
delete change.isNew;
|
||||
});
|
||||
}
|
||||
|
||||
if (options.context !== undefined) {
|
||||
element.context = options.context;
|
||||
}
|
||||
if (options.domain !== undefined) {
|
||||
element.domain = options.domain;
|
||||
}
|
||||
if (options.groupBy !== undefined) {
|
||||
element.groupedBy = options.groupBy;
|
||||
}
|
||||
if (options.limit !== undefined) {
|
||||
element.limit = options.limit;
|
||||
}
|
||||
if (options.offset !== undefined) {
|
||||
this._setOffset(element.id, options.offset);
|
||||
}
|
||||
if (options.loadMoreOffset !== undefined) {
|
||||
element.loadMoreOffset = options.loadMoreOffset;
|
||||
} else {
|
||||
// reset if not specified
|
||||
element.loadMoreOffset = 0;
|
||||
}
|
||||
if (options.currentId !== undefined) {
|
||||
element.res_id = options.currentId;
|
||||
}
|
||||
if (options.ids !== undefined) {
|
||||
element.res_ids = options.ids;
|
||||
element.count = element.res_ids.length;
|
||||
}
|
||||
if (element.type === 'record') {
|
||||
element.offset = _.indexOf(element.res_ids, element.res_id);
|
||||
}
|
||||
var loadOptions = _.pick(options, 'fieldNames', 'viewType');
|
||||
return this._load(element, loadOptions).then(function (result) {
|
||||
return result.id;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Allows to save a value in the specialData cache associated to a given
|
||||
* record and fieldName. If the value in the cache was already the given
|
||||
|
@ -841,8 +841,16 @@ ListRenderer.include({
|
||||
*/
|
||||
_onTrashIconClick: function (event) {
|
||||
event.stopPropagation();
|
||||
var id = $(event.target).closest('tr').data('id');
|
||||
var $row = $(event.target).closest('tr')
|
||||
var id = $row.data('id');
|
||||
if ($row.hasClass('o_selected_row')) {
|
||||
this.trigger_up('list_record_delete', {id: id});
|
||||
} else {
|
||||
var self = this;
|
||||
this.unselectRow().then(function () {
|
||||
self.trigger_up('list_record_delete', {id: id});
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* When a click happens outside the list view, or outside a currently
|
||||
|
@ -3206,6 +3206,136 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange for embedded one2many in a one2many with a second page', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data.turtle.fields.partner_ids.type = 'one2many';
|
||||
this.data.turtle.records[0].partner_ids = [1];
|
||||
// we need a second page, so we set two records and only display one per page
|
||||
this.data.partner.records[0].turtles = [1, 2];
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
turtles: function (obj) {
|
||||
obj.turtles = [
|
||||
[5],
|
||||
[1, 1, {
|
||||
turtle_foo: "hop",
|
||||
partner_ids: [[5], [4, 1]],
|
||||
}],
|
||||
[1, 2, {
|
||||
turtle_foo: "blip",
|
||||
partner_ids: [[5], [4, 2], [4, 4]],
|
||||
}],
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="bottom" limit="1">' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'<field name="partner_ids" widget="many2many_tags"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
var expectedResultTurtles = [
|
||||
[1, 1, {
|
||||
turtle_foo: "hop",
|
||||
}],
|
||||
[1, 2, {
|
||||
partner_ids: [[4, 2, false], [4, 4, false]],
|
||||
turtle_foo: "blip",
|
||||
}],
|
||||
];
|
||||
assert.deepEqual(args.args[1].turtles, expectedResultTurtles,
|
||||
"the right values should be written");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_data_cell').eq(1).click();
|
||||
var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]');
|
||||
$cell.val("hop").trigger('change');
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange for embedded one2many in a one2many updated by server', function (assert) {
|
||||
// here we test that after an onchange, the embedded one2many field has
|
||||
// been updated by a new list of ids by the server response, to this new
|
||||
// list should be correctly sent back at save time
|
||||
assert.expect(3);
|
||||
|
||||
this.data.turtle.fields.partner_ids.type = 'one2many';
|
||||
this.data.partner.records[0].turtles = [2];
|
||||
this.data.turtle.records[1].partner_ids = [2];
|
||||
|
||||
this.data.partner.onchanges = {
|
||||
turtles: function (obj) {
|
||||
obj.turtles = [
|
||||
[5],
|
||||
[1, 2, {
|
||||
turtle_foo: "hop",
|
||||
partner_ids: [[5], [4, 2], [4, 4]],
|
||||
}],
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'<field name="partner_ids" widget="many2many_tags"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/call_kw/partner/write') {
|
||||
var expectedResultTurtles = [
|
||||
[1, 2, {
|
||||
partner_ids: [[4, 2, false], [4, 4, false]],
|
||||
turtle_foo: "hop",
|
||||
}],
|
||||
];
|
||||
assert.deepEqual(args.args[1].turtles, expectedResultTurtles,
|
||||
'The right values should be written');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim(), "second record",
|
||||
"the partner_ids should be as specified at initialization");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_data_cell').eq(1).click();
|
||||
var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]');
|
||||
$cell.val("hop").trigger("change");
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim().split(/\s+/),
|
||||
[ "second", "record", "aaa" ],
|
||||
'The partner_ids should have been updated');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange for embedded one2many with handle widget', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
@ -3067,6 +3067,42 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('delete a line in a one2many while editing another line triggers a warning', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.records[0].p = [1, 2];
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="p">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="display_name" required="True"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_data_cell').first().click(); // edit first row
|
||||
form.$('input').val('').trigger('input');
|
||||
form.$('.fa-trash-o').eq(1).click(); // delete second row
|
||||
|
||||
assert.strictEqual($('.modal').find('.modal-title').first().text(), "Warning",
|
||||
"Clicking out of a dirty line while editing should trigger a warning modal.");
|
||||
|
||||
$('.modal').find('.btn-primary').click(); // discard changes
|
||||
|
||||
assert.strictEqual(form.$('.o_data_cell').first().text(), "first record",
|
||||
"Value should have been reset to what it was before editing began.");
|
||||
assert.strictEqual(form.$('.o_data_row').length, 1,
|
||||
"The other line should have been deleted.");
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('properly apply onchange on many2many fields', function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
|
@ -2840,6 +2840,56 @@ QUnit.module('Views', {
|
||||
testUtils.unpatch(mixins.ParentedMixin);
|
||||
});
|
||||
|
||||
QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', function (assert) {
|
||||
// in this test, we simulate that clearing the domain is slow, so that
|
||||
// clearing the groupby does not corrupt the data handled while
|
||||
// reloading the kanban view.
|
||||
assert.expect(4);
|
||||
|
||||
var def = $.Deferred();
|
||||
|
||||
var kanban = createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban class="o_kanban_test">' +
|
||||
'<field name="bar"/>' +
|
||||
'<templates><t t-name="kanban-box">' +
|
||||
'<div><field name="foo"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
domain: [['foo', '=', 'norecord']],
|
||||
groupBy: ['bar'],
|
||||
mockRPC: function (route, args) {
|
||||
var result = this._super(route, args);
|
||||
if (args.method === 'read_group') {
|
||||
var isFirstUpdate = _.isEmpty(args.kwargs.domain) &&
|
||||
args.kwargs.groupby &&
|
||||
args.kwargs.groupby[0] === 'bar';
|
||||
if (isFirstUpdate) {
|
||||
return def.then(_.constant(result));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'),
|
||||
"the kanban view should be grouped");
|
||||
assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'),
|
||||
"the kanban view should not be ungrouped");
|
||||
|
||||
kanban.update({domain: []}); // 1st update on kanban view
|
||||
kanban.update({groupBy: false}); // 2n update on kanban view
|
||||
def.resolve(); // simulate slow 1st update of kanban view
|
||||
|
||||
assert.notOk(kanban.$('.o_kanban_view').hasClass('o_kanban_grouped'),
|
||||
"the kanban view should not longer be grouped");
|
||||
assert.ok(kanban.$('.o_kanban_view').hasClass('o_kanban_ungrouped'),
|
||||
"the kanban view should have become ungrouped");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -145,6 +145,7 @@ var PagePropertiesDialog = widget.Dialog.extend({
|
||||
horizontal: 'auto',
|
||||
vertical: 'top',
|
||||
},
|
||||
widgetParent: 'body',
|
||||
};
|
||||
if (self.page.date_publish) {
|
||||
datepickersOptions.defaultDate = time.str_to_datetime(self.page.date_publish);
|
||||
|
Loading…
Reference in New Issue
Block a user