[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:
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -4,7 +4,6 @@ import base64
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import pytz
|
||||
import requests
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
@@ -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,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
|
||||
|
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user