diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py index 566f2cb40f9..85aa0a6dddf 100644 --- a/addons/base_address_extended/models/base_address_extended.py +++ b/addons/base_address_extended/models/base_address_extended.py @@ -91,7 +91,7 @@ class Partner(models.Model): for partner in self: if not partner.street: for field in street_fields: - partner[field] = '' + partner[field] = None continue street_format = (partner.country_id.street_format or diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index 6a2f6b729bc..d700753a934 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -305,6 +305,8 @@ class Import(models.TransientModel): if all(val.lower() in ('true', 'false', 't', 'f', '') for val in preview_values): return ['boolean'] # If all values can be cast to float, type is either float or monetary + # Or a date/datetime if it matches the pattern + results = [] try: thousand_separator = decimal_separator = False for val in preview_values: @@ -336,7 +338,7 @@ class Import(models.TransientModel): if thousand_separator and not options.get('float_decimal_separator'): options['float_thousand_separator'] = thousand_separator options['float_decimal_separator'] = decimal_separator - return ['float', 'monetary'] + results = ['float', 'monetary'] except ValueError: pass # Try to see if all values are a date or datetime @@ -372,13 +374,15 @@ class Import(models.TransientModel): current_date_pattern = check_patterns(date_patterns, preview_values) if current_date_pattern: options['date_format'] = current_date_pattern - return ['date'] + results += ['date'] current_datetime_pattern = check_patterns(datetime_patterns, preview_values) if current_datetime_pattern: options['datetime_format'] = current_datetime_pattern - return ['datetime'] + results += ['datetime'] + if results: + return results return ['text', 'char', 'datetime', 'selection', 'many2one', 'one2many', 'many2many', 'html'] @api.model @@ -500,7 +504,7 @@ class Import(models.TransientModel): 'headers_type': header_types or False, 'preview': preview, 'options': options, - 'advanced_mode': any([len(models.fix_import_export_id_paths(col)) > 1 for col in headers]), + 'advanced_mode': any([len(models.fix_import_export_id_paths(col)) > 1 for col in headers or []]), 'debug': self.user_has_groups('base.group_no_one'), } except Exception, error: diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index dc91bf4e5e6..b631bdd1429 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -201,9 +201,9 @@ class DeliveryCarrier(models.Model): return False if self.state_ids and contact.state_id not in self.state_ids: return False - if self.zip_from and (contact.zip or '') < self.zip_from: + if self.zip_from and (contact.zip or '').upper() < self.zip_from.upper(): return False - if self.zip_to and (contact.zip or '') > self.zip_to: + if self.zip_to and (contact.zip or '').upper() > self.zip_to.upper(): return False return self diff --git a/addons/im_livechat/controllers/main.py b/addons/im_livechat/controllers/main.py index f033fc72cc7..030581e12f2 100644 --- a/addons/im_livechat/controllers/main.py +++ b/addons/im_livechat/controllers/main.py @@ -75,7 +75,7 @@ class LivechatController(http.Controller): # if the user is identifiy (eg: portal user on the frontend), don't use the anonymous name. The user will be added to session. if request.session.uid: anonymous_name = request.env.user.name - return request.env["im_livechat.channel"].get_mail_channel(channel_id, anonymous_name) + return request.env["im_livechat.channel"].with_context(lang=False).get_mail_channel(channel_id, anonymous_name) @http.route('/im_livechat/feedback', type='json', auth='public') def feedback(self, uuid, rate, reason=None, **kwargs): diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 31cd5226346..09fdabc4b18 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -106,6 +106,8 @@ class MrpUnbuild(models.Model): self.env['stock.quant'].quants_reserve(quants, consume_move) if consume_move.has_tracking != 'none': + if not quants[0][0]: + raise UserError(_("You don't have in the stock the lot %s.") % (self.lot_id.name,)) self.env['stock.move.lots'].create({ 'move_id': consume_move.id, 'lot_id': self.lot_id.id, @@ -119,6 +121,8 @@ class MrpUnbuild(models.Model): for produce_move in produce_moves: if produce_move.has_tracking != 'none': original = original_quants.filtered(lambda quant: quant.product_id == produce_move.product_id) + if not original.lot_id: + raise UserError(_("You don't have in the stock the required lot/serial number for %s .") % (produce_move.product_id.name,)) self.env['stock.move.lots'].create({ 'move_id': produce_move.id, 'lot_id': original.lot_id.id, diff --git a/addons/mrp_repair/models/mrp_repair.py b/addons/mrp_repair/models/mrp_repair.py index e8d362ae67c..6e0f188c6b2 100644 --- a/addons/mrp_repair/models/mrp_repair.py +++ b/addons/mrp_repair/models/mrp_repair.py @@ -15,7 +15,7 @@ class Repair(models.Model): @api.model def _default_stock_location(self): - warehouse = self.env.ref('stock.warehouse0', raise_if_not_found=False) + warehouse = self.env['stock.warehouse'].search([], limit=1) if warehouse: return warehouse.lot_stock_id.id return False diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index 11d89207ada..f7f0ab75d32 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -143,15 +143,15 @@ class Product(models.Model): Quant = self.env['stock.quant'] domain_move_in_todo = [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_move_in domain_move_out_todo = [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_move_out - moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'])) - moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'])) - quants_res = dict((item['product_id'][0], item['qty']) for item in Quant.read_group(domain_quant, ['product_id', 'qty'], ['product_id'])) + moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) + moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id')) + quants_res = dict((item['product_id'][0], item['qty']) for item in Quant.read_group(domain_quant, ['product_id', 'qty'], ['product_id'], orderby='id')) if dates_in_the_past: # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones) domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_in_done domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_out_done - moves_in_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'])) - moves_out_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'])) + moves_in_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) + moves_out_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id')) res = dict() for product in self.with_context(prefetch_fields=False): @@ -288,7 +288,7 @@ class Product(models.Model): domain_quant.append(('owner_id', '=', owner_id)) if package_id: domain_quant.append(('package_id', '=', package_id)) - quants_groupby = self.env['stock.quant'].read_group(domain_quant, ['product_id', 'qty'], ['product_id']) + quants_groupby = self.env['stock.quant'].read_group(domain_quant, ['product_id', 'qty'], ['product_id'], orderby='id') for quant in quants_groupby: if OPERATORS[operator](quant['qty'], value): product_ids.add(quant['product_id'][0]) diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index 24c64aad7a4..dbbc46f3fcf 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -174,8 +174,8 @@ var CompletionFieldMixin = { init: function() { this.limit = 7; this.orderer = new utils.DropMisordered(); - this.can_create = this.node.attrs.can_create || true; - this.can_write = this.node.attrs.can_write || true; + this.can_create = this.node.attrs.can_create == "false" ? false : true; + this.can_write = this.node.attrs.can_write == "false" ? false : true; }, /** * Call this method to search using a string. diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 4f141570902..98cce6e484a 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -1091,7 +1091,8 @@ var FormView = View.extend(common.FieldManagerMixin, { || field.field.type === 'one2many' || field.field.type === 'many2many' || field.field.type === 'binary' - || field.password) { + || field.password + || !_.isEmpty(field.field.depends)) { return false; } diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js index 71647828cc6..d28279d57ed 100644 --- a/addons/web_editor/static/src/js/summernote.js +++ b/addons/web_editor/static/src/js/summernote.js @@ -504,7 +504,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) { }; for (var i=0; i

').append(br).insertAfter($(r.sc).closest('blockquote, pre')); node = br; + } else if (dom.isEditable(dom.node(r.sc))) { + // if we are directly in an editable, only SHIFT + ENTER should add a newline + node = null; } else if (last === r.sc) { if (dom.isBR(last)) { last = last.parentNode; diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js index aa1d3123db6..f924d879e01 100644 --- a/addons/web_editor/static/src/js/widgets.js +++ b/addons/web_editor/static/src/js/widgets.js @@ -914,7 +914,7 @@ var VideoDialog = Widget.extend({ video_id = this.$("#video_id").val(); } var $iframe = $( - '
'+ + '
'+ '
 
'+ '
 
'+ ''+ diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 234a43463dd..d0547dcf9db 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -418,7 +418,8 @@ animation.registry.media_video = animation.Class.extend({ var size = '
 
'; this.$target.html(editor+size); } - this.$target.html(this.$target.html()+''); + // rebuilding the iframe, from https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/ + this.$target.html(this.$target.html()+''); return this._super.apply(this, arguments); }, }); diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index f50b2c6ac1d..0f9561cd2b1 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -13,6 +13,9 @@ if(!$('.website_forum').length) { return $.Deferred().reject("DOM doesn't contain '.website_forum'"); } + // pull-left class messes up the post layout OPW 769721 + $('span[data-oe-model="forum.post"][data-oe-field="content"]').find('img.pull-left').removeClass('pull-left'); + $("[data-toggle='popover']").popover(); $('.karma_required').on('click', function (ev) { var karma = $(ev.currentTarget).data('karma'); @@ -413,6 +416,9 @@ if(!$('.website_forum').length) { toolbar: toolbar, styleWithSpan: false }); + + // pull-left class messes up the post layout OPW 769721 + $form.find('.note-editable').find('img.pull-left').removeClass('pull-left'); $form.on('click', 'button, .a-submit', function () { $textarea.html($form.find('.note-editable').code()); }); diff --git a/odoo/http.py b/odoo/http.py index 89efcbdeb41..56b1f8e6930 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1504,8 +1504,14 @@ def db_filter(dbs, httprequest=None): d, _, r = h.partition('.') if d == "www" and r: d = r.partition('.')[0] - r = odoo.tools.config['dbfilter'].replace('%h', h).replace('%d', d) - dbs = [i for i in dbs if re.match(r, i)] + if odoo.tools.config['dbfilter']: + r = odoo.tools.config['dbfilter'].replace('%h', h).replace('%d', d) + dbs = [i for i in dbs if re.match(r, i)] + elif odoo.tools.config['db_name']: + # In case --db-filter is not provided and --database is passed, Odoo will + # use the value of --database as a comma seperated list of exposed databases. + exposed_dbs = set(db.strip() for db in odoo.tools.config['db_name'].split(',')) + dbs = sorted(exposed_dbs.intersection(dbs)) return dbs def db_monodb(httprequest=None): diff --git a/odoo/service/db.py b/odoo/service/db.py index a9968963fb8..5b25ef72d8f 100644 --- a/odoo/service/db.py +++ b/odoo/service/db.py @@ -321,6 +321,14 @@ def exp_db_exist(db_name): def list_dbs(force=False): if not odoo.tools.config['list_db'] and not force: raise odoo.exceptions.AccessDenied() + + if not odoo.tools.config['dbfilter'] and odoo.tools.config['db_name']: + # In case --db-filter is not provided and --database is passed, Odoo will not + # fetch the list of databases available on the postgres server and instead will + # use the value of --database as comma seperated list of exposed databases. + res = sorted(db.strip() for db in odoo.tools.config['db_name'].split(',')) + return res + chosen_template = odoo.tools.config['db_template'] templates_list = tuple(set(['postgres', chosen_template])) db = odoo.sql_db.db_connect('postgres') diff --git a/odoo/tools/amount_to_text.py b/odoo/tools/amount_to_text.py index 8e89b48fbc4..b4076796ffd 100644 --- a/odoo/tools/amount_to_text.py +++ b/odoo/tools/amount_to_text.py @@ -22,9 +22,13 @@ def _convert_nn_fr(val): if val < 20: return to_19_fr[val] for (dcap, dval) in ((k, 20 + (10 * v)) for (v, k) in enumerate(tens_fr)): - if dval + 10 > val: - if val % 10: - return dcap + '-' + to_19_fr[val % 10] + base = 10 + if dval in [60, 80]: + base = 20 + if dval + base > val: + if val % base: + bond = (val % base in [1, 11] and dval != 80) and '-et-' or '-' + return dcap + bond + to_19_fr[val % base] return dcap def _convert_nnn_fr(val): @@ -37,7 +41,7 @@ def _convert_nnn_fr(val): word = '' (mod, rem) = (val % 100, val // 100) if rem > 0: - word = to_19_fr[rem] + ' Cent' + word = (rem > 1 and to_19_fr[rem] + ' cents') or 'Cent' if mod > 0: word += ' ' if mod > 0: @@ -92,7 +96,7 @@ def _convert_nn_nl(val): for (dcap, dval) in ((k, 20 + (10 * v)) for (v, k) in enumerate(tens_nl)): if dval + 10 > val: if val % 10: - return dcap + '-' + to_19_nl[val % 10] + return to_19_nl[val % 10] + '-en-' + dcap return dcap def _convert_nnn_nl(val): @@ -105,7 +109,7 @@ def _convert_nnn_nl(val): word = '' (mod, rem) = (val % 100, val // 100) if rem > 0: - word = to_19_nl[rem] + ' Honderd' + word = (rem > 1 and to_19_nl[rem] + 'honderd') or 'Honderd' if mod > 0: word += ' ' if mod > 0: