summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorJeremy Kersten <jke@odoo.com>2019-09-12 09:45:11 +0000
committerJeremy Kersten <jke@odoo.com>2019-09-30 13:58:14 +0000
commitbe8fc2296b3822baffe0bc68cb4b7efdfe53fa91 (patch)
tree08f0f825cfe2ffa652557f244f36880149d2f585 /addons
parentb6b42ef73f4c517d9b7bc2da4909c13d7fe8f2c6 (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')
-rw-r--r--addons/http_routing/models/ir_http.py77
-rw-r--r--addons/website/__manifest__.py1
-rw-r--r--addons/website/controllers/main.py1
-rw-r--r--addons/website/models/__init__.py2
-rw-r--r--addons/website/models/ir_http.py66
-rw-r--r--addons/website/models/website.py1
-rw-r--r--addons/website/models/website_page.py2
-rw-r--r--addons/website/models/website_redirect.py18
-rw-r--r--addons/website/models/website_rewrite.py104
-rw-r--r--addons/website/security/ir.model.access.csv5
-rw-r--r--addons/website/static/src/js/menu/content.js2
-rw-r--r--addons/website/tests/test_lang_url.py6
-rw-r--r--addons/website/views/res_config_settings_views.xml7
-rw-r--r--addons/website/views/website_rewrite.xml80
-rw-r--r--addons/website/views/website_views.xml63
15 files changed, 316 insertions, 119 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.
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
@@ -62,6 +64,58 @@ 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
match Rule. This override adds the website ones.
@@ -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"
/>
- <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"
diff --git a/addons/website/views/website_rewrite.xml b/addons/website/views/website_rewrite.xml
new file mode 100644
index 00000000000..e41111c3bf1
--- /dev/null
+++ b/addons/website/views/website_rewrite.xml
@@ -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>
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 @@
</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">