[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>
This commit is contained in:
Jeremy Kersten
2019-09-30 13:58:14 +00:00
parent b6b42ef73f
commit be8fc2296b
17 changed files with 349 additions and 143 deletions
+57 -20
View File
@@ -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.
+1
View File
@@ -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',
-1
View File
@@ -4,7 +4,6 @@ import base64
import datetime
import json
import os
import re
import logging
import pytz
import requests
+1 -1
View File
@@ -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
+64 -2
View File
@@ -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):
+1
View File
@@ -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(
+1 -1
View File
@@ -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,
-18
View File
@@ -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()
+104
View File
@@ -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()
+3 -2
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_website_designer website website.model_website group_website_designer 1 1 1 1
4 access_website_menu access_website_menu model_website_menu 1 0 0 0
5 access_website_menu_designer Web Menu Manager model_website_menu group_website_designer 1 1 1 1
6 access_website_redirect access_website_rewrite access_website_redirect access_website_rewrite model_website_redirect model_website_rewrite 1 0 0 0 0
7 access_website_redirect_designer access_website_rewrite_designer Web Redirect Manager Web Rewrite Manager model_website_redirect model_website_rewrite group_website_designer 1 1 1 1
8 access_website_page access_website_page model_website_page 1 0 0 0
9 access_website_page_designer Web Page Manager model_website_page group_website_designer 1 1 1 1
10 access_website web menu manager website.model_website group_website_designer 1 1 1 1
18 access_website_visitor_lastconnection_system access_website_visitor_lastconnection_system model_website_visitor_lastconnection base.group_system 1 1 1 1
19 access_website_track_designer access_website_track_designer model_website_track website.group_website_designer 1 1 1 1
20 access_website_track_system access_website_track_system model_website_track base.group_system 1 1 1 1
21 access_website_route_designer access_website_designer_route model_website_route group_website_designer 1 1 1 1
+1 -1
View File
@@ -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;
+3 -3
View File
@@ -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'
@@ -380,13 +380,6 @@
sequence="30"
/>
<menuitem name="Redirects"
id="menu_website_redirect_list"
action="action_website_redirect_list"
parent="menu_website_global_configuration"
sequence="40"
/>
<menuitem name="Menus"
id="menu_website_menu_list"
action="action_website_menu"
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Attachment -->
<record id="view_website_rewrite_form" model="ir.ui.view">
<field name="model">website.rewrite</field>
<field name="arch" type="xml">
<form string="Website rewrite Settings">
<header>
<button name="refresh_routes" string="Refresh route's list" type="object"
class="btn-primary"
attrs="{'invisible':[('redirect_type', '!=', 'rewrite')]}"
/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="redirect_type"/>
<field name="url_from" attrs="{'invisible': [('redirect_type', '=', 'rewrite')]}"/>
<field name="route_id" string="URL from" options="{'no_create': True, 'no_open': True}" attrs="{'invisible': [('redirect_type', '!=', 'rewrite')]}"/>
<field name="url_to" attrs="{'invisible': [('redirect_type', '=', 'not_found')]}"/>
</group>
<group>
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
<field name="active"/>
<field name="sequence" groups="base.group_no_one"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_website_rewrite_tree" model="ir.ui.view">
<field name="name">website.rewrite.list</field>
<field name="model">website.rewrite</field>
<field name="arch" type="xml">
<tree string="Website rewrites">
<field name="redirect_type"/>
<field name="name"/>
<field name="url_from"/>
<field name="url_to"/>
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
<field name="active"/>
<field name="sequence" widget="handle" />
</tree>
</field>
</record>
<record id="action_website_rewrite_list" model="ir.actions.act_window">
<field name="name">Rewrite</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">website.rewrite</field>
<field name="view_id" eval="False"/>
</record>
<menuitem name="Redirects"
id="menu_website_rewrite"
action="action_website_rewrite_list"
parent="menu_website_global_configuration"
sequence="30"
groups="base.group_no_one"/>
<record id="view_rewrite_search" model="ir.ui.view">
<field name="name">website.rewrite.search</field>
<field name="model">website.rewrite</field>
<field name="arch" type="xml">
<search string="Search Redirect">
<field name="url_from"/>
<field name="url_to"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group string="Group By">
<filter string="Redirection Type" name="group_by_type" domain="[]" context="{'group_by': 'redirect_type'}"/>
</group>
</search>
</field>
</record>
</odoo>
-63
View File
@@ -62,23 +62,6 @@
</field>
</record>
<record id="view_redirect_search" model="ir.ui.view">
<field name="name">website.redirect.search</field>
<field name="model">website.redirect</field>
<field name="arch" type="xml">
<search string="Search Redirect">
<field name="url_from"/>
<field name="url_to"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group string="Group By">
<filter string="Redirection Type" name="group_by_type" domain="[]" context="{'group_by': 'redirect_type'}"/>
</group>
</search>
</field>
</record>
<record id="action_website_list" model="ir.actions.act_window">
<field name="name">Websites</field>
<field name="res_model">website</field>
@@ -87,52 +70,6 @@
<field name="target">current</field>
</record>
<!-- website.redirect views -->
<record id="website_redirect_form_view" model="ir.ui.view">
<field name="name">website.redirect.form</field>
<field name="model">website.redirect</field>
<field name="arch" type="xml">
<form string="Website Redirect Settings">
<sheet>
<group>
<group>
<field name="redirect_type"/>
<field name="url_from"/>
<field name="url_to"/>
</group>
<group>
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
<field name="active"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="website_redirect_tree_view" model="ir.ui.view">
<field name="name">website.redirect.list</field>
<field name="model">website.redirect</field>
<field name="arch" type="xml">
<tree string="Website Redirects" editable="bottom">
<field name="redirect_type"/>
<field name="url_from"/>
<field name="url_to"/>
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
<field name="active"/>
<field name="sequence" widget="handle" />
</tree>
</field>
</record>
<record id="action_website_redirect_list" model="ir.actions.act_window">
<field name="name">Website Redirects</field>
<field name="res_model">website.redirect</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="website_redirect_tree_view"/>
<field name="target">current</field>
<field name="search_view_id" ref="view_redirect_search"/>
</record>
<!-- website.page views -->
<record id="website_pages_form_view" model="ir.ui.view">
+27 -12
View File
@@ -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(
+6 -12
View File
@@ -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