diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index a9d82701..8a20bb4c 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -1,3 +1,4 @@ +from flectra import api from flectra.addons.account.tests.account_test_classes import AccountingTestCase import time import unittest diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py index 0630163e..6a25659f 100644 --- a/addons/base_address_extended/models/base_address_extended.py +++ b/addons/base_address_extended/models/base_address_extended.py @@ -137,6 +137,12 @@ class Partner(models.Model): for k, v in vals.items(): partner[k] = v + def write(self, vals): + res = super(Partner, self).write(vals) + if 'country_id' in vals: + self._set_street() + return res + class Company(models.Model): _inherit = 'res.company' diff --git a/addons/base_address_extended/tests/test_street_fields.py b/addons/base_address_extended/tests/test_street_fields.py index db6e04fe..00e5dc81 100644 --- a/addons/base_address_extended/tests/test_street_fields.py +++ b/addons/base_address_extended/tests/test_street_fields.py @@ -51,5 +51,5 @@ class TestStreetFields(TransactionCase): self.write_and_assert(p1, {'street': 'Chaussee de Namur'}, 'Chaussee de Namur', 'Chaussee de Namur', '', '') self.write_and_assert(p1, {'street_name': 'Chee de Namur', 'street_number': '40'}, 'Chee de Namur, 40', 'Chee de Namur', '40', '') self.write_and_assert(p1, {'street_number2': '4'}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4') - #we don't recompute the street fields when we change the country - self.write_and_assert(p1, {'country_id': self.env.ref('base.us').id}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4') + #we do recompute the street fields when we change the country + self.write_and_assert(p1, {'country_id': self.env.ref('base.us').id}, '40/4 Chee de Namur', 'Chee de Namur', '40', '4') diff --git a/addons/base_sparse_field/models/models.py b/addons/base_sparse_field/models/models.py index 43fc1397..05f9704e 100644 --- a/addons/base_sparse_field/models/models.py +++ b/addons/base_sparse_field/models/models.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from flectra import models, fields, api +from flectra import models, fields, api, _ +from flectra.exceptions import UserError class IrModelFields(models.Model): diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 1a58d638..8fb7afce 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -4,7 +4,7 @@ import base64 from email.utils import formataddr import re -import uuid +from uuid import uuid4 from flectra import _, api, fields, models, modules, tools from flectra.exceptions import UserError @@ -56,7 +56,7 @@ class Channel(models.Model): ('channel', 'Channel')], 'Channel Type', default='channel') description = fields.Text('Description') - uuid = fields.Char('UUID', size=50, index=True, default=lambda self: '%s' % uuid.uuid4(), copy=False) + uuid = fields.Char('UUID', size=50, index=True, default=lambda self: str(uuid4()), copy=False) email_send = fields.Boolean('Send messages by email', default=False) # multi users channel channel_last_seen_partner_ids = fields.One2many('mail.channel.partner', 'channel_id', string='Last Seen') diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 04f99fe1..5380d4e0 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta from flectra import api, fields, models, SUPERUSER_ID, _ from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT from flectra.tools.float_utils import float_is_zero, float_compare -from flectra.exceptions import UserError, AccessError, ValidationError +from flectra.exceptions import UserError, AccessError from flectra.tools.misc import formatLang from flectra.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP from flectra.addons import decimal_precision as dp @@ -167,8 +167,8 @@ class PurchaseOrder(models.Model): ('invoiced', 'No Bill to Receive'), ], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no') - picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0, store=True) - picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False, store=True) + picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0, store=True, compute_sudo=True) + picking_ids = fields.Many2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False, store=True, compute_sudo=True) # There is no inverse function on purpose since the date may be different on each line date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', store=True, index=True) @@ -669,7 +669,7 @@ class PurchaseOrderLine(models.Model): # Replace by invoiced Qty qty_invoiced = fields.Float(compute='_compute_qty_invoiced', string="Billed Qty", digits=dp.get_precision('Product Unit of Measure'), store=True) - qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", digits=dp.get_precision('Product Unit of Measure'), store=True) + qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", digits=dp.get_precision('Product Unit of Measure'), store=True, compute_sudo=True) partner_id = fields.Many2one('res.partner', related='order_id.partner_id', string='Partner', readonly=True, store=True) currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True) diff --git a/addons/purchase_mrp/models/purchase_mrp.py b/addons/purchase_mrp/models/purchase_mrp.py index 7b1ef51f..0ac45665 100644 --- a/addons/purchase_mrp/models/purchase_mrp.py +++ b/addons/purchase_mrp/models/purchase_mrp.py @@ -8,7 +8,7 @@ from flectra.tools import float_compare class PurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' - qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", store=True) + qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", store=True, compute_sudo=True) def _compute_qty_received(self): super(PurchaseOrderLine, self)._compute_qty_received() diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py index f41f4c61..b00e71cd 100644 --- a/addons/stock_account/models/account_invoice.py +++ b/addons/stock_account/models/account_invoice.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. -from flectra import api, models +from flectra import api, models, fields import logging diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py index 9cc05fb9..fea3593e 100644 --- a/addons/stock_landed_costs/models/stock_landed_cost.py +++ b/addons/stock_landed_costs/models/stock_landed_cost.py @@ -107,7 +107,7 @@ class LandedCost(models.Model): 'landed_cost_value': new_landed_cost_value, 'value': line.move_id.value + cost_to_add, 'remaining_value': line.move_id.remaining_value + cost_to_add, - 'price_unit': (line.move_id.value + new_landed_cost_value) / line.move_id.product_qty, + 'price_unit': (line.move_id.value + cost_to_add) / line.move_id.product_qty, }) # `remaining_qty` is negative if the move is out and delivered proudcts that were not # in stock. diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 09aec6f0..96c2d34a 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1758,7 +1758,7 @@ var StatInfo = AbstractField.extend({ */ _render: function () { var options = { - value: this.value || 0, + value: this._formatValue(this.value || 0), }; if (! this.attrs.nolabel) { if (this.nodeOptions.label_field && this.recordData[this.nodeOptions.label_field]) { diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 78aafe52..8ed84c9d 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -4000,6 +4000,40 @@ QUnit.module('basic_fields', { QUnit.module('StatInfo'); + QUnit.test('statinfo widget formats decimal precision', function (assert) { + // sometimes the round method can return numbers such as 14.000001 + // when asked to round a number to 2 decimals, as such is the behaviour of floats. + // we check that even in that eventuality, only two decimals are displayed + assert.expect(2); + + this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'}; + this.data.partner.records[0].monetary = 9.999999; + this.data.partner.records[0].currency_id = 1; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '
', + res_id: 1, + }); + + // formatFloat renders according to this.field.digits + assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(0).text(), + '0.4', "Default precision should be [16,1]"); + assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(1).text(), + '10.00', "Currency decimal precision should be 2"); + + form.destroy(); + }); + QUnit.test('statinfo widget in form view', function (assert) { assert.expect(9); diff --git a/addons/web_editor/static/src/js/editor/translator.js b/addons/web_editor/static/src/js/editor/translator.js index 05ed01fd..b8b99373 100644 --- a/addons/web_editor/static/src/js/editor/translator.js +++ b/addons/web_editor/static/src/js/editor/translator.js @@ -8,6 +8,7 @@ var Widget = require('web.Widget'); var weContext = require('web_editor.context'); var rte = require('web_editor.rte'); var weWidgets = require('web_editor.widget'); +var Dialog = require('web.Dialog'); var _t = core._t; @@ -26,6 +27,7 @@ var RTETranslatorWidget = rte.Class.extend({ * @override */ _saveElement: function ($el, context, withLang) { + var self = this; if ($el.data('oe-translation-id')) { return this._rpc({ model: 'ir.translation', @@ -35,7 +37,9 @@ var RTETranslatorWidget = rte.Class.extend({ this._getEscapedElement($el).html(), context || weContext.get() ], - }); + }).fail(function (error) { + Dialog.alert(null, error.data.message); + }); } return this._super($el, context, withLang === undefined ? true : withLang); }, diff --git a/flectra/addons/base/ir/ir_model.py b/flectra/addons/base/ir/ir_model.py index 1678e860..797d1a58 100644 --- a/flectra/addons/base/ir/ir_model.py +++ b/flectra/addons/base/ir/ir_model.py @@ -546,23 +546,29 @@ class IrModelFields(models.Model): This method prevents the modification/deletion of many2one fields that have an inverse one2many, for instance. """ + failed_dependencies = [] + for rec in self: + model = self.env[rec.model] + field = model._fields[rec.name] + for dependant, path in model._field_triggers.get(field, ()): + if dependant.manual: + failed_dependencies.append((field, dependant)) + for inverse in model._field_inverses.get(field, ()): + if inverse.manual and inverse.type == 'one2many': + failed_dependencies.append((field, inverse)) + + if not self._context.get(MODULE_UNINSTALL_FLAG) and failed_dependencies: + msg = _("The field '%s' cannot be removed because the field '%s' depends on it.") + raise UserError(msg % failed_dependencies[0]) + elif failed_dependencies: + dependants = {rel[1] for rel in failed_dependencies} + to_unlink = [self._get(field.model_name, field.name) for field in dependants] + self.browse().union(*to_unlink).unlink() + self = self.filtered(lambda record: record.state == 'manual') if not self: return - for record in self: - model = self.env[record.model] - field = model._fields[record.name] - if field.type == 'many2one' and model._field_inverses.get(field): - if self._context.get(MODULE_UNINSTALL_FLAG): - # automatically unlink the corresponding one2many field(s) - inverses = self.search([('relation', '=', field.model_name), - ('relation_field', '=', field.name)]) - inverses.unlink() - continue - msg = _("The field '%s' cannot be removed because the field '%s' depends on it.") - raise UserError(msg % (field, model._field_inverses[field][0])) - # remove fields from registry, and check that views are not broken fields = [self.env[record.model]._pop_field(record.name) for record in self] domain = expression.OR([('arch_db', 'like', record.name)] for record in self) diff --git a/flectra/addons/test_pylint/tests/test_pylint.py b/flectra/addons/test_pylint/tests/test_pylint.py index 96b722e9..65e1b1ba 100644 --- a/flectra/addons/test_pylint/tests/test_pylint.py +++ b/flectra/addons/test_pylint/tests/test_pylint.py @@ -24,9 +24,10 @@ _logger = logging.getLogger(__name__) class TestPyLint(TransactionCase): ENABLED_CODES = [ - 'E0601', # using variable before assignment - 'W0123', # eval used - 'W0101', # unreachable code + 'used-before-assignment', + 'undefined-variable', + 'eval-used', + 'unreachable', 'mixed-indentation', diff --git a/flectra/fields.py b/flectra/fields.py index e444d938..6f3be17e 100644 --- a/flectra/fields.py +++ b/flectra/fields.py @@ -1630,7 +1630,8 @@ class Datetime(Field): # Received data is returned as buffer (in Python 2) or memoryview (in Python 3). _BINARY = memoryview if pycompat.PY2: - _BINARY = buffer #pylint: disable=buffer-builtin + #pylint: disable=buffer-builtin,undefined-variable + _BINARY = buffer class Binary(Field): type = 'binary' diff --git a/flectra/modules/migration.py b/flectra/modules/migration.py index fc2149a0..52ce398e 100644 --- a/flectra/modules/migration.py +++ b/flectra/modules/migration.py @@ -21,7 +21,8 @@ if pycompat.PY2: fp, fname = tools.file_open(path, pathinfo=True) fp2 = None - if not isinstance(fp, file): # pylint: disable=file-builtin + # pylint: disable=file-builtin,undefined-variable + if not isinstance(fp, file): # imp.load_source need a real file object, so we create # one from the file-like object we get from file_open fp2 = os.tmpfile() diff --git a/flectra/tools/misc.py b/flectra/tools/misc.py index bc744f50..abb8f4f1 100644 --- a/flectra/tools/misc.py +++ b/flectra/tools/misc.py @@ -1069,7 +1069,7 @@ def formatLang(env, value, digits=None, grouping=True, monetary=False, dp=False, digits = decimal_precision_obj.precision_get(dp) elif currency_obj: digits = currency_obj.decimal_places - elif (hasattr(value, '_field') and isinstance(value._field, (float_field, function_field)) and value._field.digits): + elif (hasattr(value, '_field') and getattr(value._field, 'digits', None)): digits = value._field.digits[1] if not digits and digits is not 0: digits = DEFAULT_DIGITS diff --git a/flectra/tools/pycompat.py b/flectra/tools/pycompat.py index 14195c4f..5f25e23e 100644 --- a/flectra/tools/pycompat.py +++ b/flectra/tools/pycompat.py @@ -13,7 +13,7 @@ PY2 = sys.version_info[0] == 2 _Writer = collections.namedtuple('_Writer', 'writerow writerows') if PY2: - # pylint: disable=long-builtin,unichr-builtin,unicode-builtin + # pylint: disable=long-builtin,unichr-builtin,unicode-builtin,undefined-variable unichr = unichr text_type = unicode string_types = (str, unicode) diff --git a/flectra/tools/xml_utils.py b/flectra/tools/xml_utils.py index 3323d095..237f2f91 100644 --- a/flectra/tools/xml_utils.py +++ b/flectra/tools/xml_utils.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from lxml import etree from flectra.tools.misc import file_open - +from flectra.exceptions import UserError def check_with_xsd(tree_or_str, stream): raise UserError("Method 'check_with_xsd' deprecated ") diff --git a/requirements.txt b/requirements.txt index c86a90de..0daa655e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,14 @@ decorator==4.0.10 docutils==0.12 ebaysdk==2.1.5 feedparser==5.2.1 -gevent==1.1.2 ; sys_platform != 'win32' -greenlet==0.4.10 +gevent==1.1.2 ; sys_platform != 'win32' and python_version < '3.7' +gevent==1.3.4 ; sys_platform != 'win32' and python_version >= '3.7' +greenlet==0.4.10 ; python_version < '3.7' +greenlet==0.4.13 ; python_version >= '3.7' html2text==2016.9.19 Jinja2==2.8 -lxml==3.7.1 ; sys_platform != 'win32' +lxml==3.7.1 ; sys_platform != 'win32' and python_version < '3.7' +lxml==4.2.3 ; sys_platform != 'win32' and python_version >= '3.7' lxml ; sys_platform == 'win32' Mako==1.0.4 MarkupSafe==0.23