diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index b3f719f1449..6253235442d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -238,6 +238,7 @@ class AccountMove(models.Model): @api.multi def _reverse_move(self, date=None, journal_id=None, auto=False): self.ensure_one() + date = date or fields.Date.today() with self.env.norecompute(): reversed_move = self.copy(default={ 'date': date, @@ -502,8 +503,8 @@ class AccountMoveLine(models.Model): is_unaffected_earnings_line = fields.Boolean(string="Is Unaffected Earnings Line", compute="_compute_is_unaffected_earnings_line", help="Tells whether or not this line belongs to an unaffected earnings account") _sql_constraints = [ - ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'), - ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'), + ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry! Credit or debit should be zero.'), + ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry! Credit and debit should be positive.'), ] @api.model diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 2f45dd4fff2..6e2ee099ad6 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -500,17 +500,12 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def create_move(self, post_move=True): created_moves = self.env['account.move'] - # `line.move_id` was invalidated from the cache at each iteration - # To prevent to refetch `move_id` of all lines at each iteration just to check a UserError, - # we use an intermediar dict which stores the information the UserError check requires. - line_moves = {line: line.move_id for line in self} + if self.mapped('move_id'): + raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) for line in self: - if line_moves[line]: - raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) move_vals = self._prepare_move(line) move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) - line_moves[line] = move created_moves |= move if post_move and created_moves: diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index 80994d946fa..aafd119c49a 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -162,7 +162,7 @@
- +
@@ -182,10 +182,10 @@ attrs="{'required': [('month_by', '=', 'date'), ('rrule_type', '=', 'monthly')], 'invisible': [('month_by', '!=', 'date')]}"/> diff --git a/addons/l10n_be_intrastat_2019/__manifest__.py b/addons/l10n_be_intrastat_2019/__manifest__.py index b3de01cd46b..48554dc064b 100644 --- a/addons/l10n_be_intrastat_2019/__manifest__.py +++ b/addons/l10n_be_intrastat_2019/__manifest__.py @@ -10,6 +10,7 @@ Adds the possibility to specify the origin country of goods and the partner VAT 'depends': ['l10n_be_intrastat'], 'data': [ 'views/account_invoice_line_view.xml', + 'views/report_invoice.xml', ], 'auto_install': True, } diff --git a/addons/l10n_be_intrastat_2019/views/report_invoice.xml b/addons/l10n_be_intrastat_2019/views/report_invoice.xml new file mode 100644 index 00000000000..c693021d0dc --- /dev/null +++ b/addons/l10n_be_intrastat_2019/views/report_invoice.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/addons/l10n_mx/data/account_tag_data.xml b/addons/l10n_mx/data/account_tag_data.xml index 1e8b23dacda..a025c4dd4e2 100644 --- a/addons/l10n_mx/data/account_tag_data.xml +++ b/addons/l10n_mx/data/account_tag_data.xml @@ -2077,212 +2077,230 @@ accounts A + + 401.04 Ventas y/o servicios gravados realizados en zona fronteriza norte + 4 + accounts + A + + + 401.05 Ventas y/o servicios gravados realizados en zona fronteriza norte de contado + 4 + accounts + A + + + 401.06 Ventas y/o servicios gravados realizados en zona fronteriza norte a crédito + 4 + accounts + A + - 401.04 Ventas y/o servicios gravados al 0% + 401.07 Ventas y/o servicios gravados al 0% 4 accounts A - 401.05 Ventas y/o servicios gravados al 0% de contado + 401.08 Ventas y/o servicios gravados al 0% de contado 4 accounts A - 401.06 Ventas y/o servicios gravados al 0% a crédito + 401.09 Ventas y/o servicios gravados al 0% a crédito 4 accounts A - 401.07 Ventas y/o servicios exentos + 401.10 Ventas y/o servicios exentos 4 accounts A - 401.08 Ventas y/o servicios exentos de contado + 401.11 Ventas y/o servicios exentos de contado 4 accounts A - 401.09 Ventas y/o servicios exentos a crédito + 401.12 Ventas y/o servicios exentos a crédito 4 accounts A - 401.10 Ventas y/o servicios gravados a la tasa general nacionales partes relacionadas + 401.13 Ventas y/o servicios gravados a la tasa general nacionales partes relacionadas 4 accounts A - 401.11 Ventas y/o servicios gravados a la tasa general extranjeros partes relacionadas + 401.14 Ventas y/o servicios gravados a la tasa general extranjeros partes relacionadas 4 accounts A - 401.12 Ventas y/o servicios gravados al 0% nacionales partes relacionadas + 401.15 Ventas y/o servicios gravados al 0% nacionales partes relacionadas 4 accounts A - 401.13 Ventas y/o servicios gravados al 0% extranjeros partes relacionadas + 401.16 Ventas y/o servicios gravados al 0% extranjeros partes relacionadas 4 accounts A - 401.14 Ventas y/o servicios exentos nacionales partes relacionadas + 401.17 Ventas y/o servicios exentos nacionales partes relacionadas 4 accounts A - 401.15 Ventas y/o servicios exentos extranjeros partes relacionadas + 401.18 Ventas y/o servicios exentos extranjeros partes relacionadas 4 accounts A - 401.16 Ingresos por servicios administrativos + 401.19 Ingresos por servicios administrativos 4 accounts A - 401.17 Ingresos por servicios administrativos nacionales partes relacionadas + 401.20 Ingresos por servicios administrativos nacionales partes relacionadas 4 accounts A - 401.18 Ingresos por servicios administrativos extranjeros partes relacionadas + 401.21 Ingresos por servicios administrativos extranjeros partes relacionadas 4 accounts A - 401.19 Ingresos por servicios profesionales + 401.22 Ingresos por servicios profesionales 4 accounts A - 401.20 Ingresos por servicios profesionales nacionales partes relacionadas + 401.23 Ingresos por servicios profesionales nacionales partes relacionadas 4 accounts A - 401.21 Ingresos por servicios profesionales extranjeros partes relacionadas + 401.24 Ingresos por servicios profesionales extranjeros partes relacionadas 4 accounts A - 401.22 Ingresos por arrendamiento + 401.25 Ingresos por arrendamiento 4 accounts A - 401.23 Ingresos por arrendamiento nacionales partes relacionadas + 401.26 Ingresos por arrendamiento nacionales partes relacionadas 4 accounts A - 401.24 Ingresos por arrendamiento extranjeros partes relacionadas + 401.27 Ingresos por arrendamiento extranjeros partes relacionadas 4 accounts A - 401.25 Ingresos por exportación + 401.28 Ingresos por exportación 4 accounts A - 401.26 Ingresos por comisiones + 401.29 Ingresos por comisiones 4 accounts A - 401.27 Ingresos por maquila + 401.30 Ingresos por maquila 4 accounts A - 401.28 Ingresos por coordinados + 401.31 Ingresos por coordinados 4 accounts A - 401.29 Ingresos por regalías + 401.32 Ingresos por regalías 4 accounts A - 401.30 Ingresos por asistencia técnica + 401.33 Ingresos por asistencia técnica 4 accounts A - 401.31 Ingresos por donativos + 401.34 Ingresos por donativos 4 accounts A - 401.32 Ingresos por intereses (actividad propia) + 401.35 Ingresos por intereses (actividad propia) 4 accounts A - 401.33 Ingresos de copropiedad + 401.36 Ingresos de copropiedad 4 accounts A - 401.34 Ingresos por fideicomisos + 401.37 Ingresos por fideicomisos 4 accounts A - 401.35 Ingresos por factoraje financiero + 401.38 Ingresos por factoraje financiero 4 accounts A - 401.36 Ingresos por arrendamiento financiero + 401.39 Ingresos por arrendamiento financiero 4 accounts A - 401.37 Ingresos de extranjeros con establecimiento en el país + 401.40 Ingresos de extranjeros con establecimiento en el país 4 accounts A - 401.38 Otros ingresos propios + 401.41 Otros ingresos propios 4 accounts A @@ -2293,20 +2311,26 @@ accounts D + + 402.02 Devoluciones, descuentos o bonificaciones sobre ventas y/o servicios en zona fronteriza norte + 4 + accounts + D + - 402.02 Devoluciones, descuentos o bonificaciones sobre ventas y/o servicios al 0% + 402.03 Devoluciones, descuentos o bonificaciones sobre ventas y/o servicios al 0% 4 accounts D - 402.03 Devoluciones, descuentos o bonificaciones sobre ventas y/o servicios exentos + 402.04 Devoluciones, descuentos o bonificaciones sobre ventas y/o servicios exentos 4 accounts D - 402.04 Devoluciones, descuentos o bonificaciones de otros ingresos + 402.05 Devoluciones, descuentos o bonificaciones de otros ingresos 4 accounts D diff --git a/addons/payment_adyen/models/payment.py b/addons/payment_adyen/models/payment.py index 03787a73233..c2c621254c3 100644 --- a/addons/payment_adyen/models/payment.py +++ b/addons/payment_adyen/models/payment.py @@ -170,7 +170,7 @@ class AcquirerAdyen(models.Model): 'sessionValidity': tmp_date.isoformat('T')[:19] + "Z", 'resURL': urls.url_join(base_url, AdyenController._return_url), 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url', '') else False, - 'shopperEmail': values.get('partner_email', ''), + 'shopperEmail': values.get('partner_email') or values.get('billing_partner_email') or '', }) values['merchantSig'] = self._adyen_generate_merchant_sig_sha256('in', values) diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index d8806a013d7..7515127b42a 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -85,9 +85,9 @@ class AccountInvoice(models.Model): self.currency_id = self.purchase_id.currency_id vendor_ref = self.purchase_id.partner_ref - if vendor_ref: - self.reference = ", ".join([self.reference, vendor_ref]) if ( - self.reference and vendor_ref not in self.reference) else vendor_ref + if vendor_ref and (not self.reference or ( + vendor_ref + ", " not in self.reference and not self.reference.endswith(vendor_ref))): + self.reference = ", ".join([self.reference, vendor_ref]) if self.reference else vendor_ref new_lines = self.env['account.invoice.line'] for line in self.purchase_id.order_line - self.invoice_line_ids.mapped('purchase_line_id'): diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 2d22c43abc1..f4624bc8e81 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -1021,11 +1021,15 @@ class ProcurementRule(models.Model): if price_unit and seller and line.order_id.currency_id and seller.currency_id != line.order_id.currency_id: price_unit = seller.currency_id.compute(price_unit, line.order_id.currency_id) - return { + res = { 'product_qty': line.product_qty + procurement_uom_po_qty, 'price_unit': price_unit, 'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])] } + orderpoint_id = values.get('orderpoint_id') + if orderpoint_id: + res['orderpoint_id'] = orderpoint_id.id + return res @api.multi def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, partner): diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 471fcc48da2..160db356411 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -170,7 +170,7 @@ class Route(models.Model): @api.onchange('warehouse_selectable') def _onchange_warehouse_selectable(self): if not self.warehouse_selectable: - self.warehouse_ids = [] + self.warehouse_ids = [(5, 0, 0)] def write(self, values): '''when a route is deactivated, deactivate also its pull and push rules''' diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index b51fa49ed9a..fdaddc72329 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1186,6 +1186,7 @@ var AbstractFieldBinary = AbstractField.extend({ } }, on_clear: function () { + this.$('.o_input_file').val(''); this.set_filename(''); this._setValue(false); this._render(); diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 9e94db14cb7..9b9d3315cb8 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -1718,6 +1718,35 @@ QUnit.module('basic_fields', { list.destroy(); }); + QUnit.test("binary fields input value is empty whean clearing after uploading", function (assert) { + assert.expect(2); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + res_id: 1, + }); + + form.$buttons.find('.btn:contains("Edit")').click(); + // // We need to convert the input type since we can't programmatically set the value of a file input + form.$('.o_input_file').attr('type', 'text').val('coucou.txt'); + + assert.strictEqual(form.$('.o_input_file').val(), 'coucou.txt', + "input value should be changed to \"coucou.txt\""); + + form.$('.o_field_binary_file > .o_clear_file_button').click(); + + assert.strictEqual(form.$('.o_input_file').val(), '', + "input value should be empty"); + + form.destroy(); + }); + QUnit.test('field text in editable list view', function (assert) { assert.expect(1); diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 4dfe574c52e..d1a5a905651 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1392,6 +1392,7 @@ var LinkDialog = Dialog.extend({ } this.data.range = range.create(sc, so, ec, eo); + $(editable).data("range", this.data.range); this.data.range.select(); } else { nodes = dom.ancestor(sc, dom.isAnchor).childNodes; diff --git a/addons/web_editor/static/tests/web_editor_tests.js b/addons/web_editor/static/tests/web_editor_tests.js index 2ea6078c44b..e0a6051a78b 100644 --- a/addons/web_editor/static/tests/web_editor_tests.js +++ b/addons/web_editor/static/tests/web_editor_tests.js @@ -5,6 +5,7 @@ var FormView = require('web.FormView'); var testUtils = require('web.test_utils'); var core = require('web.core'); var web_editor = require('web_editor.editor'); +var LinkDialog = require('web_editor.widget').LinkDialog; var _t = core._t; @@ -324,4 +325,64 @@ QUnit.test('html_frame saving in edit mode (editor and content fully loaded)', f }); +QUnit.test('discard on link dialog should select content of link in web_editor', function (assert) { + var done = assert.async(); + assert.expect(5); + + var form = testUtils.createView({ + View: FormView, + model: 'mass.mailing', + data: this.data, + arch: '
' + + '' + + '', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.note-editable').html(), '
yep
', + "should have rendered the field correctly in edit"); + + var $fieldEdit = form.$('.oe_form_field[name="body"]'); + var $editable = $fieldEdit.find('.field_body'); + + // create range, default startoffset:0, endOffset:length of element + var range = document.createRange(); + var selection = window.getSelection(); + range.selectNodeContents($editable[0].childNodes[0]); + selection.removeAllRanges(); + selection.addRange(range); + + var content = selection.getRangeAt(range); + assert.strictEqual(content.startOffset, 0, "selection starts at index 0"); + assert.strictEqual(content.endOffset, 3, "selection ends at index 3"); + + var defLinkDialog = $.Deferred(); + testUtils.patch(LinkDialog, { + init: function () { + this._super.apply(this, arguments); + this.opened(defLinkDialog.resolve.bind(defLinkDialog)); + } + }); + + // click on 'link' button + $fieldEdit.find('.note-toolbar button[data-event="showLinkDialog"]').click(); + + defLinkDialog.then(function () { + $('.modal .modal-footer .btn-default').click(); // click on 'discard' + + var content = selection.getRangeAt(range); + assert.strictEqual(content.startOffset, 0, + "selection should remain same after discarding modal, starts at index 0"); + assert.strictEqual(content.endOffset, 3, + "selection should remain same after discarding modal, ends at index 3"); + + testUtils.unpatch(LinkDialog); + form.destroy(); + done(); + }); + +}); + }); diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index ead68c28274..639118cf73d 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -93,7 +93,7 @@ class Website(Home): if request.env['res.users'].browse(request.uid).has_group('base.group_user'): redirect = b'/web?' + request.httprequest.query_string else: - redirect = '/' + redirect = '/my' return http.redirect_with_hash(redirect) return response @@ -443,3 +443,8 @@ class WebsiteBinary(http.Controller): if unique: kw['unique'] = unique return Binary().content_image(**kw) + + @http.route(['/favicon.ico'], type='http', auth='public', website=True) + def favicon(self, **kw): + # when opening a pdf in chrome, chrome tries to open the default favicon url + return self.content_image(model='website', id=str(request.website.id), field='favicon', **kw) diff --git a/doc/cla/individual/luisiniguezh.md b/doc/cla/individual/luisiniguezh.md new file mode 100644 index 00000000000..11a1613066b --- /dev/null +++ b/doc/cla/individual/luisiniguezh.md @@ -0,0 +1,11 @@ +Mexico, 2018-12-05 + +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, + +Luis Arturo Íñiguez Hernández luis.iniguezh@gmail.com https://github.com/luisiniguezh diff --git a/doc/cla/individual/michelezak.md b/doc/cla/individual/michelezak.md new file mode 100644 index 00000000000..d3bf2ff1b6f --- /dev/null +++ b/doc/cla/individual/michelezak.md @@ -0,0 +1,11 @@ +Italy, 2019-06-04 + +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, + +Michele Zaccheddu michelezak93@gmail.com https://github.com/MicheleZak diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index 91f5dd256e1..bca3dbfe58e 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -737,3 +737,11 @@ class IrActionsActClient(models.Model): for record in self: params = record.params record.params_store = repr(params) if isinstance(params, dict) else params + + def _get_default_form_view(self): + doc = super(IrActionsActClient, self)._get_default_form_view() + params = doc.find(".//field[@name='params']") + params.getparent().remove(params) + params_store = doc.find(".//field[@name='params_store']") + params_store.getparent().remove(params_store) + return doc diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 3a5403e2b06..c0133e4b653 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -719,7 +719,7 @@ class IrModelFields(models.Model): else: if column_rename: raise UserError(_('Can only rename one field at a time!')) - column_rename = (obj._table, item.name, vals['name'], item.index) + column_rename = (obj._table, item.name, vals['name'], item.index, item.store) # We don't check the 'state', because it might come from the context # (thus be set for multiple fields) and will be ignored anyway. @@ -737,10 +737,11 @@ class IrModelFields(models.Model): if column_rename: # rename column in database, and its corresponding index if present - table, oldname, newname, index = column_rename - self._cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (table, oldname, newname)) - if index: - self._cr.execute('ALTER INDEX "%s_%s_index" RENAME TO "%s_%s_index"' % (table, oldname, table, newname)) + table, oldname, newname, index, stored = column_rename + if stored: + self._cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % (table, oldname, newname)) + if index: + self._cr.execute('ALTER INDEX "%s_%s_index" RENAME TO "%s_%s_index"' % (table, oldname, table, newname)) if column_rename or patched_models: # setup models, this will reload all manual fields in registry diff --git a/odoo/addons/base/views/ir_model_views.xml b/odoo/addons/base/views/ir_model_views.xml index b20c49a413d..9ee259d60f1 100644 --- a/odoo/addons/base/views/ir_model_views.xml +++ b/odoo/addons/base/views/ir_model_views.xml @@ -109,7 +109,7 @@ - +
@@ -295,7 +295,7 @@ - +
diff --git a/odoo/addons/test_lint/__init__.py b/odoo/addons/test_lint/__init__.py new file mode 100644 index 00000000000..0e68430dabe --- /dev/null +++ b/odoo/addons/test_lint/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, SUPERUSER_ID + + +# TO REMOVE in master +def uninstall_test_pylint(cr): + env = api.Environment(cr, SUPERUSER_ID, {}) + env['ir.module.module'].search([ + ('name', '=', 'test_pylint'), + ('state', '=', 'installed') + ]).write({'state': 'uninstalled'}) diff --git a/odoo/addons/test_lint/__manifest__.py b/odoo/addons/test_lint/__manifest__.py new file mode 100644 index 00000000000..554e20ba3b2 --- /dev/null +++ b/odoo/addons/test_lint/__manifest__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-lint', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to test Odoo code with various linters.""", + 'maintainer': 'Odoo SA', + 'depends': ['base'], + 'installable': True, + 'auto_install': False, + 'pre_init_hook': 'uninstall_test_pylint' +} diff --git a/odoo/addons/test_lint/tests/__init__.py b/odoo/addons/test_lint/tests/__init__.py new file mode 100644 index 00000000000..d085060f6bf --- /dev/null +++ b/odoo/addons/test_lint/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_pylint +from . import test_ecmascript diff --git a/odoo/addons/test_pylint/tests/_odoo_checkers.py b/odoo/addons/test_lint/tests/_odoo_checkers.py similarity index 100% rename from odoo/addons/test_pylint/tests/_odoo_checkers.py rename to odoo/addons/test_lint/tests/_odoo_checkers.py diff --git a/odoo/addons/test_lint/tests/test_ecmascript.py b/odoo/addons/test_lint/tests/test_ecmascript.py new file mode 100644 index 00000000000..46008b0587d --- /dev/null +++ b/odoo/addons/test_lint/tests/test_ecmascript.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import os +import re +import subprocess +from unittest import skipIf +from odoo.tests.common import TransactionCase +from odoo import tools +from odoo.modules import get_modules, get_module_path + +MAX_ES_VERSION = 'es5' + +_logger = logging.getLogger(__name__) + +try: + es_check = tools.misc.find_in_path('es-check') +except IOError: + es_check = None + + +@skipIf(es_check is None, "es-check tool not found on this system") +class TestECMAScriptVersion(TransactionCase): + + longMessage = True + + def test_ecmascript_version(self): + """ Test that there is no unsupported ecmascript in javascript files """ + + black_re = re.compile(r'summernote.+(intro\.js|outro.js)$') + + mod_paths = [get_module_path(m) for m in get_modules()] + files_to_check = [] + for p in mod_paths: + for dp, _, file_names in os.walk(p): + if 'static/test' in dp: + continue + for fn in file_names: + fullpath_name = os.path.join(dp, fn) + if fullpath_name.endswith('.js') and not black_re.search(fullpath_name): + files_to_check.append(fullpath_name) + + _logger.info('Testing %s js files', len(files_to_check)) + cmd = [es_check, MAX_ES_VERSION] + files_to_check + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = process.communicate() + self.assertEqual(process.returncode, 0, msg=out) diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_lint/tests/test_pylint.py similarity index 100% rename from odoo/addons/test_pylint/tests/test_pylint.py rename to odoo/addons/test_lint/tests/test_pylint.py diff --git a/odoo/addons/test_pylint/__manifest__.py b/odoo/addons/test_pylint/__manifest__.py index 66f801454a5..42e9d621f71 100644 --- a/odoo/addons/test_pylint/__manifest__.py +++ b/odoo/addons/test_pylint/__manifest__.py @@ -3,7 +3,7 @@ 'name': 'test-eval', 'version': '0.1', 'category': 'Tests', - 'description': """A module to test Odoo with pylint.""", + 'description': """Deprecated empty module in favor of test-lint.""", 'maintainer': 'Odoo SA', 'depends': ['base'], 'installable': True, diff --git a/odoo/addons/test_pylint/tests/__init__.py b/odoo/addons/test_pylint/tests/__init__.py deleted file mode 100644 index 66bc70c522e..00000000000 --- a/odoo/addons/test_pylint/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import test_pylint