summaryrefslogtreecommitdiff
path: root/addons/website
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/website
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/website')
-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
14 files changed, 259 insertions, 99 deletions
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">