diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-04-08 19:52:29 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-04-08 19:52:29 +0200 |
| commit | fef49061ead4a437bac922d9d32270f76addd014 (patch) | |
| tree | 5d38892041e4a47c83dbc54366bd5d6bd41b6604 /addons | |
| parent | ebba721130b9887f67d4fefc9ec4855d12634cb6 (diff) | |
| parent | 4c61621efbb079a4298db99d5c0b30b90e072bb9 (diff) | |
[MERGE] forward port branch saas-11.3 up to 4c61621efbb
Diffstat (limited to 'addons')
19 files changed, 196 insertions, 42 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 3c4dc8c2a32..d2039926804 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -98,6 +98,11 @@ class AccountInvoice(models.Model): journal = self._default_journal() return journal.currency_id or journal.company_id.currency_id or self.env.user.company_id.currency_id + def _get_aml_for_amount_residual(self): + """ Get the aml to consider to compute the amount residual of invoices """ + self.ensure_one() + return self.sudo().move_id.line_ids.filtered(lambda l: l.account_id == self.account_id) + @api.one @api.depends( 'state', 'currency_id', 'invoice_line_ids.price_subtotal', @@ -107,14 +112,13 @@ class AccountInvoice(models.Model): residual = 0.0 residual_company_signed = 0.0 sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 - for line in self.sudo().move_id.line_ids: - if line.account_id == self.account_id: - residual_company_signed += line.amount_residual - if line.currency_id == self.currency_id: - residual += line.amount_residual_currency if line.currency_id else line.amount_residual - else: - from_currency = line.currency_id or line.company_id.currency_id - residual += from_currency._convert(line.amount_residual, self.currency_id, line.company_id, line.date or fields.Date.today()) + for line in self._get_aml_for_amount_residual(): + residual_company_signed += line.amount_residual + if line.currency_id == self.currency_id: + residual += line.amount_residual_currency if line.currency_id else line.amount_residual + else: + from_currency = line.currency_id or line.company_id.currency_id + residual += from_currency._convert(line.amount_residual, self.currency_id, line.company_id, line.date or fields.Date.today()) self.residual_company_signed = abs(residual_company_signed) * sign self.residual_signed = abs(residual) * sign self.residual = abs(residual) @@ -1026,11 +1030,17 @@ class AccountInvoice(models.Model): return tax_grouped @api.multi + def _get_aml_for_register_payment(self): + """ Get the aml to consider to reconcile in register payment """ + self.ensure_one() + return self.move_id.line_ids.filtered(lambda r: not r.reconciled and r.account_id.internal_type in ('payable', 'receivable')) + + @api.multi def register_payment(self, payment_line, writeoff_acc_id=False, writeoff_journal_id=False): """ Reconcile payable/receivable lines from the invoice with payment_line """ line_to_reconcile = self.env['account.move.line'] for inv in self: - line_to_reconcile += inv.move_id.line_ids.filtered(lambda r: not r.reconciled and r.account_id.internal_type in ('payable', 'receivable')) + line_to_reconcile += inv._get_aml_for_register_payment() return (line_to_reconcile + payment_line).reconcile(writeoff_acc_id, writeoff_journal_id) @api.multi diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index ff9fb522b2b..c24518e15b8 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1253,6 +1253,7 @@ <field name="invoice_id" attrs="{'invisible': [('invoice_id','=',False)]}"/> </group> <group string="Dates"> + <field name="date"/> <field name="date_maturity"/> </group> @@ -1260,7 +1261,7 @@ <field name="tax_line_id" readonly="1" attrs="{'invisible': [('tax_line_id','=',False)]}"/> <field name="tax_ids" widget="many2many_tags" readonly="1" attrs="{'invisible': [('tax_ids','=',[])]}"/> </group> - <group string="Matching" attrs="{'invisible':[('full_reconcile_id','=',False)]}"> + <group string="Matching" attrs="{'invisible':[('full_reconcile_id','=',False), ('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}"> <label for="full_reconcile_id"/> <div> <field name="full_reconcile_id"/> diff --git a/addons/account_payment/views/account_portal_templates.xml b/addons/account_payment/views/account_portal_templates.xml index e9d121e15e0..bdc9c9da488 100644 --- a/addons/account_payment/views/account_portal_templates.xml +++ b/addons/account_payment/views/account_portal_templates.xml @@ -48,7 +48,7 @@ <div t-if="pms or acquirers" id="payment_method" class="text-left col-md-13"> <t t-call="payment.payment_tokens_list"> <t t-set="mode" t-value="'payment'"/> - <t t-set="partner_id" t-value="invoice.partner_id.id"/> + <t t-set="partner_id" t-value="request.env.user.partner_id.id"/> <t t-set="success_url" t-value="invoice.get_portal_url()"/> <t t-set="error_url" t-value="invoice.get_portal_url()"/> <t t-set="access_token" t-value="access_token or ''"/> diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index f8f01119e7d..c9ee67f2c7b 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -31,13 +31,13 @@ class Partner(models.Model): replacement_xml = """ <div> <field name="country_enforce_cities" invisible="1"/> - <field name='city' placeholder="%(placeholder)s" + <field name='city' placeholder="%(placeholder)s" class="o_address_city" 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" + <field name='city_id' placeholder="%(placeholder)s" string="%(placeholder)s" class="o_address_city" context="{'default_country_id': country_id, 'default_name': city, 'default_zipcode': zip, diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index b8356cf13b5..12c81e9d620 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -165,7 +165,7 @@ def is_multilang_url(local_url, langs=None): local_url = '/'.join(spath) try: # Try to match an endpoint in werkzeug's routing table - url = local_url.split('?') + url = local_url.partition('#')[0].split('?') path = url[0] query_string = url[1] if len(url) > 1 else None router = request.httprequest.app.get_db_router(request.db).bind('') @@ -174,6 +174,10 @@ def is_multilang_url(local_url, langs=None): return (func.routing.get('website', False) and func.routing.get('multilang', func.routing['type'] == 'http')) except werkzeug.exceptions.NotFound: + # Consider /static/ files as non-multilang + static_index = path.find('/static/', 1) + if static_index != -1 and static_index == path.find('/', 1): + return False return True except Exception as e: return False diff --git a/addons/l10n_pl/data/account_tax_data.xml b/addons/l10n_pl/data/account_tax_data.xml index f29294b36aa..973d05ff7b6 100644 --- a/addons/l10n_pl/data/account_tax_data.xml +++ b/addons/l10n_pl/data/account_tax_data.xml @@ -256,7 +256,7 @@ <field name="type_tax_use">sale</field> <field name="refund_account_id" ref="chart22030200"/> <field name="account_id" ref="chart22030200"/> - <field name="tag_ids" eval="[ref('l10n_pl.tag_pl_08'), ref('l10n_pl.tag_pl_29')]"/> + <field name="tag_ids" eval="[(6,0,[ref('l10n_pl.tag_pl_08'), ref('l10n_pl.tag_pl_29')])]"/> <field name="tax_group_id" ref="tax_group_vat_7"/> </record> diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index fd42268851a..df68208194e 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -741,9 +741,6 @@ var Chrome = PosBaseWidget.extend(AbstractAction.prototype, { if(err.message === 'XmlHttpRequestError '){ title = 'Network Failure (XmlHttpRequestError)'; body = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.'; - }else if(err.message === 'TLSError'){ - title = 'Https connection to IoT Box failed'; - body = 'Make sure you are using IoT Box v18.12 or higher.\n\n Navigate to ' + err.url + ' to accept the certificate of your IoT Box.'; }else if(err.code === 200){ title = err.data.message; body = err.data.debug; diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 97befbfe2b4..fec7dc6b05f 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -135,11 +135,16 @@ exports.PosModel = Backbone.Model.extend({ done.resolve(); }, function(statusText, url){ - if (statusText == 'error' && window.location.protocol == 'https:') { - var error = {message: 'TLSError', url: url}; - self.chrome.loading_error(error); - } else { - done.resolve(); + var show_loading_error = (self.gui.current_screen === null); + done.resolve(); + if (show_loading_error && statusText == 'error' && window.location.protocol == 'https:') { + self.gui.show_popup('alert', { + title: _t('HTTPS connection to IoT Box failed'), + body: _.str.sprintf( + _t('Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box.'), + url + ), + }); } }); return done; diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 601a0da28ac..6f80b4174f9 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -15,6 +15,7 @@ from odoo.tools import float_compare, pycompat _logger = logging.getLogger(__name__) + class ProductCategory(models.Model): _name = "product.category" _description = "Product Category" @@ -556,7 +557,10 @@ class ProductProduct(models.Model): precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') res = self.env['product.supplierinfo'] - for seller in self._prepare_sellers(params): + sellers = self._prepare_sellers(params) + if self.env.context.get('force_company'): + sellers = sellers.filtered(lambda s: not s.company_id or s.company_id.id == self.env.context['force_company']) + for seller in sellers: # Set quantity in UoM of seller quantity_uom_seller = quantity if quantity_uom_seller and uom_id and uom_id != seller.product_uom: diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index 73321703058..f1068df5775 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -289,8 +289,9 @@ <group name="pricing" string="Pricing"> <field name="product_variant_count" invisible="1"/> <field name="lst_price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" attrs="{'readonly': [('product_variant_count', '>', 1)]}"/> - <field name="standard_price" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}"/> + <field name="standard_price" widget='monetary' options="{'currency_field': 'cost_currency_id', 'field_digits': True}"/> <field name="currency_id" invisible='1'/> + <field name="cost_currency_id" invisible="1"/> </group> <group name="weight"> <group name="weight" string="Weights" attrs="{'invisible':[('type', 'not in', ['product', 'consu'])]}"> diff --git a/addons/purchase_stock/models/stock_rule.py b/addons/purchase_stock/models/stock_rule.py index d24c0340325..713cc6978ca 100644 --- a/addons/purchase_stock/models/stock_rule.py +++ b/addons/purchase_stock/models/stock_rule.py @@ -85,7 +85,7 @@ class StockRule(models.Model): def _get_purchase_order_date(self, product_id, product_qty, product_uom, values, partner, schedule_date): """Return the datetime value to use as Order Date (``date_order``) for the Purchase Order created to satisfy the given procurement. """ - seller = product_id._select_seller( + seller = product_id.with_context(force_company=values['company_id'].id)._select_seller( partner_id=partner, quantity=product_qty, date=schedule_date and schedule_date.date(), @@ -95,7 +95,7 @@ class StockRule(models.Model): def _update_purchase_order_line(self, product_id, product_qty, product_uom, values, line, partner): procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id) - seller = product_id._select_seller( + seller = product_id.with_context(force_company=values['company_id'].id)._select_seller( partner_id=partner, quantity=line.product_qty + procurement_uom_po_qty, date=line.order_id.date_order and line.order_id.date_order.date(), @@ -115,7 +115,7 @@ class StockRule(models.Model): @api.multi def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, partner): procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id) - seller = product_id._select_seller( + seller = product_id.with_context(force_company=values['company_id'].id)._select_seller( partner_id=partner, quantity=procurement_uom_po_qty, date=po.date_order and po.date_order.date(), diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index 2cbe2a6c6ff..231e933f7c0 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -3,7 +3,7 @@ from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tools import float_utils, float_compare @@ -151,13 +151,13 @@ class Inventory(models.Model): if self.filter == 'none' and self.product_id and self.location_id and self.lot_id: return if self.filter not in ('product', 'product_owner') and self.product_id: - raise UserError(_('The selected product doesn\'t belong to that owner..')) + raise ValidationError(_('The selected product doesn\'t belong to that owner..')) if self.filter != 'lot' and self.lot_id: - raise UserError(_('The selected lot number doesn\'t exist.')) + raise ValidationError(_('The selected lot number doesn\'t exist.')) if self.filter not in ('owner', 'product_owner') and self.partner_id: - raise UserError(_('The selected owner doesn\'t have the proprietary of that product.')) + raise ValidationError(_('The selected owner doesn\'t have the proprietary of that product.')) if self.filter != 'pack' and self.package_id: - raise UserError(_('The selected inventory options are not coherent, the package doesn\'t exist.')) + raise ValidationError(_('The selected inventory options are not coherent, the package doesn\'t exist.')) def action_reset_product_qty(self): self.mapped('line_ids').write({'product_qty': 0}) @@ -425,7 +425,7 @@ class InventoryLine(models.Model): """ for line in self: if line.product_id.type != 'product': - raise UserError(_("You can only adjust storable products.") + '\n\n%s -> %s' % (line.product_id.display_name, line.product_id.type)) + raise ValidationError(_("You can only adjust storable products.") + '\n\n%s -> %s' % (line.product_id.display_name, line.product_id.type)) def _get_move_values(self, qty, location_id, location_dest_id, out): self.ensure_one() diff --git a/addons/stock/wizard/stock_immediate_transfer.py b/addons/stock/wizard/stock_immediate_transfer.py index 66a652ac795..9b46a91c585 100644 --- a/addons/stock/wizard/stock_immediate_transfer.py +++ b/addons/stock/wizard/stock_immediate_transfer.py @@ -22,7 +22,7 @@ class StockImmediateTransfer(models.TransientModel): picking.action_assign() if picking.state != 'assigned': raise UserError(_("Could not reserve all requested products. Please use the \'Mark as Todo\' button to handle the reservation manually.")) - for move in picking.move_lines: + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): for move_line in move.move_line_ids: move_line.qty_done = move_line.product_uom_qty if picking._check_backorder(): diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 377acea0444..1b544c5559d 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -304,7 +304,7 @@ class StockMove(models.Model): if qty_to_take_on_candidates == 0: move.write({ 'value': -tmp_value if not quantity else move.value or -tmp_value, # outgoing move are valued negatively - 'price_unit': -tmp_value / move.product_qty, + 'price_unit': -tmp_value / (move.product_qty or quantity), }) elif qty_to_take_on_candidates > 0: last_fifo_price = new_standard_price or move.product_id.standard_price diff --git a/addons/survey/static/src/js/survey.js b/addons/survey/static/src/js/survey.js index 376c6c4da28..3fa3169a485 100644 --- a/addons/survey/static/src/js/survey.js +++ b/addons/survey/static/src/js/survey.js @@ -97,7 +97,7 @@ if(!the_form.length) { } // checkboxes and radios - the_form.find("input[name^=" + key + "][type!='text']").each(function(){ + the_form.find("input[name=" + key + "][type!='text']").each(function(){ $(this).val(value); }); }); diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js index 62586a53b5c..464c58ed4ee 100644 --- a/addons/web_editor/static/src/js/editor/rte.summernote.js +++ b/addons/web_editor/static/src/js/editor/rte.summernote.js @@ -963,7 +963,10 @@ eventHandler.attach = function (oLayoutInfo, options) { show_tooltip = true; setTimeout(function () { - if (!show_tooltip) return; + // Do not show tooltip on double-click and if there is already one + if (!show_tooltip || $target.attr('title') !== undefined) { + return; + } $target.tooltip({title: _t('Double-click to edit'), trigger: 'manuel', container: 'body'}).tooltip('show'); setTimeout(function () { $target.tooltip('dispose'); diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 34a45f7193d..7fda9238f34 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -912,7 +912,7 @@ var VideoWidget = MediaWidget.extend({ options = options || {}; // Video url patterns(youtube, instagram, vimeo, dailymotion, youku, ...) - var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/; + var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$/; var ytMatch = url.match(ytRegExp); var insRegExp = /(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)/; @@ -939,8 +939,8 @@ var VideoWidget = MediaWidget.extend({ var autoplay = options.autoplay ? '?autoplay=1' : '?autoplay=0'; - if (ytMatch && ytMatch[1].length === 11) { - $video.attr('src', '//www.youtube.com/embed/' + ytMatch[1] + autoplay); + if (ytMatch && ytMatch[2].length === 11) { + $video.attr('src', '//www.youtube' + (ytMatch[1] || '') + '.com/embed/' + ytMatch[2] + autoplay); } else if (insMatch && insMatch[2].length) { $video.attr('src', '//www.instagram.com/p/' + insMatch[2] + '/embed/'); videoType = 'ins'; diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index 32d221ea5ea..5f9242f262b 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -4,6 +4,7 @@ import re from collections import OrderedDict from odoo import models +from odoo.addons.http_routing.models.ir_http import url_for from odoo.http import request from odoo.addons.base.models.assetsbundle import AssetsBundle from odoo.tools import html_escape as escape @@ -50,10 +51,16 @@ class QWeb(models.AbstractModel): if not website and options.get('website_id'): website = self.env['website'].browse(options['website_id']) - if not website or not website.cdn_activated: + if not website: return atts name = self.URL_ATTRS.get(tagName) + if request and name and name in atts: + atts[name] = url_for(atts[name]) + + if not website.cdn_activated: + return atts + if name and name in atts: atts = OrderedDict(atts) atts[name] = website.get_cdn_url(atts[name]) diff --git a/addons/website/tests/test_qweb.py b/addons/website/tests/test_qweb.py index 5e078d89dd6..8cba2d150c0 100644 --- a/addons/website/tests/test_qweb.py +++ b/addons/website/tests/test_qweb.py @@ -2,7 +2,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import re +import werkzeug +import odoo from odoo import tools from odoo.modules.module import get_module_resource from odoo.tests.common import TransactionCase @@ -63,3 +65,123 @@ class TestQweb(TransactionCase): "css": attachments[1].url, "user_id": demo.id, }).encode('utf8')) + +class MockObject(object): + _log_call = [] + def __init__(self, *args, **kwargs): + self.__dict__ = kwargs + def __call__(self, *args, **kwargs): + self._log_call.append((args, kwargs)) + return self + def __getitem__(self, index): + return self + +def werkzeugRaiseNotFound(*args, **kwargs): + raise werkzeug.exceptions.NotFound() + +class MockRequest(object): + """ Class with context manager mocking odoo.http.request for tests """ + def __init__(self, env, website=None, context=None, multilang=True, routing=True): + app = MockObject(routing={ + 'type': 'http', + 'website': True, + 'multilang': multilang, + }) + app.get_db_router = app.bind = app.match = app + if not routing: + app.match = werkzeugRaiseNotFound + self.request = MockObject( + env=env, context=context or {}, db=None, debug=False, + website=website, httprequest=MockObject( + path='/hello/', + app=app + ) + ) + odoo.http._request_stack.push(self.request) + def __enter__(self): + return self.request + def __exit__(self, exc_type, exc_value, traceback): + odoo.http._request_stack.pop() + +class TestQwebProcessAtt(TransactionCase): + def setUp(self): + super(TestQwebProcessAtt, self).setUp() + self.website = self.env['website'].browse(1) + self.website.language_ids = self.env.ref('base.lang_en') + self.env.ref('base.lang_fr') + self.website.default_lang_id = self.env.ref('base.lang_en') + self.website.cdn_activated = True + self.website.cdn_url = "http://test.cdn" + self.website.cdn_filters = "\n".join(["^(/[a-z]{2}_[A-Z]{2})?/a$", "^/b$"]) + + def _test_att(self, url, expect, tag='a', attribute='href'): + self.assertEqual( + self.env['ir.qweb']._post_processing_att(tag, {attribute: url}, {}), + expect + ) + + def test_process_att_no_request(self): + # no request so no URL rewriting + self._test_att('/', {'href': '/'}) + self._test_att('/en_US/', {'href': '/en_US/'}) + self._test_att('/fr_FR/', {'href': '/fr_FR/'}) + # no URL rewritting for CDN + self._test_att('/a', {'href': '/a'}) + + def test_process_att_no_website(self): + with MockRequest(self.env) as request: + # no website so URL rewriting + self._test_att('/', {'href': '/'}) + self._test_att('/en_US/', {'href': '/en_US/'}) + self._test_att('/fr_FR/', {'href': '/fr_FR/'}) + # no URL rewritting for CDN + self._test_att('/a', {'href': '/a'}) + + def test_process_att_monolang_route(self): + with MockRequest(self.env, website=self.website, multilang=False) as request: + # lang not changed in URL but CDN enabled + self._test_att('/a', {'href': 'http://test.cdn/a'}) + self._test_att('/en_US/a', {'href': 'http://test.cdn/en_US/a'}) + self._test_att('/b', {'href': 'http://test.cdn/b'}) + self._test_att('/en_US/b', {'href': '/en_US/b'}) + + def test_process_att_no_request_lang(self): + with MockRequest(self.env, self.website) as request: + self._test_att('/', {'href': '/'}) + self._test_att('/en_US/', {'href': '/'}) + self._test_att('/fr_FR/', {'href': '/fr_FR/'}) + + def test_process_att_with_request_lang(self): + with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}) as request: + self._test_att('/', {'href': '/fr_FR/'}) + self._test_att('/en_US/', {'href': '/'}) + self._test_att('/fr_FR/', {'href': '/fr_FR/'}) + + def test_process_att_matching_cdn_and_lang(self): + with MockRequest(self.env, self.website) as request: + # lang prefix is added before CDN + self._test_att('/a', {'href': 'http://test.cdn/a'}) + self._test_att('/en_US/a', {'href': 'http://test.cdn/a'}) + self._test_att('/fr_FR/a', {'href': 'http://test.cdn/fr_FR/a'}) + self._test_att('/b', {'href': 'http://test.cdn/b'}) + self._test_att('/en_US/b', {'href': 'http://test.cdn/b'}) + self._test_att('/fr_FR/b', {'href': '/fr_FR/b'}) + + def test_process_att_no_route(self): + with MockRequest(self.env, self.website, context={'lang': 'fr_FR'}, routing=False) as request: + # default on multilang=True if route is not /{module}/static/ + self._test_att('/web/static/hi', {'href': '/web/static/hi'}) + self._test_att('/my-page', {'href': '/fr_FR/my-page'}) + + def test_process_att_url_crap(self): + with MockRequest(self.env, self.website) as request: + # #{fragment} is stripped from URL when testing route + self._test_att('/x#y?z', {'href': '/x#y?z'}) + self.assertEqual( + request.httprequest.app._log_call[-1], + (('/x',), {'method': 'POST', 'query_args': None}) + ) + self._test_att('/x?y#z', {'href': '/x?y#z'}) + self.assertEqual( + request.httprequest.app._log_call[-1], + (('/x',), {'method': 'POST', 'query_args': 'y'}) + ) |
