diff options
| author | Jeremy Kersten <jke@odoo.com> | 2019-09-12 09:45:11 +0000 |
|---|---|---|
| committer | Jeremy Kersten <jke@odoo.com> | 2019-09-30 13:58:14 +0000 |
| commit | be8fc2296b3822baffe0bc68cb4b7efdfe53fa91 (patch) | |
| tree | 08f0f825cfe2ffa652557f244f36880149d2f585 /addons/http_routing | |
| parent | b6b42ef73f4c517d9b7bc2da4909c13d7fe8f2c6 (diff) | |
[IMP] base, http_routing, website: allow custom routing rule
After this commit, you will be able (in technical mode) to update the url for
the python controllers.
Eg.
You can now rename /shop in /garden and /shop/product/ in /garden/vegetable/
Most of urls will be replaced at fly in the renderd qweb, with the function
url_for but all old urls will keep available. So if you access url /shop you
will be automatically redirected to /garden (308 Permanent Redirect).
As for cdn and other post-process of att, the automatically replacement in the
rendered qweb is only done when you will be not website editor. But the new
dispatch of URL will be applied in all cases.
For developper, since it is Permanent Redirect, don't forget to clear cache or
open chrome debug tool (with option 'Disable cache while DevTools is Open) to
see your lasts changes.
closes odoo/odoo#36555
Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
Diffstat (limited to 'addons/http_routing')
| -rw-r--r-- | addons/http_routing/models/ir_http.py | 77 |
1 files changed, 57 insertions, 20 deletions
diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index 78217881e65..71d62d054db 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -99,6 +99,7 @@ def slug(value): return str(identifier) return "%s-%d" % (slugname, identifier) + # NOTE: as the pattern is used as it for the ModelConverter (ir_http.py), do not use any flags _UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)') @@ -128,7 +129,7 @@ def unslug_url(s): # Language tools # ------------------------------------------------------------ -def url_for(path_or_uri, lang_code=None): +def url_lang(path_or_uri, lang_code=None): ''' Given a relative URL, make it absolute and add the required lang or remove useless lang. Nothing will be done for absolute URL. @@ -164,10 +165,46 @@ def url_for(path_or_uri, lang_code=None): elif lang_url_code != default_lg.url_code or force_lang: ps.insert(1, lang_url_code) location = u'/'.join(ps) - return location +def url_for(url_from, lang_code=None, no_rewrite=False): + ''' Return the url with the rewriting applied. + Nothing will be done for absolute URL, or short URL from 1 char. + + :param url_from: The URL to convert. + :param lang_code: Must be the lang `code`. It could also be something + else, such as `'[lang]'` (used for url_return). + :param no_rewrite: don't try to match route with website.rewrite. + ''' + new_url = False + + # don't try to match route if we know that no rewrite has been loaded. + if not request.env['ir.http']._rewrite_len.get(request.website_routing): + no_rewrite = True + + # avoid useless check for 1 char URL '/', '#', ... and absolute URL + if not no_rewrite and url_from and (len(url_from) > 1 or not url_from.startswith('http')): + path, _, qs = url_from.partition('?') + req = request.httprequest + router = req.app.get_db_router(request.db).bind('') + try: + _ = router.match(path, method='POST') + except werkzeug.exceptions.MethodNotAllowed as e: + _ = router.match(path, method='GET') + except werkzeug.routing.RequestRedirect as e: + # remove query string from current env + new_url = e.new_url.split('?')[0] + # remove scheme and add query_string from url_from + new_url = new_url[7:] + (qs and '?%s' % qs or '') + except werkzeug.exceptions.NotFound as e: + new_url = url_from + except Exception as e: + raise e + + return url_lang(new_url or url_from, lang_code=lang_code) + + def is_multilang_url(local_url, lang_url_codes=None): ''' Check if the given URL content is supposed to be translated. To be considered as translatable, the URL should either: @@ -374,12 +411,15 @@ class IrHttp(models.AbstractModel): func = None routing_error = None + + # handle // in url + if request.httprequest.method == 'GET' and '//' in request.httprequest.path: + new_url = request.httprequest.path.replace('//', '/') + '?' + request.httprequest.query_string.decode('utf-8') + return werkzeug.utils.redirect(new_url, 301) + # locate the controller method try: - if request.httprequest.method == 'GET' and '//' in request.httprequest.path: - new_url = request.httprequest.path.replace('//', '/') + '?' + request.httprequest.query_string.decode('utf-8') - return werkzeug.utils.redirect(new_url, 301) - rule, arguments = cls._find_handler(return_rule=True) + rule, arguments = cls._match(request.httprequest.path) func = rule.endpoint request.is_frontend = func.routing.get('website', False) except werkzeug.exceptions.NotFound as e: @@ -388,13 +428,7 @@ class IrHttp(models.AbstractModel): request.is_frontend = True routing_error = e - request.is_frontend_multilang = ( - request.is_frontend and - (not func or (func and func.routing.get('multilang', func.routing['type'] == 'http'))) - ) - - cls._geoip_setup_resolver() - cls._geoip_resolve() + request.is_frontend_multilang = not func or (func and request.is_frontend and func.routing.get('multilang', func.routing['type'] == 'http')) # check authentication level try: @@ -405,6 +439,9 @@ class IrHttp(models.AbstractModel): except Exception as e: return cls._handle_exception(e) + cls._geoip_setup_resolver() + cls._geoip_resolve() + # For website routes (only), add website params on `request` if request.is_frontend: request.redirect = lambda url, code=302: werkzeug.utils.redirect(url_for(url), code) @@ -416,19 +453,19 @@ class IrHttp(models.AbstractModel): if request.routing_iteration == 1: is_a_bot = cls.is_a_bot() nearest_lang = not func and cls.get_nearest_lang(request.env['res.lang']._lang_get_code(path[1])) - url_lang = nearest_lang and path[1] + url_lg = nearest_lang and path[1] # The default lang should never be in the URL, and a wrong lang # should never be in the URL. - wrong_url_lang = url_lang and (url_lang != request.lang.url_code or url_lang == default_lg_id.url_code) + wrong_url_lg = url_lg and (url_lg != request.lang.url_code or url_lg == default_lg_id.url_code) # The lang is missing from the URL if multi lang is enabled for # the route and the current lang is not the default lang. # POST requests are excluded from this condition. - missing_url_lang = not url_lang and request.is_frontend_multilang and request.lang != default_lg_id and request.httprequest.method != 'POST' + missing_url_lg = not url_lg and request.is_frontend_multilang and request.lang != default_lg_id and request.httprequest.method != 'POST' # Bots should never be redirected when the lang is missing # because it is the only way for them to index the default lang. - if wrong_url_lang or (missing_url_lang and not is_a_bot): - if url_lang: + if wrong_url_lg or (missing_url_lg and not is_a_bot): + if url_lg: path.pop(1) if request.lang != default_lg_id: path.insert(1, request.lang.url_code) @@ -437,12 +474,12 @@ class IrHttp(models.AbstractModel): redirect = request.redirect(path + '?' + request.httprequest.query_string.decode('utf-8')) redirect.set_cookie('frontend_lang', request.lang.code) return redirect - elif url_lang: + elif url_lg: request.uid = None path.pop(1) routing_error = None return cls.reroute('/'.join(path) or '/') - elif missing_url_lang and is_a_bot: + elif missing_url_lg and is_a_bot: # Ensure that if the URL without lang is not redirected, the # current lang is indeed the default lang, because it is the # lang that bots should index in that case. |
