[MERGE] forward port branch 11.0 up to 8c64159b88
This commit is contained in:
@@ -144,7 +144,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="payment_type" widget="radio" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
<field name="payment_type" widget="radio" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
<field name="partner_type" widget="selection" attrs="{'required': [('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}"/>
|
<field name="partner_type" widget="selection" attrs="{'required': [('state', '=', 'draft'), ('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
<field name="partner_id" attrs="{'required': [('state', '=', 'draft'), ('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_supplier': payment_type == 'outbound', 'default_customer': payment_type == 'inbound'}"/>
|
<field name="partner_id" attrs="{'required': [('state', '=', 'draft'), ('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_supplier': payment_type == 'outbound', 'default_customer': payment_type == 'inbound'}"/>
|
||||||
<label for="amount"/>
|
<label for="amount"/>
|
||||||
<div name="amount_div" class="o_row">
|
<div name="amount_div" class="o_row">
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class AccountPayment(models.Model):
|
|||||||
_inherit = "account.payment"
|
_inherit = "account.payment"
|
||||||
|
|
||||||
check_amount_in_words = fields.Char(string="Amount in Words")
|
check_amount_in_words = fields.Char(string="Amount in Words")
|
||||||
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
|
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=1)
|
||||||
check_number = fields.Integer(string="Check Number", readonly=True, copy=False,
|
check_number = fields.Integer(string="Check Number", readonly=True, copy=False,
|
||||||
help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers "
|
help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers "
|
||||||
"or if the current numbering is wrong, you can change it in the journal configuration page.")
|
"or if the current numbering is wrong, you can change it in the journal configuration page.")
|
||||||
@@ -72,10 +72,10 @@ class AccountPayment(models.Model):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
if vals['payment_method_id'] == self.env.ref('account_check_printing.account_payment_method_check').id\
|
if vals['payment_method_id'] == self.env.ref('account_check_printing.account_payment_method_check').id:
|
||||||
and vals.get('check_manual_sequencing'):
|
journal = self.env['account.journal'].browse(vals['journal_id'])
|
||||||
sequence = self.env['account.journal'].browse(vals['journal_id']).check_sequence_id
|
if journal.check_manual_sequencing:
|
||||||
vals.update({'check_number': sequence.next_by_id()})
|
vals.update({'check_number': journal.check_sequence_id.next_by_id()})
|
||||||
return super(AccountPayment, self).create(vals)
|
return super(AccountPayment, self).create(vals)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -146,10 +146,10 @@ FormController.include({
|
|||||||
* @param {Object} activeBarcode: options sent by the field who use barcode features
|
* @param {Object} activeBarcode: options sent by the field who use barcode features
|
||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode) {
|
_barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode, quantity) {
|
||||||
var changes = {};
|
var changes = {};
|
||||||
var candidateChanges = {};
|
var candidateChanges = {};
|
||||||
candidateChanges[activeBarcode.quantity] = candidate.data[activeBarcode.quantity] + 1;
|
candidateChanges[activeBarcode.quantity] = quantity ? quantity : candidate.data[activeBarcode.quantity] + 1;
|
||||||
changes[activeBarcode.fieldName] = {
|
changes[activeBarcode.fieldName] = {
|
||||||
operation: 'UPDATE',
|
operation: 'UPDATE',
|
||||||
id: candidate.id,
|
id: candidate.id,
|
||||||
@@ -283,15 +283,14 @@ FormController.include({
|
|||||||
function (reserved) {return barcode.indexOf(reserved) === 0;});
|
function (reserved) {return barcode.indexOf(reserved) === 0;});
|
||||||
var hasCommand = false;
|
var hasCommand = false;
|
||||||
var defs = [];
|
var defs = [];
|
||||||
|
if (! $.contains(target, self.el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (var k in self.activeBarcode) {
|
for (var k in self.activeBarcode) {
|
||||||
var activeBarcode = self.activeBarcode[k];
|
var activeBarcode = self.activeBarcode[k];
|
||||||
// Handle the case where there are several barcode widgets on the same page. Since the
|
// Handle the case where there are several barcode widgets on the same page. Since the
|
||||||
// event is global on the page, all barcode widgets will be triggered. However, we only
|
// event is global on the page, all barcode widgets will be triggered. However, we only
|
||||||
// want to keep the event on the target widget.
|
// want to keep the event on the target widget.
|
||||||
if (! $.contains(target, self.el)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var methods = self.activeBarcode[k].commands;
|
var methods = self.activeBarcode[k].commands;
|
||||||
var method = prefixed ? methods[barcode] : methods.barcode;
|
var method = prefixed ? methods[barcode] : methods.barcode;
|
||||||
if (method) {
|
if (method) {
|
||||||
@@ -306,6 +305,8 @@ FormController.include({
|
|||||||
}
|
}
|
||||||
return self.alive($.when.apply($, defs)).then(function () {
|
return self.alive($.when.apply($, defs)).then(function () {
|
||||||
if (!prefixed) {
|
if (!prefixed) {
|
||||||
|
// remember the barcode scanned for the quantity listener
|
||||||
|
self.current_barcode = barcode;
|
||||||
// redraw the view if we scanned a real barcode (required if
|
// redraw the view if we scanned a real barcode (required if
|
||||||
// we manually apply the change in JS, e.g. incrementing the
|
// we manually apply the change in JS, e.g. incrementing the
|
||||||
// quantity)
|
// quantity)
|
||||||
@@ -321,6 +322,9 @@ FormController.include({
|
|||||||
_quantityListener: function (event) {
|
_quantityListener: function (event) {
|
||||||
var character = String.fromCharCode(event.which);
|
var character = String.fromCharCode(event.which);
|
||||||
|
|
||||||
|
if (! $.contains(event.target, this.el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// only catch the event if we're not focused in
|
// only catch the event if we're not focused in
|
||||||
// another field and it's a number
|
// another field and it's a number
|
||||||
if (!$(event.target).is('body, .modal') || !/[0-9]/.test(character)) {
|
if (!$(event.target).is('body, .modal') || !/[0-9]/.test(character)) {
|
||||||
@@ -355,9 +359,10 @@ FormController.include({
|
|||||||
title: _t('Set quantity'),
|
title: _t('Set quantity'),
|
||||||
buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () {
|
buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () {
|
||||||
var new_qty = this.$content.find('.o_set_qty_input').val();
|
var new_qty = this.$content.find('.o_set_qty_input').val();
|
||||||
var values = {};
|
var record = self.model.get(self.handle);
|
||||||
values[activeBarcode.quantity] = parseFloat(new_qty);
|
return self._barcodeSelectedCandidate(activeBarcode.candidate, record,
|
||||||
return self.model.notifyChanges(activeBarcode.candidate.id, values).then(function () {
|
self.current_barcode, activeBarcode, parseFloat(new_qty))
|
||||||
|
.then(function () {
|
||||||
self.update({}, {reload: false});
|
self.update({}, {reload: false});
|
||||||
});
|
});
|
||||||
}}, {text: _t('Discard'), close: true}],
|
}}, {text: _t('Discard'), close: true}],
|
||||||
|
|||||||
@@ -404,4 +404,136 @@ QUnit.test('specification of widget barcode_handler', function (assert) {
|
|||||||
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay;
|
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay;
|
||||||
delete fieldRegistry.map.test_barcode_handler;
|
delete fieldRegistry.map.test_barcode_handler;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('specification of widget barcode_handler with keypress and notifyChange', function (assert) {
|
||||||
|
assert.expect(6);
|
||||||
|
var done = assert.async();
|
||||||
|
|
||||||
|
var delay = barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms;
|
||||||
|
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = 0;
|
||||||
|
|
||||||
|
this.data.order.onchanges = {
|
||||||
|
_barcode_scanned: function () {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define a specific barcode_handler widget for this test case
|
||||||
|
var TestBarcodeHandler = AbstractField.extend({
|
||||||
|
init: function () {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
|
||||||
|
this.trigger_up('activeBarcode', {
|
||||||
|
name: 'test',
|
||||||
|
fieldName: 'line_ids',
|
||||||
|
notifyChange: false,
|
||||||
|
setQuantityWithKeypress: true,
|
||||||
|
quantity: 'quantity',
|
||||||
|
commands: {
|
||||||
|
barcode: '_barcodeAddX2MQuantity',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fieldRegistry.add('test_barcode_handler', TestBarcodeHandler);
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'order',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<form>' +
|
||||||
|
'<field name="_barcode_scanned" widget="test_barcode_handler"/>' +
|
||||||
|
'<field name="line_ids">' +
|
||||||
|
'<tree>' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'<field name="product_barcode" invisible="1"/>' +
|
||||||
|
'<field name="quantity"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'</field>' +
|
||||||
|
'</form>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
assert.step(args.method);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
res_id: 1,
|
||||||
|
viewOptions: {
|
||||||
|
mode: 'edit',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
_.each(['1','2','3','4','5','6','7','8','9','0','Enter'], triggerKeypressEvent);
|
||||||
|
// Quantity listener should open a dialog.
|
||||||
|
triggerKeypressEvent('5');
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
var keycode = $.ui.keyCode.ENTER;
|
||||||
|
|
||||||
|
assert.strictEqual($('.modal .modal-body').length, 1, 'should open a modal with a quantity as input');
|
||||||
|
assert.strictEqual($('.modal .modal-body .o_set_qty_input').val(), '5', 'the quantity by default in the modal shoud be 5');
|
||||||
|
|
||||||
|
$('.modal .modal-body .o_set_qty_input').val('7');
|
||||||
|
|
||||||
|
$('.modal .modal-body .o_set_qty_input').trigger($.Event('keypress', {which: keycode, keyCode: keycode}));
|
||||||
|
assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(1)').text(), '7',
|
||||||
|
"quantity checked should be 7");
|
||||||
|
|
||||||
|
assert.verifySteps(['read', 'read']);
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay;
|
||||||
|
delete fieldRegistry.map.test_barcode_handler;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
QUnit.test('barcode_scanned only trigger error for active view', function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
this.data.order_line.fields._barcode_scanned = {string: 'Barcode scanned', type: 'char'};
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'order',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<form>' +
|
||||||
|
'<field name="_barcode_scanned" widget="barcode_handler"/>' +
|
||||||
|
'<field name="line_ids">' +
|
||||||
|
'<tree>' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'<field name="product_barcode" invisible="1"/>' +
|
||||||
|
'<field name="quantity"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'</field>' +
|
||||||
|
'</form>',
|
||||||
|
archs: {
|
||||||
|
"order_line,false,form":
|
||||||
|
'<form string="order line">' +
|
||||||
|
'<field name="_barcode_scanned" widget="barcode_handler"/>' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'</form>',
|
||||||
|
},
|
||||||
|
res_id: 1,
|
||||||
|
intercepts: {
|
||||||
|
warning: function (event) {
|
||||||
|
assert.step(event.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
viewOptions: {
|
||||||
|
mode: 'edit',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
form.$('.o_data_row:first').click();
|
||||||
|
|
||||||
|
// We do not trigger on the body since modal and
|
||||||
|
// form view are both inside it.
|
||||||
|
function modalTriggerKeypressEvent(char) {
|
||||||
|
var keycode;
|
||||||
|
if (char === "Enter") {
|
||||||
|
keycode = $.ui.keyCode.ENTER;
|
||||||
|
} else {
|
||||||
|
keycode = char.charCodeAt(0);
|
||||||
|
}
|
||||||
|
return $('.modal').trigger($.Event('keypress', {which: keycode, keyCode: keycode}));
|
||||||
|
}
|
||||||
|
_.each(['O','-','B','T','N','.','c','a','n','c','e','l','Enter'], modalTriggerKeypressEvent);
|
||||||
|
assert.verifySteps(['warning'], "only one event should be triggered");
|
||||||
|
form.destroy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ FormView.include({
|
|||||||
*/
|
*/
|
||||||
init: function (viewInfo) {
|
init: function (viewInfo) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.controllerParams.viewID = viewInfo.view_id;
|
this.controllerParams.customViewID = viewInfo.custom_view_id;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ FormController.include({
|
|||||||
}),
|
}),
|
||||||
init: function (parent, model, renderer, params) {
|
init: function (parent, model, renderer, params) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.viewID = params.viewID;
|
this.customViewID = params.customViewID;
|
||||||
},
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -63,9 +63,9 @@ FormController.include({
|
|||||||
var board = this.renderer.getBoard();
|
var board = this.renderer.getBoard();
|
||||||
var arch = QWeb.render('DashBoard.xml', _.extend({}, board));
|
var arch = QWeb.render('DashBoard.xml', _.extend({}, board));
|
||||||
return this._rpc({
|
return this._rpc({
|
||||||
route: '/web/view/add_custom',
|
route: '/web/view/edit_custom',
|
||||||
params: {
|
params: {
|
||||||
view_id: this.viewID,
|
custom_id: this.customViewID,
|
||||||
arch: arch,
|
arch: arch,
|
||||||
}
|
}
|
||||||
}).then(dataManager.invalidate.bind(dataManager));
|
}).then(dataManager.invalidate.bind(dataManager));
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
|||||||
if (route === '/web/dataset/search_read') {
|
if (route === '/web/dataset/search_read') {
|
||||||
assert.deepEqual(args.domain, [['foo', '!=', 'False']], "the domain should be passed");
|
assert.deepEqual(args.domain, [['foo', '!=', 'False']], "the domain should be passed");
|
||||||
}
|
}
|
||||||
if (route === '/web/view/add_custom') {
|
if (route === '/web/view/edit_custom') {
|
||||||
assert.step('add custom');
|
assert.step('edit custom');
|
||||||
return $.when(true);
|
return $.when(true);
|
||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
@@ -156,7 +156,7 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
|||||||
form.$('.oe_fold').click();
|
form.$('.oe_fold').click();
|
||||||
|
|
||||||
assert.ok(form.$('.oe_content').is(':visible'), "content is visible again");
|
assert.ok(form.$('.oe_content').is(':visible'), "content is visible again");
|
||||||
assert.verifySteps(['load action', 'add custom', 'add custom']);
|
assert.verifySteps(['load action', 'edit custom', 'edit custom']);
|
||||||
|
|
||||||
assert.strictEqual($('.modal').length, 0, "should have no modal open");
|
assert.strictEqual($('.modal').length, 0, "should have no modal open");
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
|
|||||||
assert.strictEqual($('.modal').length, 0, "should have no modal open");
|
assert.strictEqual($('.modal').length, 0, "should have no modal open");
|
||||||
assert.strictEqual(form.$('.oe_action').length, 0, "should have no displayed action");
|
assert.strictEqual(form.$('.oe_action').length, 0, "should have no displayed action");
|
||||||
|
|
||||||
assert.verifySteps(['load action', 'add custom', 'add custom', 'add custom', 'add custom']);
|
assert.verifySteps(['load action', 'edit custom', 'edit custom', 'edit custom', 'edit custom']);
|
||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -293,8 +293,8 @@ QUnit.test('can drag and drop a view', function (assert) {
|
|||||||
views: [[4, 'list']],
|
views: [[4, 'list']],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (route === '/web/view/add_custom') {
|
if (route === '/web/view/edit_custom') {
|
||||||
assert.step('add custom');
|
assert.step('edit custom');
|
||||||
return $.when(true);
|
return $.when(true);
|
||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
@@ -340,8 +340,8 @@ QUnit.test('twice the same action in a dashboard', function (assert) {
|
|||||||
views: [[4, 'list'],[5, 'kanban']],
|
views: [[4, 'list'],[5, 'kanban']],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (route === '/web/view/add_custom') {
|
if (route === '/web/view/edit_custom') {
|
||||||
assert.step('add custom');
|
assert.step('edit custom');
|
||||||
return $.when(true);
|
return $.when(true);
|
||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
|
|||||||
@@ -617,13 +617,21 @@ class Meeting(models.Model):
|
|||||||
event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
|
event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone
|
||||||
rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True, tzinfos={})
|
rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True, tzinfos={})
|
||||||
recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])
|
recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])
|
||||||
for meeting in recurring_meetings:
|
|
||||||
recurring_date = fields.Datetime.from_string(meeting.recurrent_id_date)
|
# We handle a maximum of 50,000 meetings at a time, and clear the cache at each step to
|
||||||
if use_naive_datetime:
|
# control the memory usage.
|
||||||
recurring_date = recurring_date.replace(tzinfo=None)
|
invalidate = False
|
||||||
else:
|
for meetings in self.env.cr.split_for_in_conditions(recurring_meetings, size=50000):
|
||||||
recurring_date = todate(meeting.recurrent_id_date)
|
if invalidate:
|
||||||
rset1.exdate(recurring_date)
|
self.invalidate_cache()
|
||||||
|
for meeting in meetings:
|
||||||
|
recurring_date = fields.Datetime.from_string(meeting.recurrent_id_date)
|
||||||
|
if use_naive_datetime:
|
||||||
|
recurring_date = recurring_date.replace(tzinfo=None)
|
||||||
|
else:
|
||||||
|
recurring_date = todate(meeting.recurrent_id_date)
|
||||||
|
rset1.exdate(recurring_date)
|
||||||
|
invalidate = True
|
||||||
return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1]
|
return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1]
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -1148,6 +1156,7 @@ class Meeting(models.Model):
|
|||||||
def key(record):
|
def key(record):
|
||||||
# we need to deal with undefined fields, as sorted requires an homogeneous iterable
|
# we need to deal with undefined fields, as sorted requires an homogeneous iterable
|
||||||
def boolean_product(x):
|
def boolean_product(x):
|
||||||
|
x = False if (isinstance(x, models.Model) and not x) else x
|
||||||
if isinstance(x, bool):
|
if isinstance(x, bool):
|
||||||
return (x, x)
|
return (x, x)
|
||||||
return (True, x)
|
return (True, x)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class FleetVehicle(models.Model):
|
|||||||
license_plate = fields.Char(required=True, track_visibility="onchange",
|
license_plate = fields.Char(required=True, track_visibility="onchange",
|
||||||
help='License plate number of the vehicle (i = plate number for a car)')
|
help='License plate number of the vehicle (i = plate number for a car)')
|
||||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle', copy=False)
|
driver_id = fields.Many2one('res.partner', 'Driver', track_visibility="onchange", help='Driver of the vehicle', copy=False)
|
||||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||||
|
|||||||
@@ -139,19 +139,6 @@ class FleetVehicleModel(models.Model):
|
|||||||
for model in self:
|
for model in self:
|
||||||
model.default_total_depreciated_cost = model.co2_fee + model.default_recurring_cost_amount_depreciated
|
model.default_total_depreciated_cost = model.co2_fee + model.default_recurring_cost_amount_depreciated
|
||||||
|
|
||||||
@api.multi
|
|
||||||
@api.depends('name', 'brand_id', 'default_total_depreciated_cost')
|
|
||||||
def name_get(self):
|
|
||||||
res = super(FleetVehicleModel, self).name_get()
|
|
||||||
new_res = []
|
|
||||||
for res_item in res:
|
|
||||||
model = self.browse(res_item[0])
|
|
||||||
if model.default_total_depreciated_cost != 0.0:
|
|
||||||
new_res.append((res_item[0], res_item[1] + u" \u2022 " + str(round(model.default_total_depreciated_cost, 2))))
|
|
||||||
else:
|
|
||||||
new_res.append(res_item)
|
|
||||||
return new_res
|
|
||||||
|
|
||||||
@api.depends('default_co2')
|
@api.depends('default_co2')
|
||||||
def _compute_co2_fee(self):
|
def _compute_co2_fee(self):
|
||||||
for model in self:
|
for model in self:
|
||||||
|
|||||||
@@ -680,7 +680,7 @@ class Channel(models.Model):
|
|||||||
msg += _(" This channel is private. People must be invited to join it.")
|
msg += _(" This channel is private. People must be invited to join it.")
|
||||||
else:
|
else:
|
||||||
channel_partners = self.env['mail.channel.partner'].search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
|
channel_partners = self.env['mail.channel.partner'].search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
|
||||||
msg = _("You are in a private conversation with <b>@%s</b>.") % channel_partners[0].partner_id.name
|
msg = _("You are in a private conversation with <b>@%s</b>.") % (channel_partners[0].partner_id.name if channel_partners else _('Anonymous'))
|
||||||
msg += _("""<br><br>
|
msg += _("""<br><br>
|
||||||
You can mention someone by typing <b>@username</b>, this will grab its attention.<br>
|
You can mention someone by typing <b>@username</b>, this will grab its attention.<br>
|
||||||
You can mention a channel by typing <b>#channel</b>.<br>
|
You can mention a channel by typing <b>#channel</b>.<br>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class TestMassMailing(models.TransientModel):
|
|||||||
'reply_to': mailing.reply_to,
|
'reply_to': mailing.reply_to,
|
||||||
'email_to': test_mail,
|
'email_to': test_mail,
|
||||||
'subject': mailing.name,
|
'subject': mailing.name,
|
||||||
'body_html': mailing.body_html,
|
'body_html': tools.html_sanitize(mailing.body_html, sanitize_attributes=True, sanitize_style=True, strip_classes=True),
|
||||||
'notification': True,
|
'notification': True,
|
||||||
'mailing_id': mailing.id,
|
'mailing_id': mailing.id,
|
||||||
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
|
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
|
||||||
|
|||||||
@@ -345,28 +345,25 @@ class MrpWorkorder(models.Model):
|
|||||||
# If last work order, then post lots used
|
# If last work order, then post lots used
|
||||||
# TODO: should be same as checking if for every workorder something has been done?
|
# TODO: should be same as checking if for every workorder something has been done?
|
||||||
if not self.next_work_order_id:
|
if not self.next_work_order_id:
|
||||||
production_moves = self.production_id.move_finished_ids.filtered(lambda x: (x.state not in ('done', 'cancel')))
|
production_move = self.production_id.move_finished_ids.filtered(
|
||||||
for production_move in production_moves:
|
lambda x: (x.product_id.id == self.production_id.product_id.id) and (x.state not in ('done', 'cancel')))
|
||||||
if production_move.product_id.id == self.production_id.product_id.id and production_move.has_tracking != 'none':
|
if production_move.product_id.tracking != 'none':
|
||||||
move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id)
|
move_line = production_move.move_line_ids.filtered(lambda x: x.lot_id.id == self.final_lot_id.id)
|
||||||
if move_line:
|
if move_line:
|
||||||
move_line.product_uom_qty += self.qty_producing
|
move_line.product_uom_qty += self.qty_producing
|
||||||
else:
|
|
||||||
move_line.create({'move_id': production_move.id,
|
|
||||||
'product_id': production_move.product_id.id,
|
|
||||||
'lot_id': self.final_lot_id.id,
|
|
||||||
'product_uom_qty': self.qty_producing,
|
|
||||||
'product_uom_id': production_move.product_uom.id,
|
|
||||||
'qty_done': self.qty_producing,
|
|
||||||
'workorder_id': self.id,
|
|
||||||
'location_id': production_move.location_id.id,
|
|
||||||
'location_dest_id': production_move.location_dest_id.id,
|
|
||||||
})
|
|
||||||
elif production_move.unit_factor:
|
|
||||||
rounding = production_move.product_uom.rounding
|
|
||||||
production_move.quantity_done += float_round(self.qty_producing * production_move.unit_factor, precision_rounding=rounding)
|
|
||||||
else:
|
else:
|
||||||
production_move.quantity_done += self.qty_producing
|
move_line.create({'move_id': production_move.id,
|
||||||
|
'product_id': production_move.product_id.id,
|
||||||
|
'lot_id': self.final_lot_id.id,
|
||||||
|
'product_uom_qty': self.qty_producing,
|
||||||
|
'product_uom_id': production_move.product_uom.id,
|
||||||
|
'qty_done': self.qty_producing,
|
||||||
|
'workorder_id': self.id,
|
||||||
|
'location_id': production_move.location_id.id,
|
||||||
|
'location_dest_id': production_move.location_dest_id.id,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
production_move.quantity_done += self.qty_producing
|
||||||
|
|
||||||
if not self.next_work_order_id:
|
if not self.next_work_order_id:
|
||||||
for by_product_move in self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))):
|
for by_product_move in self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel'))):
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class PadCommon(models.AbstractModel):
|
|||||||
@api.model
|
@api.model
|
||||||
def pad_get_content(self, url):
|
def pad_get_content(self, url):
|
||||||
company = self.env.user.sudo().company_id
|
company = self.env.user.sudo().company_id
|
||||||
myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api')
|
myPad = EtherpadLiteClient(company.pad_key, (company.pad_server or '') + '/api')
|
||||||
content = ''
|
content = ''
|
||||||
if url:
|
if url:
|
||||||
split_url = url.split('/p/')
|
split_url = url.split('/p/')
|
||||||
@@ -132,7 +132,7 @@ class PadCommon(models.AbstractModel):
|
|||||||
for k, field in self._fields.items():
|
for k, field in self._fields.items():
|
||||||
if hasattr(field, 'pad_content_field') and vals.get(field.pad_content_field) and self[k]:
|
if hasattr(field, 'pad_content_field') and vals.get(field.pad_content_field) and self[k]:
|
||||||
company = self.env.user.sudo().company_id
|
company = self.env.user.sudo().company_id
|
||||||
myPad = EtherpadLiteClient(company.pad_key, company.pad_server + '/api')
|
myPad = EtherpadLiteClient(company.pad_key, (company.pad_server or '') + '/api')
|
||||||
path = self[k].split('/p/')[1]
|
path = self[k].split('/p/')[1]
|
||||||
myPad.setHtmlFallbackText(path, vals[field.pad_content_field])
|
myPad.setHtmlFallbackText(path, vals[field.pad_content_field])
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class WebsitePayment(http.Controller):
|
|||||||
'amount': float(amount),
|
'amount': float(amount),
|
||||||
'currency_id': currency_id,
|
'currency_id': currency_id,
|
||||||
'partner_id': partner_id,
|
'partner_id': partner_id,
|
||||||
'type': 'form_save' if acquirer.save_token else 'form',
|
'type': 'form_save' if acquirer.save_token != 'none' and partner_id else 'form',
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = request.env['payment.transaction'].sudo().create(values)
|
tx = request.env['payment.transaction'].sudo().create(values)
|
||||||
@@ -119,7 +119,7 @@ class WebsitePayment(http.Controller):
|
|||||||
'currency_id': int(currency_id),
|
'currency_id': int(currency_id),
|
||||||
'partner_id': partner_id,
|
'partner_id': partner_id,
|
||||||
'payment_token_id': pm_id,
|
'payment_token_id': pm_id,
|
||||||
'type': 'form_save' if token.acquirer_id.save_token else 'form',
|
'type': 'form_save' if token.acquirer_id.save_token != 'none' and partner_id else 'form',
|
||||||
}
|
}
|
||||||
|
|
||||||
tx = request.env['payment.transaction'].sudo().create(values)
|
tx = request.env['payment.transaction'].sudo().create(values)
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
if references.get(invoices.get(group_key)):
|
if references.get(invoices.get(group_key)):
|
||||||
if order not in references[invoices[group_key]]:
|
if order not in references[invoices[group_key]]:
|
||||||
references[invoice] = references[invoice] | order
|
references[invoices[group_key]] |= order
|
||||||
|
|
||||||
if not invoices:
|
if not invoices:
|
||||||
raise UserError(_('There is no invoiceable line.'))
|
raise UserError(_('There is no invoiceable line.'))
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
<group string="Applied On">
|
<group string="Applied On">
|
||||||
<field name="location_id"/>
|
<field name="location_id"/>
|
||||||
<field name="warehouse_id" groups="base.group_no_one"/>
|
<field name="warehouse_id" groups="base.group_no_one"/>
|
||||||
|
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Creates">
|
<group string="Creates">
|
||||||
<field name="location_src_id" attrs="{'required': [('action', '=', 'move')], 'invisible':[('action', '!=', 'move')]}" domain="[('usage','!=','view')]"/>
|
<field name="location_src_id" attrs="{'required': [('action', '=', 'move')], 'invisible':[('action', '!=', 'move')]}" domain="[('usage','!=','view')]"/>
|
||||||
|
|||||||
@@ -271,7 +271,7 @@
|
|||||||
<field name="push_ids" colspan="4" nolabel="1"/>
|
<field name="push_ids" colspan="4" nolabel="1"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Procurement Rules" colspan="4" >
|
<group string="Procurement Rules" colspan="4" >
|
||||||
<field name="pull_ids" colspan="4" nolabel="1"/>
|
<field name="pull_ids" colspan="4" nolabel="1" context="{'default_company_id': company_id}"/>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_id" ref="view_stock_product_tree2"/>
|
<field name="view_id" ref="view_stock_product_tree2"/>
|
||||||
<field name="domain">[('type', '=', 'product'), ('qty_available', '!=', 0)]</field>
|
<field name="domain">[('type', '=', 'product'), ('qty_available', '!=', 0)]</field>
|
||||||
<field name="context">{}</field>
|
<field name="context">{'company_owned': True}</field>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Create a new product valuation
|
Create a new product valuation
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StockQuantityHistory(models.TransientModel):
|
|||||||
'name': _('Products'),
|
'name': _('Products'),
|
||||||
'res_model': 'product.product',
|
'res_model': 'product.product',
|
||||||
'domain': "[('type', '=', 'product'), ('qty_available', '!=', 0)]",
|
'domain': "[('type', '=', 'product'), ('qty_available', '!=', 0)]",
|
||||||
'context': dict(self.env.context, to_date=self.date),
|
'context': dict(self.env.context, to_date=self.date, company_owned=True),
|
||||||
}
|
}
|
||||||
return action
|
return action
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -78,11 +78,14 @@ class IrTranslation(models.Model):
|
|||||||
translation.transifex_url = False
|
translation.transifex_url = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# e.g. 'https://www.transifex.com/odoo/odoo-10/translate/#fr/sale/42?q=Sale+Order'
|
# e.g. https://www.transifex.com/odoo/odoo-10/translate/#fr/sale/42?q=text'Sale+Order'
|
||||||
translation.transifex_url = "%(url)s/%(project)s/translate/#%(lang)s/%(module)s/42?q=%(src)s" % {
|
translation.transifex_url = "%(url)s/%(project)s/translate/#%(lang)s/%(module)s/42?q=%(src)s" % {
|
||||||
'url': base_url,
|
'url': base_url,
|
||||||
'project': project,
|
'project': project,
|
||||||
'lang': lang_code,
|
'lang': lang_code,
|
||||||
'module': translation.module,
|
'module': translation.module,
|
||||||
'src': werkzeug.url_quote_plus(translation.source[:50]),
|
'src': werkzeug.url_quote_plus(
|
||||||
|
"text:'" +
|
||||||
|
translation.source[:50].replace("'", "\'") +
|
||||||
|
"'"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -961,14 +961,17 @@ class DataSet(http.Controller):
|
|||||||
|
|
||||||
class View(http.Controller):
|
class View(http.Controller):
|
||||||
|
|
||||||
@http.route('/web/view/add_custom', type='json', auth="user")
|
@http.route('/web/view/edit_custom', type='json', auth="user")
|
||||||
def add_custom(self, view_id, arch):
|
def edit_custom(self, custom_id, arch):
|
||||||
CustomView = request.env['ir.ui.view.custom']
|
"""
|
||||||
CustomView.create({
|
Edit a custom view
|
||||||
'user_id': request.session.uid,
|
|
||||||
'ref_id': view_id,
|
:param int custom_id: the id of the edited custom view
|
||||||
'arch': arch
|
:param str arch: the edited arch of the custom view
|
||||||
})
|
:returns: dict with acknowledged operation (result set to True)
|
||||||
|
"""
|
||||||
|
custom_view = request.env['ir.ui.view.custom'].browse(custom_id)
|
||||||
|
custom_view.write({ 'arch': arch })
|
||||||
return {'result': True}
|
return {'result': True}
|
||||||
|
|
||||||
class Binary(http.Controller):
|
class Binary(http.Controller):
|
||||||
|
|||||||
@@ -1241,9 +1241,6 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
|
|||||||
self.do_warn(_t("Image"), _t("Could not display the selected image."));
|
self.do_warn(_t("Image"), _t("Could not display the selected image."));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSet: function () {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var FieldBinaryFile = AbstractFieldBinary.extend({
|
var FieldBinaryFile = AbstractFieldBinary.extend({
|
||||||
|
|||||||
@@ -2302,6 +2302,21 @@ var FieldReference = FieldMany2One.extend({
|
|||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @returns {jQuery}
|
||||||
|
*/
|
||||||
|
getFocusableElement: function () {
|
||||||
|
if (this.mode === 'edit' && !this.field.relation) {
|
||||||
|
return this.$('select');
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -3614,8 +3614,9 @@ var BasicModel = AbstractModel.extend({
|
|||||||
if (isFieldInView) {
|
if (isFieldInView) {
|
||||||
var field = r.fields[fieldName];
|
var field = r.fields[fieldName];
|
||||||
var fieldType = field.type;
|
var fieldType = field.type;
|
||||||
|
var rec;
|
||||||
if (fieldType === 'many2one') {
|
if (fieldType === 'many2one') {
|
||||||
var rec = self._makeDataPoint({
|
rec = self._makeDataPoint({
|
||||||
context: r.context,
|
context: r.context,
|
||||||
modelName: field.relation,
|
modelName: field.relation,
|
||||||
data: {id: r._changes[fieldName]},
|
data: {id: r._changes[fieldName]},
|
||||||
@@ -3623,6 +3624,16 @@ var BasicModel = AbstractModel.extend({
|
|||||||
});
|
});
|
||||||
r._changes[fieldName] = rec.id;
|
r._changes[fieldName] = rec.id;
|
||||||
many2ones[fieldName] = true;
|
many2ones[fieldName] = true;
|
||||||
|
} else if (fieldType === 'reference') {
|
||||||
|
var reference = r._changes[fieldName].split(',');
|
||||||
|
rec = self._makeDataPoint({
|
||||||
|
context: r.context,
|
||||||
|
modelName: reference[0],
|
||||||
|
data: {id: parseInt(reference[1])},
|
||||||
|
parentID: r.id,
|
||||||
|
});
|
||||||
|
r._changes[fieldName] = rec.id;
|
||||||
|
many2ones[fieldName] = true;
|
||||||
} else if (_.contains(['one2many', 'many2many'], fieldType)) {
|
} else if (_.contains(['one2many', 'many2many'], fieldType)) {
|
||||||
var x2mCommands = commands[0][2][fieldName];
|
var x2mCommands = commands[0][2][fieldName];
|
||||||
defs.push(self._processX2ManyCommands(r, fieldName, x2mCommands));
|
defs.push(self._processX2ManyCommands(r, fieldName, x2mCommands));
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ var PivotModel = AbstractModel.extend({
|
|||||||
this.data.colGroupBys = params.context.pivot_column_groupby || this.data.colGroupBys;
|
this.data.colGroupBys = params.context.pivot_column_groupby || this.data.colGroupBys;
|
||||||
this.data.groupedBy = params.context.pivot_row_groupby || this.data.groupedBy;
|
this.data.groupedBy = params.context.pivot_row_groupby || this.data.groupedBy;
|
||||||
this.data.measures = this._processMeasures(params.context.pivot_measures) || this.data.measures;
|
this.data.measures = this._processMeasures(params.context.pivot_measures) || this.data.measures;
|
||||||
|
this.defaultGroupedBy = this.data.groupedBy.length ? this.data.groupedBy : this.defaultGroupedBy;
|
||||||
}
|
}
|
||||||
if ('domain' in params) {
|
if ('domain' in params) {
|
||||||
this.data.domain = params.domain;
|
this.data.domain = params.domain;
|
||||||
@@ -278,7 +279,7 @@ var PivotModel = AbstractModel.extend({
|
|||||||
|
|
||||||
self._updateTree(old_col_root, self.data.main_col.root);
|
self._updateTree(old_col_root, self.data.main_col.root);
|
||||||
new_groupby_length = self._getHeaderDepth(self.data.main_col.root) - 1;
|
new_groupby_length = self._getHeaderDepth(self.data.main_col.root) - 1;
|
||||||
self.data.main_row.groupbys = old_col_root.groupbys.slice(0, new_groupby_length);
|
self.data.main_row.groupbys = old_row_root.groupbys.slice(0, new_groupby_length);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -361,6 +361,10 @@
|
|||||||
&:hover .o_form_image_controls {
|
&:hover .o_form_image_controls {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.o_field_invalid > img {
|
||||||
|
border: 1px solid @brand-danger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input loading/file
|
// Input loading/file
|
||||||
|
|||||||
@@ -1736,7 +1736,6 @@ QUnit.module('basic_fields', {
|
|||||||
assert.strictEqual(form.$('div[name="document"] > img').attr('width'), "90",
|
assert.strictEqual(form.$('div[name="document"] > img').attr('width'), "90",
|
||||||
"the image should correctly set its attributes");
|
"the image should correctly set its attributes");
|
||||||
form.destroy();
|
form.destroy();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('image fields in subviews are loaded correctly', function (assert) {
|
QUnit.test('image fields in subviews are loaded correctly', function (assert) {
|
||||||
@@ -1788,6 +1787,34 @@ QUnit.module('basic_fields', {
|
|||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('image fields with required attribute', function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<form string="Partners">' +
|
||||||
|
'<field name="document" required="1" widget="image"/>' +
|
||||||
|
'</form>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'create') {
|
||||||
|
throw new Error("Should not do a create RPC with unset required image field");
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
form.$buttons.find('.o_form_button_save').click();
|
||||||
|
|
||||||
|
assert.ok(form.$('.o_form_view').hasClass('o_form_editable'),
|
||||||
|
"form view should still be editable");
|
||||||
|
assert.ok(form.$('.o_field_widget').hasClass('o_field_invalid'),
|
||||||
|
"image field should be displayed as invalid");
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.module('JournalDashboardGraph', {
|
QUnit.module('JournalDashboardGraph', {
|
||||||
beforeEach: function () {
|
beforeEach: function () {
|
||||||
_.extend(this.data.partner.fields, {
|
_.extend(this.data.partner.fields, {
|
||||||
|
|||||||
@@ -11326,6 +11326,38 @@ QUnit.module('relational_fields', {
|
|||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('default_get a reference field in a x2m', function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
this.data.partner.fields.turtles.default = [
|
||||||
|
[0, false, {turtle_ref: 'product,37'}]
|
||||||
|
];
|
||||||
|
|
||||||
|
var form = createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<form string="Partners">' +
|
||||||
|
'<sheet>' +
|
||||||
|
'<field name="turtles">' +
|
||||||
|
'<tree>' +
|
||||||
|
'<field name="turtle_ref"/>' +
|
||||||
|
'</tree>' +
|
||||||
|
'</field>' +
|
||||||
|
'</sheet>' +
|
||||||
|
'</form>',
|
||||||
|
viewOptions: {
|
||||||
|
mode: 'edit',
|
||||||
|
},
|
||||||
|
archs: {
|
||||||
|
'turtle,false,form': '<form><field name="display_name"/><field name="turtle_ref"/></form>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_data_row:first').text(), "xphone",
|
||||||
|
"the default value should be correctly handled");
|
||||||
|
form.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('widget reference on char field, reset by onchange', function (assert) {
|
QUnit.test('widget reference on char field, reset by onchange', function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
@@ -11376,6 +11408,32 @@ QUnit.module('relational_fields', {
|
|||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('reference and list navigation', function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
var list = createView({
|
||||||
|
View: ListView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
arch: '<tree editable="bottom"><field name="reference"/></tree>',
|
||||||
|
});
|
||||||
|
|
||||||
|
// edit first row
|
||||||
|
list.$('.o_data_row .o_data_cell').first().click();
|
||||||
|
assert.strictEqual(list.$('.o_data_row:eq(0) .o_field_widget[name="reference"] input')[0], document.activeElement,
|
||||||
|
'input of first data row should be selected');
|
||||||
|
|
||||||
|
// press TAB to go to next line
|
||||||
|
list.$('.o_data_row:eq(0) input').trigger($.Event('keydown', {
|
||||||
|
which: $.ui.keyCode.TAB,
|
||||||
|
keyCode: $.ui.keyCode.TAB,
|
||||||
|
}));
|
||||||
|
assert.strictEqual(list.$('.o_data_row:eq(1) .o_field_widget[name="reference"] select')[0], document.activeElement,
|
||||||
|
'select of second data row should be selected');
|
||||||
|
|
||||||
|
list.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('one2many with extra field from server not in form', function (assert) {
|
QUnit.test('one2many with extra field from server not in form', function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
|
|
||||||
|
|||||||
@@ -965,4 +965,45 @@ QUnit.module('Views', {
|
|||||||
|
|
||||||
pivot.destroy();
|
pivot.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('Row and column groupbys plus a domain', function (assert) {
|
||||||
|
assert.expect(3);
|
||||||
|
|
||||||
|
var pivot = createView({
|
||||||
|
View: PivotView,
|
||||||
|
model: "partner",
|
||||||
|
data: this.data,
|
||||||
|
arch: '<pivot>' +
|
||||||
|
'<field name="foo" type="measure"/>' +
|
||||||
|
'</pivot>',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set a column groupby
|
||||||
|
pivot.$('thead .o_pivot_header_cell_closed').click();
|
||||||
|
pivot.$('.o_field_selection li[data-field=customer] a').click();
|
||||||
|
|
||||||
|
// Set a Row groupby
|
||||||
|
pivot.$('tbody .o_pivot_header_cell_closed').click();
|
||||||
|
pivot.$('.o_pivot_field_menu li[data-field=product_id] a').click();
|
||||||
|
|
||||||
|
// Set a domain
|
||||||
|
pivot.update({domain: [['product_id', '=', 41]]});
|
||||||
|
|
||||||
|
var expectedContext = {pivot_column_groupby: ['customer'],
|
||||||
|
pivot_measures: ['foo'],
|
||||||
|
pivot_row_groupby: ['product_id']};
|
||||||
|
|
||||||
|
// Mock 'save as favorite'
|
||||||
|
assert.deepEqual(pivot.getContext(), expectedContext,
|
||||||
|
'The pivot view should have the right context');
|
||||||
|
|
||||||
|
var $xpadHeader = pivot.$('tbody .o_pivot_header_cell_closed[data-original-title=Product]');
|
||||||
|
assert.equal($xpadHeader.length, 1,
|
||||||
|
'There should be only one product line because of the domain');
|
||||||
|
|
||||||
|
assert.equal($xpadHeader.text(), 'xpad',
|
||||||
|
'The product should be the right one');
|
||||||
|
|
||||||
|
pivot.destroy();
|
||||||
|
});
|
||||||
});});
|
});});
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class Web_Editor(http.Controller):
|
|||||||
'datas_fname': datas_fname,
|
'datas_fname': datas_fname,
|
||||||
'type': 'url',
|
'type': 'url',
|
||||||
'url': url,
|
'url': url,
|
||||||
'public': True,
|
'public': res_model == 'ir.ui.view',
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'res_model': res_model,
|
'res_model': res_model,
|
||||||
})
|
})
|
||||||
@@ -195,7 +195,7 @@ class Web_Editor(http.Controller):
|
|||||||
'name': name,
|
'name': name,
|
||||||
'datas': base64.b64encode(data),
|
'datas': base64.b64encode(data),
|
||||||
'datas_fname': datas_fname,
|
'datas_fname': datas_fname,
|
||||||
'public': True,
|
'public': res_model == 'ir.ui.view',
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'res_model': res_model,
|
'res_model': res_model,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -83,12 +83,50 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
|||||||
// Private
|
// Private
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the domain for attachments used in media dialog.
|
||||||
|
* We look for attachments related to the current document. If there is a value for the model
|
||||||
|
* field, it is used to search attachments, and the attachments from the current document are
|
||||||
|
* filtered to display only user-created documents.
|
||||||
|
* In the case of a wizard such as mail, we have the documents uploaded and those of the model
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {Array} "ir.attachment" odoo domain.
|
||||||
|
*/
|
||||||
|
_getAttachmentsDomain: function () {
|
||||||
|
var domain = ['|', ['id', 'in', _.pluck(this.attachments, 'id')]];
|
||||||
|
var attachedDocumentDomain = [
|
||||||
|
'&',
|
||||||
|
['res_model', '=', this.model],
|
||||||
|
['res_id', '=', this.res_id|0]
|
||||||
|
];
|
||||||
|
// if the document is not yet created, do not see the documents of other users
|
||||||
|
if (!this.res_id) {
|
||||||
|
attachedDocumentDomain.unshift('&');
|
||||||
|
attachedDocumentDomain.push(['create_uid', '=', session.uid]);
|
||||||
|
}
|
||||||
|
if (this.recordData.model) {
|
||||||
|
var relatedDomain = ['&',
|
||||||
|
['res_model', '=', this.recordData.model],
|
||||||
|
['res_id', '=', this.recordData.res_id|0]];
|
||||||
|
if (!this.recordData.res_id) {
|
||||||
|
relatedDomain.unshift('&');
|
||||||
|
relatedDomain.push(['create_uid', '=', session.uid]);
|
||||||
|
}
|
||||||
|
domain = domain.concat(['|'], attachedDocumentDomain, relatedDomain);
|
||||||
|
} else {
|
||||||
|
domain = domain.concat(attachedDocumentDomain);
|
||||||
|
}
|
||||||
|
return domain;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @returns {Object} the summernote configuration
|
* @returns {Object} the summernote configuration
|
||||||
*/
|
*/
|
||||||
_getSummernoteConfig: function () {
|
_getSummernoteConfig: function () {
|
||||||
var summernoteConfig = {
|
var summernoteConfig = {
|
||||||
|
model: this.model,
|
||||||
|
id: this.res_id,
|
||||||
focus: false,
|
focus: false,
|
||||||
height: 180,
|
height: 180,
|
||||||
toolbar: [
|
toolbar: [
|
||||||
@@ -107,6 +145,22 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
|||||||
lang: "odoo",
|
lang: "odoo",
|
||||||
onChange: this._doDebouncedAction.bind(this),
|
onChange: this._doDebouncedAction.bind(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var fieldNameAttachment =_.chain(this.recordData)
|
||||||
|
.pairs()
|
||||||
|
.find(function (value) {
|
||||||
|
return _.isObject(value[1]) && value[1].model === "ir.attachment";
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
if (fieldNameAttachment) {
|
||||||
|
this.fieldNameAttachment = fieldNameAttachment;
|
||||||
|
this.attachments = [];
|
||||||
|
summernoteConfig.onImageUpload = this._onImageUpload.bind(this);
|
||||||
|
summernoteConfig.getMediaDomain = this._getAttachmentsDomain.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
summernoteConfig.toolbar.splice(7, 0, ['view', ['codeview']]);
|
summernoteConfig.toolbar.splice(7, 0, ['view', ['codeview']]);
|
||||||
}
|
}
|
||||||
@@ -124,6 +178,34 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
|||||||
}
|
}
|
||||||
return this.$content.html();
|
return this.$content.html();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* trigger_up 'field_changed' add record into the "ir.attachment" field found in the view.
|
||||||
|
* This method is called when an image is uploaded by the media dialog.
|
||||||
|
*
|
||||||
|
* For e.g. when sending email, this allows people to add attachments with the content
|
||||||
|
* editor interface and that they appear in the attachment list.
|
||||||
|
* The new documents being attached to the email, they will not be erased by the CRON
|
||||||
|
* when closing the wizard.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onImageUpload: function (attachments) {
|
||||||
|
var self = this;
|
||||||
|
attachments = _.filter(attachments, function (attachment) {
|
||||||
|
return !_.findWhere(self.attachments, {id: attachment.id});
|
||||||
|
});
|
||||||
|
if (!attachments.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.attachments = this.attachments.concat(attachments);
|
||||||
|
this.trigger_up('field_changed', {
|
||||||
|
dataPointID: this.dataPointID,
|
||||||
|
changes: _.object([this.fieldNameAttachment], [{
|
||||||
|
operation: 'ADD_M2M',
|
||||||
|
ids: attachments
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @private
|
* @private
|
||||||
@@ -134,8 +216,6 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
|||||||
this.$textarea.summernote(this._getSummernoteConfig());
|
this.$textarea.summernote(this._getSummernoteConfig());
|
||||||
this.$content = this.$('.note-editable:first');
|
this.$content = this.$('.note-editable:first');
|
||||||
this.$content.html(this._textToHtml(this.value));
|
this.$content.html(this._textToHtml(this.value));
|
||||||
this.$content.data('oe-id', this.recordData.res_id || this.res_id);
|
|
||||||
this.$content.data('oe-model', this.recordData.model || this.model);
|
|
||||||
// trigger a mouseup to refresh the editor toolbar
|
// trigger a mouseup to refresh the editor toolbar
|
||||||
this.$content.trigger('mouseup');
|
this.$content.trigger('mouseup');
|
||||||
if (this.nodeOptions['style-inline']) {
|
if (this.nodeOptions['style-inline']) {
|
||||||
|
|||||||
@@ -397,6 +397,7 @@ eventHandler.modules.imageDialog.showImageDialog = function ($editable) {
|
|||||||
media: media,
|
media: media,
|
||||||
options: {
|
options: {
|
||||||
lastFilters: ['background'],
|
lastFilters: ['background'],
|
||||||
|
onUpload: $editable.data('callbacks').onImageUpload,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return new $.Deferred().reject();
|
return new $.Deferred().reject();
|
||||||
@@ -788,6 +789,14 @@ eventHandler.attach = function (oLayoutInfo, options) {
|
|||||||
$(document).on("keyup", reRangeSelectKey);
|
$(document).on("keyup", reRangeSelectKey);
|
||||||
|
|
||||||
var clone_data = false;
|
var clone_data = false;
|
||||||
|
|
||||||
|
if (options.model) {
|
||||||
|
oLayoutInfo.editable().data({'oe-model': options.model, 'oe-id': options.id});
|
||||||
|
}
|
||||||
|
if (options.getMediaDomain) {
|
||||||
|
oLayoutInfo.editable().data('oe-media-domain', options.getMediaDomain);
|
||||||
|
}
|
||||||
|
|
||||||
var $node = oLayoutInfo.editor();
|
var $node = oLayoutInfo.editor();
|
||||||
if ($node.data('oe-model') || $node.data('oe-translation-id')) {
|
if ($node.data('oe-model') || $node.data('oe-translation-id')) {
|
||||||
$node.on('content_changed', function () {
|
$node.on('content_changed', function () {
|
||||||
@@ -1089,6 +1098,7 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, {
|
|||||||
_.extend({
|
_.extend({
|
||||||
res_model: data.$editable.data('oe-model'),
|
res_model: data.$editable.data('oe-model'),
|
||||||
res_id: data.$editable.data('oe-id'),
|
res_id: data.$editable.data('oe-id'),
|
||||||
|
domain: data.$editable.data('oe-media-domain'),
|
||||||
}, data.options),
|
}, data.options),
|
||||||
data.$editable,
|
data.$editable,
|
||||||
data.media
|
data.media
|
||||||
|
|||||||
@@ -209,7 +209,9 @@ var ImageWidget = MediaWidget.extend({
|
|||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.accept = options.accept || (options.document ? '*/*' : 'image/*');
|
this.accept = options.accept || (options.document ? '*/*' : 'image/*');
|
||||||
if (options.res_id) {
|
if (options.domain) {
|
||||||
|
this.domain = typeof options.domain === 'function' ? options.domain() : options.domain;
|
||||||
|
} else if (options.res_id) {
|
||||||
this.domain = ['|',
|
this.domain = ['|',
|
||||||
'&', ['res_model', '=', options.res_model], ['res_id', '=', options.res_id],
|
'&', ['res_model', '=', options.res_model], ['res_id', '=', options.res_id],
|
||||||
['res_model', '=', 'ir.ui.view']];
|
['res_model', '=', 'ir.ui.view']];
|
||||||
@@ -330,6 +332,11 @@ var ImageWidget = MediaWidget.extend({
|
|||||||
self.$media.css(style);
|
self.$media.css(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.options.onUpload) {
|
||||||
|
// We consider that when selecting an image it is as if we upload it in the html content.
|
||||||
|
self.options.onUpload([img]);
|
||||||
|
}
|
||||||
|
|
||||||
return self.media;
|
return self.media;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -504,6 +511,10 @@ var ImageWidget = MediaWidget.extend({
|
|||||||
_processFile(attachments[i], error);
|
_processFile(attachments[i], error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.options.onUpload) {
|
||||||
|
self.options.onUpload(attachments);
|
||||||
|
}
|
||||||
|
|
||||||
function _processFile(attachment, error) {
|
function _processFile(attachment, error) {
|
||||||
var $button = self.$('.o_upload_image_button');
|
var $button = self.$('.o_upload_image_button');
|
||||||
if (!error) {
|
if (!error) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Jesus Zapata jesus@vauxoo.com https://github.com/JesusZapata
|
|||||||
Germana Oliveira germana@vauxoo.com https://github.com/goliveirab
|
Germana Oliveira germana@vauxoo.com https://github.com/goliveirab
|
||||||
Mariano Fernandez mariano@vauxoo.com https://github.com/Batuto
|
Mariano Fernandez mariano@vauxoo.com https://github.com/Batuto
|
||||||
Edgar Rivero edgar@vauxoo.com https://github.com/egrivero
|
Edgar Rivero edgar@vauxoo.com https://github.com/egrivero
|
||||||
|
Edilianny Sánchez esanchez@vauxoo.com https://github.com/edy1192
|
||||||
Alexander Olivares alexander@vauxoo.com https://github.com/alxolivares
|
Alexander Olivares alexander@vauxoo.com https://github.com/alxolivares
|
||||||
Jose Manuel Robles josemanuel@vauxoo.com https://github.com/keylor2906
|
Jose Manuel Robles josemanuel@vauxoo.com https://github.com/keylor2906
|
||||||
Erick Birbe erick@vauxoo.com https://github.com/ebirbe
|
Erick Birbe erick@vauxoo.com https://github.com/ebirbe
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ Barcodes
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
Barcodes are images returned by a controller and can easily be embedded in
|
Barcodes are images returned by a controller and can easily be embedded in
|
||||||
reports thanks to the QWeb syntax:
|
reports thanks to the QWeb syntax (e.g. see :ref:`reference/qweb/attributes`):
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: html
|
||||||
|
|
||||||
|
|||||||
@@ -442,6 +442,10 @@ security-related topics:
|
|||||||
only for controlling/managing the installation.
|
only for controlling/managing the installation.
|
||||||
*Never* use any default passwords like admin/admin, even for test/staging databases.
|
*Never* use any default passwords like admin/admin, even for test/staging databases.
|
||||||
|
|
||||||
|
- Do **not** install demo data on internet-facing servers. Databases with demo data contain
|
||||||
|
default logins and passwords that can be used to get into your systems and cause significant
|
||||||
|
trouble, even on staging/dev systems.
|
||||||
|
|
||||||
- Use appropriate database filters ( :option:`--db-filter <odoo-bin --db-filter>`)
|
- Use appropriate database filters ( :option:`--db-filter <odoo-bin --db-filter>`)
|
||||||
to restrict the visibility of your databases according to the hostname.
|
to restrict the visibility of your databases according to the hostname.
|
||||||
See :ref:`db_filter`.
|
See :ref:`db_filter`.
|
||||||
@@ -474,6 +478,12 @@ security-related topics:
|
|||||||
and then enable the :option:`proxy mode <odoo-bin --proxy-mode>` option.
|
and then enable the :option:`proxy mode <odoo-bin --proxy-mode>` option.
|
||||||
See also :ref:`https_proxy`.
|
See also :ref:`https_proxy`.
|
||||||
|
|
||||||
|
- If you need to allow remote SSH access to your servers, make sure to set a strong password
|
||||||
|
for **all** accounts, not just `root`. It is strongly recommended to entirely disable
|
||||||
|
password-based authentication, and only allow public key authentication. Also consider
|
||||||
|
restricting access via a VPN, allowing only trusted IPs in the firewall, and/or
|
||||||
|
running a brute-force detection system such as `fail2ban` or equivalent.
|
||||||
|
|
||||||
- Whenever possible, host your public-facing demo/test/staging instances on different
|
- Whenever possible, host your public-facing demo/test/staging instances on different
|
||||||
machines than the production ones. And apply the same security precautions as for
|
machines than the production ones. And apply the same security precautions as for
|
||||||
production.
|
production.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","translatable","week_start"
|
"id","name","code","iso_code","direction","grouping","decimal_point","thousands_sep","date_format","time_format","translatable","week_start"
|
||||||
"base.lang_en","English","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","True","7"
|
"base.lang_en","English","en_US","en","Left-to-Right","[3,0]",".",",","%m/%d/%Y","%H:%M:%S","True","7"
|
||||||
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","True","7"
|
"base.lang_am_ET","Amharic / አምሃርኛ","am_ET","am_ET","Left-to-Right","[3,0]",".",",","%d/%m/%Y","%I:%M:%S","True","7"
|
||||||
"base.lang_ar_SY","Arabic / الْعَرَبيّة","ar_SY","ar_SY","Left-to-Right","[3,0]",".",",","%d %b, %Y","%I:%M:%S","True","6"
|
"base.lang_ar_SY","Arabic / الْعَرَبيّة","ar_SY","ar","Left-to-Right","[3,0]",".",",","%d %b, %Y","%I:%M:%S","True","6"
|
||||||
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","True","1"
|
"base.lang_eu_ES","Basque / Euskara","eu_ES","eu_ES","Left-to-Right","[]",",",,"%a, %Y.eko %bren %da","%H:%M:%S","True","1"
|
||||||
"base.lang_bs_BA","Bosnian / bosanski jezik","bs_BA","bs","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","True","7"
|
"base.lang_bs_BA","Bosnian / bosanski jezik","bs_BA","bs","Left-to-Right","[]",",",,"%d.%m.%Y","%H:%M:%S","True","7"
|
||||||
"base.lang_bg","Bulgarian / български език","bg_BG","bg","Left-to-Right","[3,0]",",",,"%d.%m.%Y","%H,%M,%S","True","1"
|
"base.lang_bg","Bulgarian / български език","bg_BG","bg","Left-to-Right","[3,0]",",",,"%d.%m.%Y","%H,%M,%S","True","1"
|
||||||
|
|||||||
|
@@ -12044,6 +12044,18 @@ msgstr ""
|
|||||||
msgid "Memos pad"
|
msgid "Memos pad"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base
|
||||||
|
#: code:addons/base/ir/ir_model.py:382
|
||||||
|
#, python-format
|
||||||
|
msgid "Model %s does not exist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: base
|
||||||
|
#: code:addons/base/ir/ir_model.py:383
|
||||||
|
#, python-format
|
||||||
|
msgid "Please specify a valid model for the object relation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: base
|
#. module: base
|
||||||
#: model:ir.model.fields,field_description:base.field_ir_ui_menu__name
|
#: model:ir.model.fields,field_description:base.field_ir_ui_menu__name
|
||||||
#: model:ir.ui.view,arch_db:base.edit_menu
|
#: model:ir.ui.view,arch_db:base.edit_menu
|
||||||
|
|||||||
@@ -466,6 +466,13 @@ class IrModelFields(models.Model):
|
|||||||
def _onchange_ttype(self):
|
def _onchange_ttype(self):
|
||||||
self.copy = (self.ttype != 'one2many')
|
self.copy = (self.ttype != 'one2many')
|
||||||
if self.ttype == 'many2many' and self.model_id and self.relation:
|
if self.ttype == 'many2many' and self.model_id and self.relation:
|
||||||
|
if self.relation not in self.env:
|
||||||
|
return {
|
||||||
|
'warning': {
|
||||||
|
'title': _('Model %s does not exist') % self.relation,
|
||||||
|
'message': _('Please specify a valid model for the object relation'),
|
||||||
|
}
|
||||||
|
}
|
||||||
names = self._custom_many2many_names(self.model_id.model, self.relation)
|
names = self._custom_many2many_names(self.model_id.model, self.relation)
|
||||||
self.relation_table, self.column1, self.column2 = names
|
self.relation_table, self.column1, self.column2 = names
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user