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. diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py index 82483b63bd2..3c86cc69a0e 100644 --- a/addons/website/__manifest__.py +++ b/addons/website/__manifest__.py @@ -29,6 +29,7 @@ 'views/website_views.xml', 'views/website_visitor_views.xml', 'views/res_config_settings_views.xml', + 'views/website_rewrite.xml', 'views/ir_actions_views.xml', 'views/ir_attachment_views.xml', 'views/res_partner_views.xml', diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 6ac7ef197f8..e1252e0e23b 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -4,7 +4,6 @@ import base64 import datetime import json import os -import re import logging import pytz import requests diff --git a/addons/website/models/__init__.py b/addons/website/models/__init__.py index 1c32e9d514a..83ef426cacf 100644 --- a/addons/website/models/__init__.py +++ b/addons/website/models/__init__.py @@ -11,7 +11,7 @@ from . import mixins from . import website from . import website_menu from . import website_page -from . import website_redirect +from . import website_rewrite from . import ir_rule from . import ir_ui_view from . import res_company diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index ccfc2c531e4..2ad541d3e33 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -11,6 +11,8 @@ import werkzeug import werkzeug.routing import werkzeug.utils +from functools import partial + import odoo from odoo import api, models, registry from odoo import SUPERUSER_ID @@ -61,6 +63,58 @@ def get_request_website(): class Http(models.AbstractModel): _inherit = 'ir.http' + @classmethod + def routing_map(cls, key=None): + key = key or (request and request.website_routing) + return super(Http, cls).routing_map(key=key) + + @classmethod + def clear_caches(cls): + super(Http, cls)._clear_routing_map() + return super(Http, cls).clear_caches() + + @classmethod + def _slug_matching(cls, adapter, endpoint, **kw): + for arg in kw: + if isinstance(kw[arg], models.BaseModel): + kw[arg] = kw[arg].with_user(request.uid) + qs = request.httprequest.query_string.decode('utf-8') + return adapter.build(endpoint, kw) + (qs and '?%s' % qs or '') + + @classmethod + def _match(cls, path_info, key=None): + key = key or (request and request.website_routing) + return super(Http, cls)._match(path_info, key=key) + + @classmethod + def _generate_routing_rules(cls, modules, converters): + website_id = request.website_routing + logger.debug("_generate_routing_rules for website: %s", website_id) + domain = [('redirect_type', 'in', ('rewrite', 'not_found')), '|', ('website_id', '=', False), ('website_id', '=', website_id)] + + rewrites = dict([(x.url_from, x) for x in request.env['website.rewrite'].sudo().search(domain)]) + cls._rewrite_len[website_id] = len(rewrites) + + for url, endpoint, routing in super(Http, cls)._generate_routing_rules(modules, converters): + routing = dict(routing) + if url in rewrites: + rewrite = rewrites[url] + url_to = rewrite.url_to + if rewrite.redirect_type == 'rewrite': + logger.debug('Add rule %s for %s' % (url_to, website_id)) + yield url_to, endpoint, routing # yield new url + + if url != url_to: + logger.debug('Redirect from %s to %s for website %s' % (url, url_to, website_id)) + _slug_matching = partial(cls._slug_matching, endpoint=endpoint) + routing['redirect_to'] = _slug_matching + yield url, endpoint, routing # yield original redirected to new url + elif rewrite.redirect_type == 'not_found': + logger.debug('Return 404 for %s for website %s' % (url, website_id)) + continue + else: + yield url, endpoint, routing + @classmethod def _get_converters(cls): """ Get the converters list for custom url pattern werkzeug need to @@ -111,7 +165,11 @@ class Http(models.AbstractModel): handling the original request, in which we should create the visitor. We ignore every other rerouting requests. """ is_rerouting = hasattr(request, 'routing_iteration') + + request.website_routing = request.env['website'].get_current_website().id + response = super(Http, cls)._dispatch() + if not is_rerouting: cls._register_website_track(response) return response @@ -186,8 +244,12 @@ class Http(models.AbstractModel): @classmethod def _serve_redirect(cls): req_page = request.httprequest.path - domain = [('url_from', '=', req_page)] + request.website.website_domain() - return request.env['website.redirect'].search(domain, limit=1) + domain = [ + ('redirect_type', 'in', ('redirect_301', 'redirect_302')), + ('url_from', '=', req_page) + ] + domain += request.website.website_domain() + return request.env['website.rewrite'].sudo().search(domain, limit=1) @classmethod def _serve_fallback(cls, exception): diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 0ddca6810e2..f8d8d652e72 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -714,6 +714,7 @@ class Website(models.Model): converters = rule._converters or {} if query_string and not converters and (query_string not in rule.build([{}], append_unknown=False)[1]): continue + values = [{}] # converters with a domain are processed after the other ones convitems = sorted( diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py index 5473efeffe6..1d3d72b68a6 100644 --- a/addons/website/models/website_page.py +++ b/addons/website/models/website_page.py @@ -122,7 +122,7 @@ class Page(models.Model): # Create redirect if needed if data['create_redirect']: - self.env['website.redirect'].create({ + self.env['website.rewrite'].create({ 'redirect_type': data['redirect_type'], 'url_from': original_url, 'url_to': url, diff --git a/addons/website/models/website_redirect.py b/addons/website/models/website_redirect.py deleted file mode 100644 index a0fbda884b0..00000000000 --- a/addons/website/models/website_redirect.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import fields, models - - -class WebsiteRedirect(models.Model): - _name = "website.redirect" - _description = "Website Redirect" - _order = "sequence, id" - _rec_name = 'url_from' - - redirect_type = fields.Selection([('301', 'Moved permanently (301)'), ('302', 'Moved temporarily (302)')], string='Redirection Type', required=True, default="301") - url_from = fields.Char('Redirect From', required=True) - url_to = fields.Char('Redirect To', required=True) - website_id = fields.Many2one('website', 'Website', ondelete='cascade') - active = fields.Boolean(default=True) - sequence = fields.Integer() diff --git a/addons/website/models/website_rewrite.py b/addons/website/models/website_rewrite.py new file mode 100644 index 00000000000..77acdb8c120 --- /dev/null +++ b/addons/website/models/website_rewrite.py @@ -0,0 +1,104 @@ +from odoo import models, fields, api +from odoo.exceptions import AccessDenied + +import logging +_logger = logging.getLogger(__name__) + + +class WebsiteRoute(models.Model): + _rec_name = 'path' + _name = 'website.route' + _description = "All Website Route" + _order = 'path' + + path = fields.Char('Route') + + @api.model + def _name_search(self, name='', args=None, operator='ilike', limit=100, name_get_uid=None): + res = super(WebsiteRoute, self)._name_search(name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid) + if not len(res): + self._refresh() + return super(WebsiteRoute, self)._name_search(name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid) + return res + + def _refresh(self): + _logger.debug("Refreshing website.route") + ir_http = self.env['ir.http'] + tocreate = [] + paths = {rec.path: rec for rec in self.search([])} + for url, _, routing in ir_http._generate_routing_rules(self.pool._init_modules, converters=ir_http._get_converters()): + if 'GET' in (routing.get('methods') or ['GET']): + if paths.get(url): + paths.pop(url) + else: + tocreate.append({'path': url}) + + if tocreate: + _logger.info("Add %d website.route" % len(tocreate)) + self.create(tocreate) + + if paths: + find = self.search([('path', 'in', list(paths.keys()))]) + _logger.info("Delete %d website.route" % len(find)) + find.unlink() + + +class WebsiteRewrite(models.Model): + _name = 'website.rewrite' + _description = "Website rewrite" + + name = fields.Char('Name', required=True) + website_id = fields.Many2one('website', string="Website", ondelete='cascade', index=True) + active = fields.Boolean(default=True) + url_from = fields.Char('URL from', index=True) + route_id = fields.Many2one('website.route') + url_to = fields.Char("URL to") + redirect_type = fields.Selection([ + ('not_found', '404 Not Found'), + ('redirect_301', '301 Moved permanently'), + ('redirect_302', '302 Moved temporarily'), + ('rewrite', '308 Redirect / Rewrite'), + ], string='Action', default="redirect_302", + help='''Type of redirect/Rewrite:\n + 301 Moved permanently: The browser will keep in cache the new url. + 302 Moved temporarily: The browser will not keep in cache the new url and ask again the next time the new url. + 404 Not Found: If you want remove a specific page/controller (e.g. Ecommerce is installed, but you don't want /shop on a specific website) + 308 Redirect / Rewrite: If you want rename a controller with a new url. (Eg: /shop -> /garden - Both url will be accessible but /shop will automatically be redirected to /garden) + ''') + + sequence = fields.Integer() + + @api.onchange('route_id') + def _onchange_route_id(self): + self.url_from = self.route_id.path + self.url_to = self.route_id.path + + def name_get(self): + result = [] + for rewrite in self: + name = rewrite.redirect_type + ' - ' + rewrite.name + result.append((rewrite.id, name)) + return result + + @api.model + def create(self, vals): + res = super(WebsiteRewrite, self).create(vals) + self._invalidate_routing() + return res + + def write(self, vals): + res = super(WebsiteRewrite, self).write(vals) + self._invalidate_routing() + return res + + def unlink(self): + res = super(WebsiteRewrite, self).unlink() + self._invalidate_routing() + return res + + def _invalidate_routing(self): + # call clear_caches on this worker to reload routing table + self.env['ir.http'].clear_caches() + + def refresh_routes(self): + self.env['website.route']._refresh() diff --git a/addons/website/security/ir.model.access.csv b/addons/website/security/ir.model.access.csv index 624735ca4a4..242c3bd2ea5 100644 --- a/addons/website/security/ir.model.access.csv +++ b/addons/website/security/ir.model.access.csv @@ -3,8 +3,8 @@ access_website_public,website,website.model_website,,1,0,0,0 access_website_designer,website,website.model_website,group_website_designer,1,1,1,1 access_website_menu,access_website_menu,model_website_menu,,1,0,0,0 access_website_menu_designer,Web Menu Manager,model_website_menu,group_website_designer,1,1,1,1 -access_website_redirect,access_website_redirect,model_website_redirect,,1,0,0,0 -access_website_redirect_designer,Web Redirect Manager,model_website_redirect,group_website_designer,1,1,1,1 +access_website_rewrite,access_website_rewrite,model_website_rewrite,,0,0,0,0 +access_website_rewrite_designer,Web Rewrite Manager,model_website_rewrite,group_website_designer,1,1,1,1 access_website_page,access_website_page,model_website_page,,1,0,0,0 access_website_page_designer,Web Page Manager,model_website_page,group_website_designer,1,1,1,1 access_website,web menu manager,website.model_website,group_website_designer,1,1,1,1 @@ -18,3 +18,4 @@ access_website_visitor_lastconnection_designer,access_website_visitor_lastconnec access_website_visitor_lastconnection_system,access_website_visitor_lastconnection_system,model_website_visitor_lastconnection,base.group_system,1,1,1,1 access_website_track_designer,access_website_track_designer,model_website_track,website.group_website_designer,1,1,1,1 access_website_track_system,access_website_track_system,model_website_track,base.group_system,1,1,1,1 +access_website_route_designer,access_website_designer_route,model_website_route,group_website_designer,1,1,1,1 \ No newline at end of file diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js index c0daa93925f..6aeeb9d5085 100644 --- a/addons/website/static/src/js/menu/content.js +++ b/addons/website/static/src/js/menu/content.js @@ -86,7 +86,7 @@ var PagePropertiesDialog = weWidgets.Dialog.extend({ })); defs.push(this._rpc({ - model: 'website.redirect', + model: 'website.rewrite', method: 'fields_get', }).then(function (fields) { self.fields = fields; diff --git a/addons/website/tests/test_lang_url.py b/addons/website/tests/test_lang_url.py index f4998168074..871248c2778 100644 --- a/addons/website/tests/test_lang_url.py +++ b/addons/website/tests/test_lang_url.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.addons.http_routing.models.ir_http import url_for +from odoo.addons.http_routing.models.ir_http import url_lang from odoo.addons.website.tools import MockRequest from odoo.tests import HttpCase, tagged @@ -18,9 +18,9 @@ class TestLangUrl(HttpCase): self.website.language_ids = self.env.ref('base.lang_en') + lang_fr self.website.default_lang_id = self.env.ref('base.lang_en') - def test_01_url_for(self): + def test_01_url_lang(self): with MockRequest(self.env, website=self.website): - self.assertEqual(url_for('', '[lang]'), '/[lang]/hello/', "`[lang]` is used to be replaced in the url_return after installing a language, it should not be replaced or removed.") + self.assertEqual(url_lang('', '[lang]'), '/[lang]/hello/', "`[lang]` is used to be replaced in the url_return after installing a language, it should not be replaced or removed.") def test_02_url_redirect(self): url = '/fr_WHATEVER/contactus' diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index ff0ac73f2b5..4c3f7e6c296 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -380,13 +380,6 @@ sequence="30" /> - - + + + + website.rewrite + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + + website.rewrite.list + website.rewrite + + + + + + + + + + + + + + + + Rewrite + ir.actions.act_window + website.rewrite + + + + + + + website.rewrite.search + website.rewrite + + + + + + + + + + + + +
diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index 92841cb2063..cac1ba89b28 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -62,23 +62,6 @@ - - - website.redirect.search - website.redirect - - - - - - - - - - - - - Websites website @@ -87,52 +70,6 @@ current - - - website.redirect.form - website.redirect - -
- - - - - - - - - - - - - -
-
-
- - - website.redirect.list - website.redirect - - - - - - - - - - - - - - Website Redirects - website.redirect - tree,form - - current - - diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index cc76a8d0ff9..f2c87db5fa4 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -87,8 +87,12 @@ class IrHttp(models.AbstractModel): return {'model': ModelConverter, 'models': ModelsConverter, 'int': SignedIntConverter} @classmethod - def _find_handler(cls, return_rule=False): - return cls.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule) + def _match(cls, path_info, key=None): + return cls.routing_map(key=key).bind('').match( + return_rule=True, + method=request.httprequest.method, + path_info=path_info + ) @classmethod def _auth_method_user(cls): @@ -208,7 +212,7 @@ class IrHttp(models.AbstractModel): # locate the controller method try: - rule, arguments = cls._find_handler(return_rule=True) + rule, arguments = cls._match(request.httprequest.path) func = rule.endpoint except werkzeug.exceptions.NotFound as e: return cls._handle_exception(e) @@ -245,24 +249,37 @@ class IrHttp(models.AbstractModel): return cls._handle_exception(werkzeug.exceptions.NotFound()) @classmethod - def routing_map(cls): + def _generate_routing_rules(cls, modules, converters): + return http._generate_routing_rules(modules, False, converters) + + @classmethod + def routing_map(cls, key=None): if not hasattr(cls, '_routing_map'): - _logger.info("Generating routing map") - installed = request.registry._init_modules - {'web'} + cls._routing_map = {} + cls._rewrite_len = {} + if key not in cls._routing_map: + _logger.info("Generating routing map for key %s" % str(key)) + installed = request.registry._init_modules | set(odoo.conf.server_wide_modules) if tools.config['test_enable'] and odoo.modules.module.current_test: installed.add(odoo.modules.module.current_test) - mods = [''] + odoo.conf.server_wide_modules + sorted(installed) + mods = sorted(installed) # Note : when routing map is generated, we put it on the class `cls` # to make it available for all instance. Since `env` create an new instance # of the model, each instance will regenared its own routing map and thus # regenerate its EndPoint. The routing map should be static. - cls._routing_map = http.routing_map(mods, False, converters=cls._get_converters()) - return cls._routing_map + routing_map = werkzeug.routing.Map(strict_slashes=False, converters=cls._get_converters()) + for url, endpoint, routing in cls._generate_routing_rules(mods, converters=cls._get_converters()): + xtra_keys = 'defaults subdomain build_only strict_slashes redirect_to alias host'.split() + kw = {k: routing[k] for k in xtra_keys if k in routing} + routing_map.add(werkzeug.routing.Rule(url, endpoint=endpoint, methods=routing['methods'], **kw)) + cls._routing_map[key] = routing_map + return cls._routing_map[key] @classmethod def _clear_routing_map(cls): if hasattr(cls, '_routing_map'): - del cls._routing_map + cls._routing_map = {} + _logger.debug("Clear routing map") #------------------------------------------------------ # Binary server @@ -337,8 +354,6 @@ class IrHttp(models.AbstractModel): status = 301 content = record.url - - return status, content, filename, mimetype, filehash def _binary_record_content( diff --git a/odoo/http.py b/odoo/http.py index 94445401801..721746ee7ce 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -908,9 +908,8 @@ class EndPoint(object): def __call__(self, *args, **kw): return self.method(*args, **kw) -def routing_map(modules, nodb_only, converters=None): - routing_map = werkzeug.routing.Map(strict_slashes=False, converters=converters) +def _generate_routing_rules(modules, nodb_only, converters=None): def get_subclasses(klass): def valid(c): return c.__module__.startswith('odoo.addons.') and c.__module__.split(".")[2] in modules @@ -949,16 +948,8 @@ def routing_map(modules, nodb_only, converters=None): assert routing['routes'], "Method %r has not route defined" % mv endpoint = EndPoint(mv, routing) for url in routing['routes']: - if routing.get("combine", False): - # deprecated v7 declaration - url = o._cp_path.rstrip('/') + '/' + url.lstrip('/') - if url.endswith("/") and len(url) > 1: - url = url[: -1] + yield (url, endpoint, routing) - xtra_keys = 'defaults subdomain build_only strict_slashes redirect_to alias host'.split() - kw = {k: routing[k] for k in xtra_keys if k in routing} - routing_map.add(werkzeug.routing.Rule(url, endpoint=endpoint, methods=routing['methods'], **kw)) - return routing_map #---------------------------------------------------------- # HTTP Sessions @@ -1276,7 +1267,10 @@ class Root(object): @lazy_property def nodb_routing_map(self): _logger.info("Generating nondb routing") - return routing_map([''] + odoo.conf.server_wide_modules, True) + routing_map = werkzeug.routing.Map(strict_slashes=False, converters=None) + for url, endpoint, routing in odoo.http._generate_routing_rules([''] + odoo.conf.server_wide_modules, True): + routing_map.add(werkzeug.routing.Rule(url, endpoint=endpoint, methods=routing['methods'])) + return routing_map def __call__(self, environ, start_response): """ Handle a WSGI request