partner.membership_stop:
res[partner.id] = 'free' if partner.free_member else 'old'
continue
+ if partner.associate_member:
+ res_state = partner.associate_member._membership_state()
+ res[partner.id] = res_state[partner.associate_member.id]
+ continue
s = 4
if partner.member_lines:
for mline in partner.member_lines:
if (mline.date_to or '0000-00-00') >= today and (mline.date_from or '0000-00-00') <= today:
- if mline.account_invoice_line.invoice_id:
+ if mline.account_invoice_line.invoice_id.partner_id == partner:
mstate = mline.account_invoice_line.invoice_id.state
if mstate == 'paid':
s = 0
inv = mline.account_invoice_line.invoice_id
- for payment in inv.payment_ids:
- if any(payment.invoice_ids.filtered(lambda inv: inv.type == 'out_refund')):
+ for ml in inv.payment_move_line_ids:
+ if any(ml.invoice_id.filtered(lambda inv: inv.type == 'out_refund')):
s = 2
break
elif mstate == 'open' and s != 0:
@@ -144,9 +150,6 @@ class Partner(models.Model):
res[partner.id] = 'none'
if partner.free_member and s != 0:
res[partner.id] = 'free'
- if partner.associate_member:
- res_state = partner.associate_member._membership_state()
- res[partner.id] = res_state[partner.associate_member.id]
return res
@api.one
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index 86399e9a722..045186b16b7 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -116,7 +116,7 @@
-
+
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 4fa895bd939..25072ad578d 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -184,7 +184,7 @@ class PosOrder(models.Model):
}
@api.model
- def _get_account_move_line_group_data_type_key(self, data_type, values):
+ def _get_account_move_line_group_data_type_key(self, data_type, values, options={}):
"""
Return a tuple which will be used as a key for grouping account
move lines in _create_account_move_line method.
@@ -199,10 +199,16 @@ class PosOrder(models.Model):
values['analytic_account_id'],
values['debit'] > 0)
elif data_type == 'tax':
- return ('tax',
- values['partner_id'],
- values['tax_line_id'],
- values['debit'] > 0)
+ order_id = values.pop('order_id', False)
+ tax_key = ('tax',
+ values['partner_id'],
+ values['tax_line_id'],
+ values['debit'] > 0)
+ if options.get('rounding_method') == 'round_globally':
+ tax_key = ('tax',
+ values['tax_line_id'],
+ order_id)
+ return tax_key
elif data_type == 'counter_part':
return ('counter_part',
values['partner_id'],
@@ -312,7 +318,7 @@ class PosOrder(models.Model):
'move_id': move.id,
})
- key = self._get_account_move_line_group_data_type_key(data_type, values)
+ key = self._get_account_move_line_group_data_type_key(data_type, values, {'rounding_method': rounding_method})
if not key:
return
@@ -326,6 +332,14 @@ class PosOrder(models.Model):
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
+ if key[0] == 'tax' and rounding_method == 'round_globally':
+ if current_value['debit'] - current_value['credit'] > 0:
+ current_value['debit'] = current_value['debit'] - current_value['credit']
+ current_value['credit'] = 0
+ else:
+ current_value['credit'] = current_value['credit'] - current_value['debit']
+ current_value['debit'] = 0
+
else:
grouped_data[key].append(values)
@@ -385,7 +399,8 @@ class PosOrder(models.Model):
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
'tax_line_id': tax['id'],
- 'partner_id': partner_id
+ 'partner_id': partner_id,
+ 'order_id': order.id
})
# round tax lines per order
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 7b947a62344..190dc7e6c69 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -785,15 +785,20 @@ class Task(models.Model):
groups = super(Task, self)._notify_get_groups(message, groups)
self.ensure_one()
+
+ project_user_group_id = self.env.ref('project.group_project_user').id
+ new_group = (
+ 'group_project_user',
+ lambda pdata: pdata['type'] == 'user' and project_user_group_id in pdata['groups']
+ {},
+ )
+
if not self.user_id and not self.stage_id.fold:
take_action = self._notify_get_action_link('assign')
project_actions = [{'url': take_action, 'title': _('I take it')}]
- project_user_group_id = self.env.ref('project.group_project_user').id
- new_group = (
- 'group_project_user', lambda pdata: pdata['type'] == 'user' and project_user_group_id in pdata['groups'], {
- 'actions': project_actions,
- })
- groups = [new_group] + groups
+ new_group[2]['actions'] = project_actions
+
+ groups = [new_group] + groups
for group_name, group_method, group_data in groups:
if group_name == 'customer':
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 41abbba1cfe..a672cdcb748 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -348,7 +348,7 @@ class PurchaseOrder(models.Model):
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
'product_uom': line.product_uom.id,
'min_qty': 0.0,
- 'price': self.currency_id._convert(line.price_unit, currency, line.company_id, line.date_order or fields.Date.today()),
+ 'price': self.currency_id._convert(line.price_unit, currency, line.company_id, line.date_order or fields.Date.today(), round=False),
'currency_id': currency.id,
'delay': 0,
}
diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py
index cad005b4f36..4b3f28effeb 100644
--- a/addons/sale_stock/models/stock.py
+++ b/addons/sale_stock/models/stock.py
@@ -36,6 +36,15 @@ class StockMove(models.Model):
#rslt += invoices.mapped('refund_invoice_ids')
return rslt
+ def _assign_picking_post_process(self, new=False):
+ super(StockMove, self)._assign_picking_post_process(new=new)
+ if new and self.sale_line_id and self.sale_line_id.order_id:
+ self.picking_id.message_post_with_view(
+ 'mail.message_origin_link',
+ values={'self': self.picking_id, 'origin': self.sale_line_id.order_id},
+ subtype_id=self.env.ref('mail.mt_note').id)
+
+
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
diff --git a/addons/sale_timesheet/controllers/main.py b/addons/sale_timesheet/controllers/main.py
index 2b82f175ac1..48596805796 100644
--- a/addons/sale_timesheet/controllers/main.py
+++ b/addons/sale_timesheet/controllers/main.py
@@ -3,6 +3,7 @@
from ast import literal_eval
import babel
from dateutil.relativedelta import relativedelta
+import itertools
import json
from odoo import http, fields, _
@@ -88,7 +89,12 @@ class SaleTimesheetController(http.Controller):
#
# Time Repartition (per employee per billable types)
#
- employees = projects.mapped('tasks.user_id.employee_ids') | request.env['account.analytic.line'].search([('project_id', 'in', projects.ids)]).mapped('employee_id')
+ user_ids = request.env['project.task'].sudo().search_read([('project_id', 'in', projects.ids), ('user_id', '!=', False)], ['user_id'])
+ user_ids = [user_id['user_id'][0] for user_id in user_ids]
+ employee_ids = request.env['res.users'].sudo().search_read([('id', 'in', user_ids)], ['employee_ids'])
+ # flatten the list of list
+ employee_ids = list(itertools.chain.from_iterable([employee_id['employee_ids'] for employee_id in employee_ids]))
+ employees = request.env['hr.employee'].sudo().browse(employee_ids) | request.env['account.analytic.line'].search([('project_id', 'in', projects.ids)]).mapped('employee_id')
repartition_domain = [('project_id', 'in', projects.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
repartition_data = request.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py
index 11212d2728c..476f8a9e991 100644
--- a/addons/sales_team/models/crm_team.py
+++ b/addons/sales_team/models/crm_team.py
@@ -8,7 +8,7 @@ from dateutil.relativedelta import relativedelta
import json
from odoo import api, fields, models, _
-from odoo.exceptions import UserError
+from odoo.exceptions import AccessError, UserError
from odoo.release import version
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
@@ -33,8 +33,13 @@ class CrmTeam(models.Model):
team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id'))
if not team_id:
default_team_id = self.env.ref('sales_team.team_sales_department', raise_if_not_found=False)
- if default_team_id and (self.env.context.get('default_type') != 'lead' or default_team_id.use_leads):
- team_id = default_team_id
+ if default_team_id:
+ try:
+ default_team_id.check_access_rule('read')
+ except AccessError:
+ return self.env['crm.team']
+ if self.env.context.get('default_type') != 'lead' or default_team_id.use_leads and default_team_id.active:
+ team_id = default_team_id
return team_id
def _get_default_favorite_user_ids(self):
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 601cc144bd9..b760ff82a45 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -705,7 +705,7 @@ class StockMove(models.Model):
recompute = True
picking = Picking.create(move._get_new_picking_values())
move.write({'picking_id': picking.id})
-
+ move._assign_picking_post_process(new=recompute)
# If this method is called in batch by a write on a one2many and
# at some point had to create a picking, some next iterations could
# try to find back the created picking. As we look for it by searching
@@ -715,6 +715,9 @@ class StockMove(models.Model):
move.recompute()
return True
+ def _assign_picking_post_process(self, new=False):
+ pass
+
def _get_new_picking_values(self):
""" Prepares a new picking for this move as it could not be assigned to
another picking. This method is designed to be inherited. """
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index 90017ca3017..891f362843b 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -121,6 +121,8 @@ class StockScrap(models.Model):
def action_validate(self):
self.ensure_one()
+ if self.product_id.type != 'product':
+ return self.do_scrap()
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
available_qty = sum(self.env['stock.quant']._gather(self.product_id,
self.location_id,
diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml
index 469d2e8383e..ab212f45598 100644
--- a/addons/stock/report/report_deliveryslip.xml
+++ b/addons/stock/report/report_deliveryslip.xml
@@ -5,13 +5,13 @@
-
+
-
+
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index ce0554a8fe6..64da4936683 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -43,7 +43,7 @@ var ListController = BasicController.extend({
this.toolbarActions = params.toolbarActions || {};
this.editable = params.editable;
this.noLeaf = params.noLeaf;
- this.selectedRecords = []; // there is no selected record by default
+ this.selectedRecords = params.selectedRecords || [];
},
//--------------------------------------------------------------------------
@@ -177,6 +177,25 @@ var ListController = BasicController.extend({
this._toggleSidebar();
}
},
+ /**
+ * Overrides to update the list of selected records
+ *
+ * @override
+ */
+ update: function (params, options) {
+ var self = this;
+ if (options && options.keepSelection) {
+ // filter out removed records from selection
+ var res_ids = this.model.get(this.handle).res_ids;
+ this.selectedRecords = _.filter(this.selectedRecords, function (id) {
+ return _.contains(res_ids, self.model.get(id).res_id);
+ });
+ } else {
+ this.selectedRecords = [];
+ }
+ params.selectedRecords = this.selectedRecords;
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -339,7 +358,6 @@ var ListController = BasicController.extend({
* @returns {Deferred}
*/
_update: function () {
- this.selectedRecords = [];
this._toggleSidebar();
return this._super.apply(this, arguments);
},
@@ -547,7 +565,7 @@ var ListController = BasicController.extend({
_onToggleGroup: function (event) {
this.model
.toggleGroup(event.data.group.id)
- .then(this.update.bind(this, {}, {reload: false}));
+ .then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
},
});
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index f4eac700aa3..353368b40b1 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -63,7 +63,7 @@ var ListRenderer = BasicRenderer.extend({
return py.parse(py.tokenize(value));
}).value();
this.hasSelectors = params.hasSelectors;
- this.selection = [];
+ this.selection = params.selectedRecords || [];
this.pagers = []; // instantiated pagers (only for grouped lists)
this.editable = params.editable;
},
@@ -84,7 +84,9 @@ var ListRenderer = BasicRenderer.extend({
*/
updateState: function (state, params) {
this._processColumns(params.columnInvisibleFields || {});
- this.selection = [];
+ if (params.selectedRecords) {
+ this.selection = params.selectedRecords;
+ }
return this._super.apply(this, arguments);
},
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index 3082312e770..bc80017743b 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -35,6 +35,7 @@ var ListView = BasicView.extend({
*/
init: function (viewInfo, params) {
this._super.apply(this, arguments);
+ var selectedRecords = []; // there is no selected records by default
var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly";
@@ -43,11 +44,13 @@ var ListView = BasicView.extend({
this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
this.controllerParams.mode = mode;
+ this.controllerParams.selectedRecords = selectedRecords;
this.rendererParams.arch = this.arch;
this.rendererParams.hasSelectors =
'hasSelectors' in params ? params.hasSelectors : true;
this.rendererParams.editable = params.readonly ? false : this.arch.attrs.editable;
+ this.rendererParams.selectedRecords = selectedRecords;
this.loadParams.limit = this.loadParams.limit || 80;
this.loadParams.type = 'list';
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index b63ba83ae85..5b0e23157a2 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -663,6 +663,45 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('selection is kept on render without reload', function (assert) {
+ assert.expect(5);
+
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ groupBy: ['foo'],
+ viewOptions: {hasSidebar: true},
+ arch: '' +
+ '' +
+ '' +
+ '',
+ });
+
+ // open blip grouping and check all lines
+ list.$('.o_group_header:contains("blip (2)")').click();
+ list.$('.o_data_row input').click();
+ assert.strictEqual(true, list.sidebar.$el.is(':visible'),
+ "element checked so sidebar")
+
+ // open yop grouping and verify blip are still checked
+ list.$('.o_group_header:contains("yop (1)")').click()
+ assert.strictEqual(2, list.$('.o_data_row input:checked').length,
+ "opening a grouping does not uncheck others");
+ assert.strictEqual(true, list.sidebar.$el.is(':visible'),
+ "element checked so sidebar")
+
+ // close and open blip grouping and verify blip are unchecked
+ list.$('.o_group_header:contains("blip (2)")').click();
+ list.$('.o_group_header:contains("blip (2)")').click();
+ assert.strictEqual(0, list.$('.o_data_row input:checked').length,
+ "opening and closing a grouping uncheck its elements");
+ assert.strictEqual(false, list.sidebar.$el.is(':visible'),
+ "no element checked so no sidebar")
+
+ list.destroy();
+ });
+
QUnit.test('aggregates are computed correctly', function (assert) {
assert.expect(4);
diff --git a/addons/website_forum/models/res_users.py b/addons/website_forum/models/res_users.py
index 054e3a4d56a..77cbcd7d9e5 100644
--- a/addons/website_forum/models/res_users.py
+++ b/addons/website_forum/models/res_users.py
@@ -88,7 +88,9 @@ class Users(models.Model):
params['forum_id'] = forum_id
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
token_url = base_url + '/forum/validate_email?%s' % urls.url_encode(params)
- activation_template.sudo().with_context(token_url=token_url).send_mail(self.id, force_send=True)
+ with self._cr.savepoint():
+ activation_template.sudo().with_context(token_url=token_url).send_mail(
+ self.id, force_send=True, raise_exception=True)
return True
@api.one
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 279cda21c6d..617a6ad9f9f 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -248,8 +248,8 @@
- View
- Views
+ Answer
+ Answers
Comment
diff --git a/doc/cla/corporate/dynapps.md b/doc/cla/corporate/dynapps.md
index 54a678a1149..cf84923d4ed 100644
--- a/doc/cla/corporate/dynapps.md
+++ b/doc/cla/corporate/dynapps.md
@@ -8,11 +8,11 @@ declaration.
Signed,
-Stijn Houben stijn.houben@dynapps.be https://github.com/stijnh92
+Pieter Paulussen pieter.paulussen@dynapps.be https://github.com/PieterPaulussen
List of contributors:
-Stijn Houben stijn.houben@dynapps.be https://github.com/stijnh92
+Pieter Paulussen pieter.paulussen@dynapps.be https://github.com/PieterPaulussen
Raf Ven raf.ven@dynapps.be https://github.com/rven
Kurt Schmitz kurt.schmitz@dynapps.be https://github.com/kurt-schmitz
Rod Schouteden rod.schouteden@dynapps.be https://github.com/schout-it
diff --git a/doc/cla/corporate/janus.md b/doc/cla/corporate/janus.md
new file mode 100644
index 00000000000..4269c11aa22
--- /dev/null
+++ b/doc/cla/corporate/janus.md
@@ -0,0 +1,15 @@
+France, 2018-06-20
+
+JANUS agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Martin Duvergey martin.duvergey@gpsante.fr https://github.com/mduvergey
+
+List of contributors:
+
+Martin Duvergey martin.duvergey@gpsante.fr https://github.com/mduvergey
diff --git a/doc/cla/corporate/paradigmdigital.md b/doc/cla/corporate/paradigmdigital.md
new file mode 100644
index 00000000000..decfa0c1ef1
--- /dev/null
+++ b/doc/cla/corporate/paradigmdigital.md
@@ -0,0 +1,15 @@
+South Africa, 2018-08-20
+
+Paradigm Digital (Pty) Ltd agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Habib Ayob habib@paradigmdigital.co.za https://github.com/paradigmdigital
+
+List of contributors:
+
+Habib Ayob habib@paradigmdigital.co.za https://github.com/h4818
diff --git a/doc/cla/individual/oleksandrpaziuk.md b/doc/cla/individual/oleksandrpaziuk.md
new file mode 100644
index 00000000000..94f0459800e
--- /dev/null
+++ b/doc/cla/individual/oleksandrpaziuk.md
@@ -0,0 +1,11 @@
+Ukraine, 2018-06-11
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Paziuk Oleksandr o.paziuk@mobilunity.com https://github.com/oleksandrpaziuk
\ No newline at end of file
diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py
index 8ea0d574199..2663658ac3c 100644
--- a/odoo/addons/base/tests/test_orm.py
+++ b/odoo/addons/base/tests/test_orm.py
@@ -202,6 +202,31 @@ class TestORM(TransactionCase):
group_user.write({'users': [(3, user.id)]})
self.assertTrue(user.share)
+ @mute_logger('odoo.models')
+ def test_unlink_with_property(self):
+ """ Verify that unlink removes the related ir.property as unprivileged user """
+ user = self.env['res.users'].create({
+ 'name': 'Justine Bridou',
+ 'login': 'saucisson',
+ 'groups_id': [4, self.ref('base.group_partner_manager')],
+ })
+ p1 = self.env['res.partner'].sudo(user).create({'name': 'Zorro'})
+ p1_prop = self.env['ir.property'].sudo(user).create({
+ 'name': 'Slip en laine',
+ 'res_id': 'res.partner,{}'.format(p1.id),
+ 'fields_id': self.env['ir.model.fields'].search([
+ ('model', '=', 'res.partner'), ('name', '=', 'ref')], limit=1).id,
+ 'value_text': 'Nain poilu',
+ 'type': 'char',
+ })
+
+ # Unlink with unprivileged user
+ p1.unlink()
+
+ # ir.property is deleted
+ self.assertEqual(
+ p1_prop.exists(), self.env['ir.property'], 'p1_prop should have been deleted')
+
def test_create_multi(self):
""" create for multiple records """
# assumption: 'res.bank' does not override 'create'
diff --git a/odoo/models.py b/odoo/models.py
index c4e748ca4d9..3aa493663eb 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -3088,9 +3088,8 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# Delete the records' properties.
with self.env.norecompute():
- self.env['ir.property'].search([('res_id', 'in', refs)]).unlink()
-
self.check_access_rule('unlink')
+ self.env['ir.property'].search([('res_id', 'in', refs)]).sudo().unlink()
cr = self._cr
Data = self.env['ir.model.data'].sudo().with_context({})
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index 2067f3373d7..668d0678f88 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -48,6 +48,7 @@ class _Cleaner(clean.Cleaner):
'float', 'vertical-align', 'display',
'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right',
'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right',
+ 'white-space',
# box model
'border', 'border-color', 'border-radius', 'border-style', 'border-width', 'border-top',
'height', 'width', 'max-width', 'min-width', 'min-height',
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index 6b785c0d47a..a225231e062 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -1101,10 +1101,14 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
# discard the target from the POT targets.
src = dic['src']
- if src in pot_targets:
- target = pot_targets[src]
- target.value = dic['value']
- target.targets.discard((dic['type'], dic['name'], dic['res_id']))
+ target = pot_targets.get(src)
+ if not target or (dic['type'], dic['name'], dic['res_id']) not in target.targets:
+ _logger.info("Translation '%s' (%s, %s, %s) not found in reference pot, skipping",
+ src[:60], dic['type'], dic['name'], dic['res_id'])
+ return
+
+ target.value = dic['value']
+ target.targets.discard((dic['type'], dic['name'], dic['res_id']))
# This would skip terms that fail to specify a res_id
res_id = dic['res_id']
|