diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-11-16 15:39:02 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-11-16 15:39:02 +0100 |
| commit | 4b8bd36219c1c11bb1ca36a9862baf632a995bad (patch) | |
| tree | 6fdb8470c536aa4b55ded2fbfdd9ade003294c62 /addons | |
| parent | ec7aa3c1f16d76008dbd83e723591a04da6a868b (diff) | |
| parent | bab2a191d292737d1c2bc6b1650a1581614b0cc1 (diff) | |
[MERGE] forward port branch saas-11.3 up to bab2a191d29
Diffstat (limited to 'addons')
26 files changed, 329 insertions, 60 deletions
diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 948cebf5c23..b89ec6bcb13 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -46,7 +46,9 @@ class ReportAgedPartnerBalance(models.AbstractModel): total = [] partner_clause = '' cr = self.env.cr - company_ids = self.env.context.get('company_ids', (self.env.user.company_id.id,)) + user_company = self.env.user.company_id + user_currency = user_company.currency_id + company_ids = self._context.get('company_ids') or [user_company.id] move_state = ['draft', 'posted'] if target_move == 'posted': move_state = ['posted'] @@ -128,15 +130,15 @@ class ReportAgedPartnerBalance(models.AbstractModel): partner_id = line.partner_id.id or False if partner_id not in partners_amount: partners_amount[partner_id] = 0.0 - line_amount = line.balance - if line.balance == 0: + line_amount = line.company_id.currency_id._convert(line.balance, user_currency, line.company_id, date_from) + if user_currency.is_zero(line_amount): continue for partial_line in line.matched_debit_ids: if partial_line.max_date <= date_from: - line_amount += partial_line.amount + line_amount += partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, partial_line.currency_id, date_from) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: - line_amount -= partial_line.amount + line_amount -= partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, partial_line.currency_id, date_from) if not self.env.user.company_id.currency_id.is_zero(line_amount): partners_amount[partner_id] += line_amount @@ -166,15 +168,15 @@ class ReportAgedPartnerBalance(models.AbstractModel): partner_id = line.partner_id.id or False if partner_id not in undue_amounts: undue_amounts[partner_id] = 0.0 - line_amount = line.balance - if line.balance == 0: + line_amount = line.company_id.currency_id._convert(line.balance, user_currency, line.company_id, date_from) + if user_currency.is_zero(line_amount): continue for partial_line in line.matched_debit_ids: if partial_line.max_date <= date_from: - line_amount += partial_line.amount + line_amount += partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, partial_line.currency_id, date_from) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: - line_amount -= partial_line.amount + line_amount -= partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, partial_line.currency_id, date_from) if not self.env.user.company_id.currency_id.is_zero(line_amount): undue_amounts[partner_id] += line_amount lines[partner_id].append({ diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index 52ac52ad108..ef09176438b 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -39,8 +39,6 @@ access_account_journal_invoice,account.journal invoice,model_account_journal,acc access_account_invoice_group_invoice,account.invoice group invoice,model_account_invoice,account.group_account_invoice,1,1,1,1 access_res_currency_account_manager,res.currency account manager,base.model_res_currency,group_account_manager,1,1,1,1 access_res_currency_rate_account_manager,res.currency.rate account manager,base.model_res_currency_rate,group_account_manager,1,1,1,1 -access_account_invoice_user,account.invoice user,model_account_invoice,base.group_user,1,0,0,0 -access_account_invoice_line_user,account.invoice.line user,model_account_invoice_line,base.group_user,1,0,0,0 access_account_invoice_portal,account.invoice.portal,account.model_account_invoice,base.group_portal,1,0,0,0 access_account_invoice_line_portal,account.invoice.line.portal,account.model_account_invoice_line,base.group_portal,1,0,0,0 access_account_payment_term_partner_manager,account.payment.term partner manager,model_account_payment_term,base.group_user,1,0,0,0 diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index b9504bd6ee6..3c9174546cf 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -1262,8 +1262,9 @@ var ManualModel = StatementModel.extend({ }); var domainReconcile = []; - if (context && context.company_ids) { - domainReconcile.push(['company_id', 'in', context.company_ids]); + var company_ids = context && context.company_ids || [session.company_id] + if (company_ids) { + domainReconcile.push(['company_id', 'in', company_ids]); } var def_reconcileModel = this._rpc({ model: 'account.reconcile.model', diff --git a/addons/account/wizard/account_report_common.py b/addons/account/wizard/account_report_common.py index 6b47f2a7e8e..f5e5814c814 100644 --- a/addons/account/wizard/account_report_common.py +++ b/addons/account/wizard/account_report_common.py @@ -45,4 +45,4 @@ class AccountCommonReport(models.TransientModel): data['form'] = self.read(['date_from', 'date_to', 'journal_ids', 'target_move', 'company_id'])[0] used_context = self._build_contexts(data) data['form']['used_context'] = dict(used_context, lang=self.env.context.get('lang') or 'en_US') - return self._print_report(data) + return self.with_context(discard_logo_check=True)._print_report(data) diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index d6161e17b91..87a47ffa455 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -57,17 +57,17 @@ class ResPartner(models.Model): for partner in self: # when required, make sure the partner has a valid signup token if self.env.context.get('signup_valid') and not partner.user_ids: - partner.signup_prepare() + partner.sudo().signup_prepare() route = 'login' # the parameters to encode for the query query = dict(db=self.env.cr.dbname) - signup_type = self.env.context.get('signup_force_type_in_url', partner.signup_type or '') + signup_type = self.env.context.get('signup_force_type_in_url', partner.sudo().signup_type or '') if signup_type: route = 'reset_password' if signup_type == 'reset' else signup_type - if partner.signup_token and signup_type: - query['token'] = partner.signup_token + if partner.sudo().signup_token and signup_type: + query['token'] = partner.sudo().signup_token elif partner.user_ids: query['login'] = partner.user_ids[0].login else: diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index 07c42694ef1..7591299f3e2 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -25,18 +25,58 @@ class Partner(models.Model): # render the partner address accordingly to address_view_id doc = etree.fromstring(arch) if doc.xpath("//field[@name='city_id']"): - return arch - label = _('City') - for city_node in doc.xpath("//field[@name='city']"): - replacement_xml = """ + return arch + + replacement_xml = """ <div> <field name="country_enforce_cities" invisible="1"/> - <field name='city' placeholder="%s" attrs="{'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}"/> - <field name='city_id' placeholder="%s" string="%s" attrs="{'invisible': [('country_enforce_cities', '=', False)], 'readonly': [('type', '=', 'contact'), ('parent_id', '!=', False)]}" context="{'default_country_id': country_id}" domain="[('country_id', '=', country_id)]"/> + <field name='city' placeholder="%(placeholder)s" + attrs="{ + 'invisible': [('country_enforce_cities', '=', True), ('city_id', '!=', False)], + 'readonly': [('type', '=', 'contact')%(parent_condition)s] + }" + /> + <field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s" + context="{'default_country_id': country_id}" + domain="[('country_id', '=', country_id)]" + attrs="{ + 'invisible': [('country_enforce_cities', '=', False)], + 'readonly': [('type', '=', 'contact')%(parent_condition)s] + }" + /> </div> - """ % (label, label, label) - city_id_node = etree.fromstring(replacement_xml) - city_node.getparent().replace(city_node, city_id_node) + """ + + replacement_data = { + 'placeholder': _('City'), + } + + def _arch_location(node): + in_subview = False + view_type = False + parent = node.getparent() + while parent is not None and (not view_type or not in_subview): + if parent.tag == 'field': + in_subview = True + elif parent.tag in ['list', 'tree', 'kanban', 'form']: + view_type = parent.tag + parent = parent.getparent() + return { + 'view_type': view_type, + 'in_subview': in_subview, + } + + for city_node in doc.xpath("//field[@name='city']"): + location = _arch_location(city_node) + replacement_data['parent_condition'] = '' + if location['view_type'] == 'form' or not location['in_subview']: + replacement_data['parent_condition'] = ", ('parent_id', '!=', False)" + + replacement_formatted = replacement_xml % replacement_data + for replace_node in etree.fromstring(replacement_formatted).getchildren(): + city_node.addprevious(replace_node) + parent = city_node.getparent() + parent.remove(city_node) arch = etree.tostring(doc, encoding='unicode') return arch diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 8124749f75e..cc91739fb6a 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1789,7 +1789,15 @@ class Meeting(models.Model): if values.get('description'): activity_values['note'] = values['description'] if values.get('start'): - activity_values['date_deadline'] = fields.Datetime.from_string(values['start']).date() + # self.start is a datetime UTC *only when the event is not allday* + # activty.date_deadline is a date (No TZ, but should represent the day in which the user's TZ is) + # See 72254129dbaeae58d0a2055cba4e4a82cde495b7 for the same issue, but elsewhere + deadline = fields.Datetime.from_string(values['start']) + user_tz = self.env.context.get('tz') + if user_tz and not self.allday: + deadline = pytz.UTC.localize(deadline) + deadline = deadline.astimezone(pytz.timezone(user_tz)) + activity_values['date_deadline'] = deadline.date() if values.get('user_id'): activity_values['user_id'] = values['user_id'] if activity_values.keys(): diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py index fe456339eaa..085c3671491 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -314,3 +314,73 @@ class TestCalendar(TransactionCase): else: self.assertEqual(d.hour, 15) self.assertEqual(d.minute, 30) + + def test_event_activity_timezone(self): + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'Meeting with partner', + 'activity_ids': [(6, False, activity_id.ids)], + 'start': '2018-11-12 21:00:00', + 'stop': '2018-11-13 00:00:00', + }) + + # Check output in UTC + self.assertEqual(activity_id.date_deadline, '2018-11-12') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Australia/Brisbane'}).write({ + 'start': '2018-11-12 21:00:00', + }) + + self.assertEqual(activity_id.date_deadline, '2018-11-13') + + def test_event_allday_activity_timezone(self): + # Covers use case of commit eef4c3b48bcb4feac028bf640b545006dd0c9b91 + # Also, read the comment in the code at calendar.event._inverse_dates + activty_type = self.env['mail.activity.type'].create({ + 'name': 'Meeting', + 'category': 'meeting' + }) + + activity_id = self.env['mail.activity'].create({ + 'summary': 'Meeting with partner', + 'activity_type_id': activty_type.id, + 'res_model_id': self.env['ir.model'].search([('model', '=', 'res.partner')], limit=1).id, + 'res_id': self.env['res.partner'].search([('name', 'ilike', 'Agrolait')], limit=1).id, + }) + + calendar_event = self.env['calendar.event'].create({ + 'name': 'All Day', + 'start': "2018-10-16 00:00:00", + 'start_date': "2018-10-16", + 'start_datetime': False, + 'stop': "2018-10-18 00:00:00", + 'stop_date': "2018-10-18", + 'stop_datetime': False, + 'allday': True, + 'activity_ids': [(6, False, activity_id.ids)], + }) + + # Check output in UTC + self.assertEqual(activity_id.date_deadline, '2018-10-16') + + # Check output in the user's tz + # write on the event to trigger sync of activities + calendar_event.with_context({'tz': 'Pacific/Honolulu'}).write({ + 'start': '2018-10-16 00:00:00', + 'start_date': '2018-10-16', + }) + + self.assertEqual(activity_id.date_deadline, '2018-10-16') diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 77f34f856d0..9f07ad5c6e5 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -217,7 +217,7 @@ class DeliveryCarrier(models.Model): 'error_message': _('Error: this delivery method is not available for this address.'), 'warning_message': False} price = self.fixed_price - if self.company_id.currency_id.id != order.currency_id.id: + if self.company_id and self.company_id.currency_id.id != order.currency_id.id: price = self.env['res.currency']._compute(self.company_id.currency_id, order.currency_id, price) return {'success': True, 'price': price, diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index b991609ca89..59ec2f0b801 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -437,6 +437,8 @@ td { display: flex; -webkit-flex: 1; flex: 1; + max-width: -moz-available; + max-width: -webkit-fill-available; } .pos .orders { display: -webkit-flex; diff --git a/addons/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js index e61f8d1097e..c48ecae1c5d 100644 --- a/addons/pos_restaurant/static/src/js/multiprint.js +++ b/addons/pos_restaurant/static/src/js/multiprint.js @@ -139,8 +139,10 @@ models.Orderline = models.Orderline.extend({ } }, set_dirty: function(dirty) { - this.mp_dirty = dirty; - this.trigger('change',this); + if (this.mp_dirty !== dirty) { + this.mp_dirty = dirty; + this.trigger('change', this); + } }, get_line_diff_hash: function(){ if (this.get_note()) { diff --git a/addons/product/report/product_pricelist.py b/addons/product/report/product_pricelist.py index 2a94a4b2a29..e6ccde44d8e 100644 --- a/addons/product/report/product_pricelist.py +++ b/addons/product/report/product_pricelist.py @@ -17,7 +17,7 @@ class report_product_pricelist(models.AbstractModel): quantities = self._get_quantity(data) return { 'doc_ids': data.get('ids', data.get('active_ids')), - 'doc_model': 'hr.contribution.register', + 'doc_model': 'product.pricelist', 'docs': products, 'data': dict( data, diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index 6778c47ebc3..7e899b2e928 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -492,3 +492,15 @@ class StockMove(models.Model): return [(requisition_line.requisition_id, requisition_line.requisition_id.user_id, visited) for requisition_line in self.requisition_line_ids if requisition_line.state not in ('done', 'cancel')] else: return super(StockMove, self)._get_upstream_documents_and_responsibles(visited) + + +class Orderpoint(models.Model): + _inherit = "stock.warehouse.orderpoint" + + def _quantity_in_progress(self): + res = super(Orderpoint, self)._quantity_in_progress() + for op in self: + for pr in self.env['purchase.requisition'].search([('state','=','draft'),('origin','=',op.name)]): + for prline in pr.line_ids.filtered(lambda l: l.product_id.id == op.product_id.id): + res[op.id] += prline.product_uom_id._compute_quantity(prline.product_qty, op.product_uom, round=False) + return res diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 6afc0b6b5d3..1169b137e1c 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -549,10 +549,11 @@ class SaleOrder(models.Model): raise UserError(_('There is no invoiceable line. If a product has a Delivered quantities invoicing policy, please make sure that a quantity has been delivered.')) for invoice in invoices.values(): + invoice.compute_taxes() if not invoice.invoice_line_ids: raise UserError(_('There is no invoiceable line. If a product has a Delivered quantities invoicing policy, please make sure that a quantity has been delivered.')) # If invoice is negative, do a refund invoice instead - if invoice.amount_untaxed < 0: + if invoice.amount_total < 0: invoice.type = 'out_refund' for line in invoice.invoice_line_ids: line.quantity = -line.quantity diff --git a/addons/sale/report/sale_report_views.xml b/addons/sale/report/sale_report_views.xml index ef419e6eb0a..0b098040bd5 100644 --- a/addons/sale/report/sale_report_views.xml +++ b/addons/sale/report/sale_report_views.xml @@ -18,7 +18,7 @@ <field name="model">sale.report</field> <field name="arch" type="xml"> <graph string="Sales Analysis" type="line"> - <field name="date" type="row" interval="day"/> + <field name="confirmation_date" type="row" interval="day"/> <field name="price_subtotal" type="measure"/> </graph> </field> diff --git a/addons/sale_margin/models/sale_order.py b/addons/sale_margin/models/sale_order.py index cf5902d6b2b..866890ff822 100644 --- a/addons/sale_margin/models/sale_order.py +++ b/addons/sale_margin/models/sale_order.py @@ -60,14 +60,6 @@ class SaleOrderLine(models.Model): for line in self: currency = line.order_id.pricelist_id.currency_id price = line.purchase_price - if not price: - from_cur = line.env.user.company_id.currency_id - price = from_cur._convert( - line.product_id.standard_price, - currency, - line.order_id.company_id or self.env.user.company_id, - line.order_id.date_order or fields.Date.today(), round=False) - line.margin = currency.round(line.price_subtotal - (price * line.product_uom_qty)) diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 9d84752dd24..430c3cc52d9 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -327,7 +327,10 @@ class SaleOrderLine(models.Model): self.ensure_one() qty = 0.0 for move in self.move_ids.filtered(lambda r: r.state != 'cancel'): - qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + if move.picking_code == 'outgoing': + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') + elif move.picking_code == 'incoming': + qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') return qty @api.multi @@ -433,7 +436,8 @@ class SaleOrderLine(models.Model): return is_available def _update_line_quantity(self, values): - if self.mapped('qty_delivered') and values['product_uom_qty'] < max(self.mapped('qty_delivered')): + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') + if self.mapped('qty_delivered') and float_compare(values['product_uom_qty'], max(self.mapped('qty_delivered')), precision_digits=precision) == -1: raise UserError(_('You cannot decrease the ordered quantity below the delivered quantity.\n' 'Create a return first.')) super(SaleOrderLine, self)._update_line_quantity(values) diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 43d604eac0b..76adef0a154 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -264,7 +264,9 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { resIDs: record.res_ids, }, on_success: def.resolve.bind(def), - on_fail: def.reject.bind(def), + on_fail: function () { + self.update({}, {reload: false}).always(def.reject.bind(def)); + }, on_closed: reload, }); return this.alive(def); diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index d6ea10d1243..ee11afbc3bd 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -186,9 +186,12 @@ var FormViewDialog = ViewDialog.extend({ } fields_view_def.then(function (viewInfo) { + var refinedContext = _.pick(self.context, function (value, key) { + return key.indexOf('_view_ref') === -1; + }); var formview = new FormView(viewInfo, { modelName: self.res_model, - context: self.context, + context: refinedContext, ids: self.res_id ? [self.res_id] : [], currentId: self.res_id || undefined, index: 0, diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 0af96038c96..ada27d09afc 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -6986,6 +6986,69 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('keep editing after call_button fail', function (assert) { + assert.expect(4); + + var values; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'<form>' + + '<button name="post" class="p" string="Raise Error" type="object"/>' + + '<field name="p">' + + '<tree editable="top">' + + '<field name="display_name"/>' + + '<field name="product_id"/>' + + '</tree>' + + '</field>' + + '</form>', + res_id: 1, + intercepts: { + execute_action: function (ev) { + assert.ok(true, 'the action is correctly executed'); + ev.data.on_fail(); + }, + }, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1].p[0][2], values); + } + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // add a row and partially fill it + form.$('.o_field_x2many_list_row_add a').click(); + form.$('input:nth(0)').val('abc').trigger('input'); + + // click button which will trigger_up 'execute_action' (this will save) + values = { + display_name: 'abc', + product_id: false, + }; + form.$('button.p').click(); + + // edit the new row again and set a many2one value + form.$('.o_form_view .o_field_one2many .o_data_row .o_data_cell').click(); + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').click(); + $dropdown.find('li:first()').click(); + + assert.strictEqual(form.$('.o_field_many2one input').val(), 'xphone', + "value of the m2o should have been correctly updated"); + + values = { + product_id: 37, + }; + form.$buttons.find('.o_form_button_save').click(); + + form.destroy(); + }); + QUnit.test('asynchronous rendering of a widget tag', function (assert) { assert.expect(1); @@ -7283,7 +7346,6 @@ QUnit.module('Views', { '</sheet>' + '</form>', res_id: 2, - //debug:1, }); assert.strictEqual(form.$buttons.find('.o_form_button_edit')[0],document.activeElement, "in read mode, when there are no primary buttons on the form, the default button with the focus should be edit"); diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index b45c27e4d60..372edbc0f24 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -364,6 +364,61 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('Form dialog and subview with _view_ref contexts', function (assert) { + assert.expect(2); + + this.data.instrument.records = [{id: 1, name: 'Tromblon', badassery: [1]}]; + this.data.partner.records[0].instrument = 1; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="name"/>' + + '<field name="instrument" context="{\'tree_view_ref\': \'some_tree_view\'}"/>' + + '</form>', + res_id: 1, + archs: { + 'instrument,false,form': '<form>'+ + '<field name="name"/>'+ + '<field name="badassery" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' + + '</form>', + + 'badassery,false,list': '<tree>'+ + '<field name="level"/>'+ + '</tree>', + }, + viewOptions: { + mode: 'edit', + }, + + mockRPC: function(route, args) { + if (args.method === 'get_formview_id') { + return $.when(false); + } + return this._super(route, args); + }, + + interceptsPropagate: { + load_views: function (ev) { + var evaluatedContext = ev.data.context; + if (ev.data.modelName === 'instrument') { + assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_tree_view'}, + 'The correct _view_ref should have been sent to the server, first time'); + } + if (ev.data.modelName === 'badassery') { + assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_other_tree_view'}, + 'The correct _view_ref should have been sent to the server for the subview'); + } + }, + }, + }); + + form.$('.o_field_widget[name="instrument"] button.o_external_button').click(); + form.destroy(); + }); + QUnit.test('SelectCreateDialog: save current search', function (assert) { assert.expect(4); diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js index a2f0f6a308f..36f5becce06 100644 --- a/addons/web_editor/static/src/js/editor/summernote.js +++ b/addons/web_editor/static/src/js/editor/summernote.js @@ -1936,6 +1936,15 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa if (!r) { return; } + // select content since container (that firefox selects) may be removed + if (r.so === 0) { + r.sc = dom.firstChild(r.sc); + } + if (dom.nodeLength(r.ec) >= r.eo) { + r.ec = dom.lastChild(r.ec); + r.eo = dom.nodeLength(r.ec); + } + r = range.create(r.sc, r.so, r.ec, r.eo); r.reRange().select(); if (sTagName === "blockquote" || sTagName === "pre") { @@ -1949,6 +1958,10 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa for (var i=0; i<nodes.length; i++) { if (dom.isBR(nodes[i]) || (dom.isText(nodes[i]) && dom.isVisibleText(nodes[i])) || dom.isB(nodes[i]) || dom.isU(nodes[i]) || dom.isS(nodes[i]) || dom.isI(nodes[i]) || dom.isFont(nodes[i])) { var ancestor = dom.ancestor(nodes[i], isFormatNode); + if ($(ancestor).parent().is('blockquote')) { + // firefox may wrap formatting block in blockquote + $(ancestor).unwrap(); + } if (!ancestor) { dom.wrap(nodes[i], sTagName); } else if (ancestor.tagName.toLowerCase() !== sTagName) { @@ -2272,7 +2285,8 @@ options.onCreateLink = function (sLinkUrl) { // pass } else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) { sLinkUrl = 'mailto:' + sLinkUrl; - } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) { + } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl[0] !== '/' + && sLinkUrl[0] !== '#' && sLinkUrl.slice(0, 2) !== '${') { sLinkUrl = 'http://' + sLinkUrl; } return sLinkUrl; diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml index 85cf336130d..631bd069e8b 100644 --- a/addons/web_editor/static/src/xml/editor.xml +++ b/addons/web_editor/static/src/xml/editor.xml @@ -81,11 +81,12 @@ <!-- Image choosing part of the Media Dialog --> <t t-name="web_editor.dialog.image"> + <t t-set="iframeName" t-value="_.uniqueId('fileframe-')"/> <form class="form-inline" method="POST" action="/web_editor/attachment/add" enctype="multipart/form-data" - target="fileframe"> + t-att-target="iframeName"> <input type="hidden" name="csrf_token" t-att-value="csrf_token"/> <input type="hidden" name="filters" t-att-value="widget.firstFilters.join('_')"/> <input t-if="widget.options.res_id" type="hidden" name="res_id" t-att-value="widget.options.res_id"/> @@ -119,7 +120,7 @@ <div class="existing-attachments"> <t t-call="web_editor.dialog.image.existing"/> </div> - <iframe class="d-none" name="fileframe"/> + <iframe class="d-none" t-att-name="iframeName"/> </form> </t> <t t-name="web_editor.dialog.image.existing"> diff --git a/addons/website/models/ir_actions.py b/addons/website/models/ir_actions.py index f26d6263004..4add5587d7f 100644 --- a/addons/website/models/ir_actions.py +++ b/addons/website/models/ir_actions.py @@ -38,7 +38,7 @@ class ServerAction(models.Model): def _get_website_url(self): for action in self: if action.state == 'code' and action.website_published: - action.website_url = self._compute_website_url(action.website_path, action.xml_id) + action.website_url = action._compute_website_url(action.website_path, action.xml_id) @api.model def _get_eval_context(self, action): diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml index 20608d9e0d7..fc70e2ee4b0 100644 --- a/addons/website_event/views/event_templates.xml +++ b/addons/website_event/views/event_templates.xml @@ -74,14 +74,14 @@ </li> </ul> </div> - <div class="row"> - <div class="col-md-6 offset-3"> - <t t-call="website.pager"/> - </div> - </div> <div class="col-lg-3 oe_structure" id="right_column"> </div> </div> + <div class="row"> + <div class="col-md-6 offset-3"> + <t t-call="website.pager"/> + </div> + </div> </div> <div class="oe_structure" id="oe_structure_website_event_index_2"/> </div> diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py index 41a120008b4..f8a75af3130 100644 --- a/addons/website_form/controllers/main.py +++ b/addons/website_form/controllers/main.py @@ -221,10 +221,10 @@ class WebsiteForm(http.Controller): else: orphan_attachment_ids.append(attachment_id.id) - # If some attachments didn't match a field on the model, - # we create a mail.message to link them to the record - if orphan_attachment_ids: - if model_name != 'mail.mail': + if model_name != 'mail.mail': + # If some attachments didn't match a field on the model, + # we create a mail.message to link them to the record + if orphan_attachment_ids: values = { 'body': _('<p>Attached files : </p>'), 'model': model_name, |
