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:
Parthiv Patel 2018-07-19 11:42:03 +00:00
commit d0c65d6989
9 changed files with 313 additions and 74 deletions

View File

@ -1542,6 +1542,8 @@ class ExcelExport(ExportFormat, http.Controller):
if isinstance(cell_value, pycompat.string_types): if isinstance(cell_value, pycompat.string_types):
cell_value = re.sub("\r", " ", pycompat.to_text(cell_value)) 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): elif isinstance(cell_value, datetime.datetime):
cell_style = datetime_style cell_style = datetime_style
elif isinstance(cell_value, datetime.date): elif isinstance(cell_value, datetime.date):

View File

@ -26,7 +26,7 @@ class Http(models.AbstractModel):
"session_id": request.session.sid, "session_id": request.session.sid,
"uid": request.session.uid, "uid": request.session.uid,
"is_system": request.env.user._is_system(), "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 {}, "user_context": request.session.get_context() if request.session.uid else {},
"db": request.session.db, "db": request.session.db,
"server_version": version_info.get('server_version'), "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, "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, "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, "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=''), "web.base.url": self.env['ir.config_parameter'].sudo().get_param('web.base.url', default=''),
} }

View File

@ -260,14 +260,15 @@ var QueryGroup = Class.extend({
var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count'; var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count';
var aggregates = {}; var aggregates = {};
_(fixed_group).each(function (value, key) { for (var key in fixed_group) {
if (key.indexOf('__') === 0 if (fixed_group.hasOwnProperty(key)) {
if (!(key.indexOf('__') === 0
|| _.contains(grouping_fields, key) || _.contains(grouping_fields, key)
|| (key === count_key)) { || (key === count_key))) {
return; aggregates[key] = fixed_group[key] || 0;
}
}
} }
aggregates[key] = value || 0;
});
this.model = new Model( this.model = new Model(
model, fixed_group.__context, fixed_group.__domain); model, fixed_group.__context, fixed_group.__domain);

View File

@ -814,7 +814,8 @@ var BasicModel = AbstractModel.extend({
return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options)); 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 {string} id local id for a resource
* @param {Object} [options] * @param {Object} [options]
@ -823,64 +824,7 @@ var BasicModel = AbstractModel.extend({
* @returns {Deferred<string>} resolves to the id of the resource * @returns {Deferred<string>} resolves to the id of the resource
*/ */
reload: function (id, options) { reload: function (id, options) {
options = options || {}; return this.mutex.exec(this._reload.bind(this, id, 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;
});
}, },
/** /**
* In some case, we may need to remove an element from a list, without going * In some case, we may need to remove an element from a list, without going
@ -2916,13 +2860,10 @@ var BasicModel = AbstractModel.extend({
continue; continue;
} }
changes = this._generateChanges(relRecord, options); changes = this._generateChanges(relRecord, options);
if (changes.id) { if (!this.isNew(relRecord.id)) {
// the subrecord already exists in db // 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)); commands[fieldName].push(x2ManyCommands.link_to(relRecord.res_id));
} delete changes.id;
if (!_.isEmpty(changes)) { if (!_.isEmpty(changes)) {
commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, 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 * 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 * record and fieldName. If the value in the cache was already the given

View File

@ -841,8 +841,16 @@ ListRenderer.include({
*/ */
_onTrashIconClick: function (event) { _onTrashIconClick: function (event) {
event.stopPropagation(); 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}); 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 * When a click happens outside the list view, or outside a currently

View File

@ -3206,6 +3206,136 @@ QUnit.module('relational_fields', {
form.destroy(); 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) { QUnit.test('onchange for embedded one2many with handle widget', function (assert) {
assert.expect(2); assert.expect(2);

View File

@ -3067,6 +3067,42 @@ QUnit.module('Views', {
form.destroy(); 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) { QUnit.test('properly apply onchange on many2many fields', function (assert) {
assert.expect(14); assert.expect(14);

View File

@ -2840,6 +2840,56 @@ QUnit.module('Views', {
testUtils.unpatch(mixins.ParentedMixin); 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();
});
}); });
}); });

View File

@ -145,6 +145,7 @@ var PagePropertiesDialog = widget.Dialog.extend({
horizontal: 'auto', horizontal: 'auto',
vertical: 'top', vertical: 'top',
}, },
widgetParent: 'body',
}; };
if (self.page.date_publish) { if (self.page.date_publish) {
datepickersOptions.defaultDate = time.str_to_datetime(self.page.date_publish); datepickersOptions.defaultDate = time.str_to_datetime(self.page.date_publish);