diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 62a760aa017..b41dbd4a384 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -56,7 +56,7 @@ class account_journal(models.Model): last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('date', '<=', last_month.strftime(DF))], order="date desc, id desc", limit=1) start_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0 - locale = self._context.get('lang', 'en_US') + locale = self._context.get('lang') or 'en_US' show_date = last_month #get date in locale format name = format_date(show_date, 'd LLLL Y', locale=locale) @@ -94,7 +94,7 @@ class account_journal(models.Model): data = [] today = datetime.strptime(fields.Date.context_today(self), DF) data.append({'label': _('Past'), 'value':0.0, 'type': 'past'}) - day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang', 'en_US'))) + day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US')) first_day_of_week = today + timedelta(days=-day_of_week+1) for i in range(-1,4): if i==0: @@ -105,9 +105,9 @@ class account_journal(models.Model): start_week = first_day_of_week + timedelta(days=i*7) end_week = start_week + timedelta(days=6) if start_week.month == end_week.month: - label = str(start_week.day) + '-' +str(end_week.day)+ ' ' + format_date(end_week, 'MMM', locale=self._context.get('lang', 'en_US')) + label = str(start_week.day) + '-' +str(end_week.day)+ ' ' + format_date(end_week, 'MMM', locale=self._context.get('lang') or 'en_US') else: - label = format_date(start_week, 'd MMM', locale=self._context.get('lang', 'en_US'))+'-'+format_date(end_week, 'd MMM', locale=self._context.get('lang', 'en_US')) + label = format_date(start_week, 'd MMM', locale=self._context.get('lang') or 'en_US')+'-'+format_date(end_week, 'd MMM', locale=self._context.get('lang') or 'en_US') data.append({'label':label,'value':0.0, 'type': 'past' if i<0 else 'future'}) # Build SQL query to find amount aggregated by week diff --git a/addons/account/report/account_overdue_report.py b/addons/account/report/account_overdue_report.py index 0fb7529e20b..fd032197233 100644 --- a/addons/account/report/account_overdue_report.py +++ b/addons/account/report/account_overdue_report.py @@ -25,7 +25,7 @@ class ReportOverdue(models.AbstractModel): "FROM account_move_line l " "JOIN account_account_type at ON (l.user_type_id = at.id) " "JOIN account_move m ON (l.move_id = m.id) " - "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),)))) + "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') AND NOT l.reconciled GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),)))) for row in self.env.cr.dictfetchall(): res[row.pop('partner_id')].append(row) return res diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js index 554354b31ae..54ff55fcdcc 100644 --- a/addons/account/static/src/js/account_reconciliation_widgets.js +++ b/addons/account/static/src/js/account_reconciliation_widgets.js @@ -453,6 +453,8 @@ var abstractReconciliationLine = Widget.extend({ start: function() { var self = this; + // fail silently if reconciliation widget is removed (eg. mode is changed) + var parent = this.getParent(); return self._super().then(function() { // no animation while loading @@ -468,8 +470,8 @@ var abstractReconciliationLine = Widget.extend({ return $.when(self.render()).then(function(){ self.is_consistent = true; // Make an entrance - self.animation_speed = self.getParent().animation_speed; - self.aestetic_animation_speed = self.getParent().aestetic_animation_speed; + self.animation_speed = parent.animation_speed; + self.aestetic_animation_speed = parent.aestetic_animation_speed; if (self.context.animate_entrance) { return self.$el.stop(true, true).fadeIn({ duration: self.aestetic_animation_speed, queue: false }).css('display', 'none').slideDown(self.aestetic_animation_speed); } @@ -2268,13 +2270,19 @@ var manualReconciliation = abstractReconciliation.extend({ animate_entrance = defaultIfUndef(animate_entrance, true); var self = this; var displayed_collections = self.getDisplayedCollections(); + var plural = { 'customer': 'customers', 'supplier': 'suppliers', 'account': 'others' }; self.updateProgressbar(); + // number of reconciliation items already displayed + var num_child_displayed = self.getChildren().length; + // Remove children that should not be displayed if (self.mode !== 'all') { _.each(self.getChildren(), function(child) { - if (child.data.reconciliation_type !== self.mode) { + var type = plural[child.data.reconciliation_type]; + if (type !== self.mode) { + num_child_displayed--; child.$el.slideUp(self.aestetic_animation_speed, function(){ child.destroy() }); child.data.displayed = false; } @@ -2287,7 +2295,7 @@ var manualReconciliation = abstractReconciliation.extend({ var items_to_display = _.foldl(displayed_collections, function(prev, current){ return prev.concat(current.items) }, []); // Filter out those which are already displayed items_to_display = _.filter(items_to_display, function(item) { return item.displayed === false; }) - .slice(0, self.num_reconciliations_fetched_in_batch - self.getChildren().length); + .slice(0, self.num_reconciliations_fetched_in_batch - num_child_displayed); _.each(items_to_display, function(item){ children_promises.push(self.displayReconciliation(item, animate_entrance)); }); diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index c543f5ea830..f267c3b984e 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1211,11 +1211,11 @@ class Meeting(models.Model): elif interval == 'month': # Localized month name and year - result = babel.dates.format_date(date=date, format='MMMM y', locale=self._context.get('lang', 'en_US')) + result = babel.dates.format_date(date=date, format='MMMM y', locale=self._context.get('lang') or 'en_US') elif interval == 'dayname': # Localized day name - result = babel.dates.format_date(date=date, format='EEEE', locale=self._context.get('lang', 'en_US')) + result = babel.dates.format_date(date=date, format='EEEE', locale=self._context.get('lang') or 'en_US') elif interval == 'time': # Localized time diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index 413f6230eb2..a402a79d84b 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -93,7 +93,7 @@ class Team(models.Model): @api.multi def write(self, vals): result = super(Team, self).write(vals) - if vals.get('use_leads') or vals.get('alias_defaults'): + if 'use_leads' in vals or 'alias_defaults' in vals: for team in self: team.alias_id.write(team.get_alias_values()) return result diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py index 18fd4c6a3e6..27f3f3e002d 100644 --- a/addons/crm/wizard/base_partner_merge.py +++ b/addons/crm/wizard/base_partner_merge.py @@ -489,13 +489,19 @@ class MergePartnerAutomatic(models.TransientModel): counter = 0 for min_id, aggr_ids in self._cr.fetchall(): - # exclude partner according to options - if model_mapping and self._partner_use_in(aggr_ids, model_mapping): + # To ensure that the used partners are accessible by the user + partners = self.env['res.partner'].search([('id', 'in', aggr_ids)]) + if len(partners) < 2: continue + + # exclude partner according to options + if model_mapping and self._partner_use_in(partners.ids, model_mapping): + continue + self.env['base.partner.merge.line'].create({ 'wizard_id': self.id, 'min_id': min_id, - 'aggr_ids': aggr_ids, + 'aggr_ids': partners.ids, }) counter += 1 diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 03aa47bbb27..d6892e927af 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -70,7 +70,7 @@ class EventEvent(models.Model): 'interval_unit': 'days', 'interval_type': 'before_event', 'template_id': self.env.ref('event.event_reminder') - })] if self.user_has_groups('event.group_email_scheduling') else [] + })] # Seats and computation seats_max = fields.Integer( diff --git a/addons/hr_maintenance/views/maintenance_views.xml b/addons/hr_maintenance/views/maintenance_views.xml index 2c56a9f2538..72970f9cd77 100644 --- a/addons/hr_maintenance/views/maintenance_views.xml +++ b/addons/hr_maintenance/views/maintenance_views.xml @@ -54,6 +54,20 @@ + + maintenance.equipment.view.search.inherit.hr + maintenance.equipment + + + + ['|', ('employee_id', '!=', False), ('department_id', '!=', False)] + + + [('employee_id', '=', False), ('department_id', '=', False)] + + + + maintenance.equipment.view.form.inherit.hr maintenance.equipment @@ -99,4 +113,4 @@ - \ No newline at end of file + diff --git a/addons/marketing_campaign/models/marketing_campaign.py b/addons/marketing_campaign/models/marketing_campaign.py index b6f8ba64b48..de60ed86e4f 100644 --- a/addons/marketing_campaign/models/marketing_campaign.py +++ b/addons/marketing_campaign/models/marketing_campaign.py @@ -426,7 +426,7 @@ class MarketingCampaignWorkitem(models.Model): campaign_id = fields.Many2one('marketing.campaign', related='activity_id.campaign_id', string='Campaign', readonly=True, store=True) object_id = fields.Many2one('ir.model', related='activity_id.campaign_id.object_id', string='Resource', index=1, readonly=True, store=True) res_id = fields.Integer('Resource ID', index=1, readonly=True) - res_name = fields.Char(compute='_compute_res_name', string='Resource Name', search='search_res_name') + res_name = fields.Char(compute='_compute_res_name', string='Resource Name', search='_search_res_name') date = fields.Datetime('Execution Date', readonly=True, default=False, help='If date is not set, this workitem has to be run manually') partner_id = fields.Many2one('res.partner', 'Partner', index=1, readonly=True) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index e18b55fc9d7..5e1a861ae8d 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -386,3 +386,12 @@ class StockMove(models.Model): 'split_from': self.id, # Needed in order to keep sale connection, but will be removed by unlink }) return self.env['stock.move'] + +class PushedFlow(models.Model): + _inherit = "stock.location.path" + + def _prepare_move_copy_values(self, move_to_copy, new_date): + new_move_vals = super(PushedFlow, self)._prepare_move_copy_values(move_to_copy, new_date) + new_move_vals['production_id'] = False + + return new_move_vals diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py index 2fefb1417ef..00359c4d038 100644 --- a/addons/payment_paypal/controllers/main.py +++ b/addons/payment_paypal/controllers/main.py @@ -23,7 +23,7 @@ class PaypalController(http.Controller): """ Extract the return URL from the data coming from paypal. """ return_url = post.pop('return_url', '') if not return_url: - custom = json.loads(post.pop('custom', False) or post.pop('cm', False) or '{}') + custom = json.loads(urllib.unquote_plus(post.pop('custom', False) or post.pop('cm', False) or '{}')) return_url = custom.get('return_url', '/') return return_url diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py index 9c9a8c09388..bdedd9d7f9d 100644 --- a/addons/product/models/product_attribute.py +++ b/addons/product/models/product_attribute.py @@ -76,7 +76,7 @@ class ProductAttributevalue(models.Model): @api.multi def _variant_name(self, variable_attributes): - return ", ".join([v.name for v in self if v.attribute_id in variable_attributes]) + return ", ".join([v.name for v in self.sorted(key=lambda r: r.name) if v.attribute_id in variable_attributes]) class ProductAttributePrice(models.Model): diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index 45cc87d17ae..c4dc828b540 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -44,7 +44,7 @@ class Pricelist(models.Model): if name and operator == '=' and not args: # search on the name of the pricelist and its currency, opposite of name_get(), # Used by the magic context filter in the product search view. - query_args = {'name': name, 'limit': limit, 'lang': self._context.get('lang', 'en_US')} + query_args = {'name': name, 'limit': limit, 'lang': self._context.get('lang') or 'en_US'} query = """SELECT p.id FROM (( SELECT pr.id, pr.name diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index d8d03f5ef1e..686752431ad 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -347,7 +347,7 @@ class SaleOrder(models.Model): vals = {} if order.name not in invoices[group_key].origin.split(', '): vals['origin'] = invoices[group_key].origin + ', ' + order.name - if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', '): + if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name: vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref invoices[group_key].write(vals) if line.qty_to_invoice > 0: @@ -818,7 +818,6 @@ class SaleOrderLine(models.Model): 'uom_id': self.product_uom.id, 'product_id': self.product_id.id or False, 'layout_category_id': self.layout_category_id and self.layout_category_id.id or False, - 'product_id': self.product_id.id or False, 'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)], 'account_analytic_id': self.order_id.project_id.id, 'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)], diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 2ca4bd3e46b..a393e3358cd 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -213,9 +213,15 @@ class PushedFlow(models.Model): # TDE FIXME: should probably be done in the move model IMO move._push_apply() else: - new_move = move.copy({ - 'origin': move.origin or move.picking_id.name or "/", - 'location_id': move.location_dest_id.id, + new_move_vals = self._prepare_move_copy_values(move, new_date) + new_move = move.copy(new_move_vals) + move.write({'move_dest_id': new_move.id}) + new_move.action_confirm() + + def _prepare_move_copy_values(self, move_to_copy, new_date): + new_move_vals = { + 'origin': move_to_copy.origin or move_to_copy.picking_id.name or "/", + 'location_id': move_to_copy.location_dest_id.id, 'location_dest_id': self.location_dest_id.id, 'date': new_date, 'date_expected': new_date, @@ -226,6 +232,6 @@ class PushedFlow(models.Model): 'push_rule_id': self.id, 'warehouse_id': self.warehouse_id.id, 'procurement_id': False, - }) - move.write({'move_dest_id': new_move.id}) - new_move.action_confirm() + } + + return new_move_vals diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 9c95a6682ac..1d144695f52 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -520,7 +520,7 @@ class Quant(models.Model): if any(quant not in self for quant in package.get_content()): all_in = False if all_in: - destinations = [product_to_location[product] for product in package.get_content().mapped('product_id')] + destinations = set([product_to_location[product] for product in package.get_content().mapped('product_id')]) if len(destinations) > 1: all_in = False if all_in: diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index ed3e34762e5..b597c2eea55 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -54,6 +54,8 @@ env.filters["json"] = json.dumps # 1 week cache for asset bundles as advised by Google Page Speed BUNDLE_MAXAGE = 60 * 60 * 24 * 7 +DBNAME_PATTERN = '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$' + #---------------------------------------------------------- # Odoo Web helpers #---------------------------------------------------------- @@ -630,6 +632,7 @@ class Database(http.Controller): d['list_db'] = odoo.tools.config['list_db'] d['langs'] = odoo.service.db.exp_list_lang() d['countries'] = odoo.service.db.exp_list_countries() + d['pattern'] = DBNAME_PATTERN # databases list d['databases'] = [] try: @@ -651,8 +654,8 @@ class Database(http.Controller): @http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False) def create(self, master_pwd, name, lang, password, **post): try: - if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', name): - raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.')) + if not re.match(DBNAME_PATTERN, name): + raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.')) # country code could be = "False" which is actually True in python country_code = post.get('country_code') or False dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code]) @@ -665,8 +668,8 @@ class Database(http.Controller): @http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False) def duplicate(self, master_pwd, name, new_name): try: - if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', new_name): - raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.')) + if not re.match(DBNAME_PATTERN, new_name): + raise Exception(_('Invalid database name. Only alphanumerical characters, underscore, hyphen and dot are allowed.')) dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name]) return http.local_redirect('/web/database/manager') except Exception, e: diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js index 1e854a6960c..378c5eb6023 100644 --- a/addons/web/static/lib/qweb/qweb2.js +++ b/addons/web/static/lib/qweb/qweb2.js @@ -129,12 +129,15 @@ var QWeb2 = { } return r.join(''); } else { + // avoid XMLSerializer with text node for IE + if (node.nodeType == 3) { + return node.data; + } if (typeof XMLSerializer !== 'undefined') { return (new XMLSerializer()).serializeToString(node); } else { switch(node.nodeType) { case 1: return node.outerHTML; - case 3: return node.data; case 4: return ''; case 8: return ''; } diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index 629d2a622ec..991578b2469 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -60,7 +60,7 @@
- +
@@ -207,7 +207,7 @@
- +
@@ -280,7 +280,7 @@
- +