diff options
| author | Jeremy Kersten <jke@odoo.com> | 2019-09-12 09:45:11 +0000 |
|---|---|---|
| committer | Jeremy Kersten <jke@odoo.com> | 2019-09-30 13:58:14 +0000 |
| commit | be8fc2296b3822baffe0bc68cb4b7efdfe53fa91 (patch) | |
| tree | 08f0f825cfe2ffa652557f244f36880149d2f585 /addons/website | |
| parent | b6b42ef73f4c517d9b7bc2da4909c13d7fe8f2c6 (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__.py | 1 | ||||
| -rw-r--r-- | addons/website/controllers/main.py | 1 | ||||
| -rw-r--r-- | addons/website/models/__init__.py | 2 | ||||
| -rw-r--r-- | addons/website/models/ir_http.py | 66 | ||||
| -rw-r--r-- | addons/website/models/website.py | 1 | ||||
| -rw-r--r-- | addons/website/models/website_page.py | 2 | ||||
| -rw-r--r-- | addons/website/models/website_redirect.py | 18 | ||||
| -rw-r--r-- | addons/website/models/website_rewrite.py | 104 | ||||
| -rw-r--r-- | addons/website/security/ir.model.access.csv | 5 | ||||
| -rw-r--r-- | addons/website/static/src/js/menu/content.js | 2 | ||||
| -rw-r--r-- | addons/website/tests/test_lang_url.py | 6 | ||||
| -rw-r--r-- | addons/website/views/res_config_settings_views.xml | 7 | ||||
| -rw-r--r-- | addons/website/views/website_rewrite.xml | 80 | ||||
| -rw-r--r-- | addons/website/views/website_views.xml | 63 |
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"> |
