From bb5af8852691abfc310fdbf3a8bdb0d859fbce96 Mon Sep 17 00:00:00 2001 From: Alexey Pelykh Date: Thu, 28 Nov 2024 09:37:02 +0100 Subject: [PATCH] [ADD] website_page_redirect --- setup/_metapackage/pyproject.toml | 1 + website_page_redirect/README.rst | 98 ++++ website_page_redirect/__init__.py | 4 + website_page_redirect/__manifest__.py | 21 + website_page_redirect/models/__init__.py | 5 + website_page_redirect/models/ir_http.py | 48 ++ website_page_redirect/models/website_page.py | 67 +++ website_page_redirect/pyproject.toml | 3 + website_page_redirect/readme/CONFIGURE.md | 8 + website_page_redirect/readme/CONTRIBUTORS.md | 4 + website_page_redirect/readme/DESCRIPTION.md | 1 + .../static/description/index.html | 441 ++++++++++++++++++ website_page_redirect/tests/__init__.py | 1 + website_page_redirect/tests/test_ir_http.py | 297 ++++++++++++ .../views/website_layout.xml | 27 ++ website_page_redirect/views/website_page.xml | 56 +++ 16 files changed, 1082 insertions(+) create mode 100644 website_page_redirect/README.rst create mode 100644 website_page_redirect/__init__.py create mode 100644 website_page_redirect/__manifest__.py create mode 100644 website_page_redirect/models/__init__.py create mode 100644 website_page_redirect/models/ir_http.py create mode 100644 website_page_redirect/models/website_page.py create mode 100644 website_page_redirect/pyproject.toml create mode 100644 website_page_redirect/readme/CONFIGURE.md create mode 100644 website_page_redirect/readme/CONTRIBUTORS.md create mode 100644 website_page_redirect/readme/DESCRIPTION.md create mode 100644 website_page_redirect/static/description/index.html create mode 100644 website_page_redirect/tests/__init__.py create mode 100644 website_page_redirect/tests/test_ir_http.py create mode 100644 website_page_redirect/views/website_layout.xml create mode 100644 website_page_redirect/views/website_page.xml diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 1128aa40ae..e14d4b321e 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "odoo-addon-website_forum_subscription>=17.0dev,<17.1dev", "odoo-addon-website_google_tag_manager>=17.0dev,<17.1dev", "odoo-addon-website_odoo_debranding>=17.0dev,<17.1dev", + "odoo-addon-website_page_redirect>=17.0dev,<17.1dev", "odoo-addon-website_require_login>=17.0dev,<17.1dev", ] classifiers=[ diff --git a/website_page_redirect/README.rst b/website_page_redirect/README.rst new file mode 100644 index 0000000000..a5c01e9594 --- /dev/null +++ b/website_page_redirect/README.rst @@ -0,0 +1,98 @@ +===================== +Website Page Redirect +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3eaca071ad228aab0540926070c564d408d2868cd41e0dc43a59d67aba5e5e68 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github + :target: https://github.com/OCA/website/tree/17.0/website_page_redirect + :alt: OCA/website +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/website-17-0/website-17-0-website_page_redirect + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to configure an redirect for specific website pages. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure a redirect for the page, you need to: + +1. Go to *Website > Site > Pages* +2. Open the page of interest +3. Check the *Redirect* checkbox +4. Configure the *Redirect URL* field +5. Configure the *Redirect Method* and related fields +6. Save the page + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* CorporateHub + +Contributors +------------ + +- ``CorporateHub ``\ \_\_ + + - Alexey Pelykh alexey.pelykh@corphub.eu + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-alexey-pelykh| image:: https://github.com/alexey-pelykh.png?size=40px + :target: https://github.com/alexey-pelykh + :alt: alexey-pelykh + +Current `maintainer `__: + +|maintainer-alexey-pelykh| + +This module is part of the `OCA/website `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_page_redirect/__init__.py b/website_page_redirect/__init__.py new file mode 100644 index 0000000000..a5056b1c64 --- /dev/null +++ b/website_page_redirect/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/website_page_redirect/__manifest__.py b/website_page_redirect/__manifest__.py new file mode 100644 index 0000000000..0553cbf200 --- /dev/null +++ b/website_page_redirect/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Website Page Redirect", + "summary": "Redirect page to another URL", + "category": "Website", + "version": "17.0.1.0.0", + "author": "CorporateHub, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/website", + "license": "AGPL-3", + "depends": [ + "website", + ], + "data": [ + "views/website_layout.xml", + "views/website_page.xml", + ], + "installable": True, + "maintainers": ["alexey-pelykh"], +} diff --git a/website_page_redirect/models/__init__.py b/website_page_redirect/models/__init__.py new file mode 100644 index 0000000000..193a34e282 --- /dev/null +++ b/website_page_redirect/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_http +from . import website_page diff --git a/website_page_redirect/models/ir_http.py b/website_page_redirect/models/ir_http.py new file mode 100644 index 0000000000..9d7e22009a --- /dev/null +++ b/website_page_redirect/models/ir_http.py @@ -0,0 +1,48 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import http, models + +logger = logging.getLogger(__name__) + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + @classmethod + def _serve_page(cls): + response = super()._serve_page() + + if not response and getattr(response, "status_code", 0) != 200: + return response + + if ( + http.request.db + and http.request.session.uid + and http.request.env.user.has_group("website.group_website_designer") + ): + return response + + page = ( + http.request.env["website.page"] + .sudo() + .search( + [("url", "=", http.request.httprequest.path)], + order="website_id asc", + limit=1, + ) + ) + if not page: # pragma: no cover + logger.error("Served page found for URL %s", http.request.httprequest.path) + return response + + if not page.is_redirect or page.redirect_method != "http": + return response + + return http.request.redirect( + page.redirect_url, + code=int(page.redirect_http_code) if page.redirect_http_code else 301, + local=not page.redirect_url.lower().startswith("http"), + ) diff --git a/website_page_redirect/models/website_page.py b/website_page_redirect/models/website_page.py new file mode 100644 index 0000000000..b6195dd35a --- /dev/null +++ b/website_page_redirect/models/website_page.py @@ -0,0 +1,67 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class WebsitePage(models.Model): + _inherit = "website.page" + + is_redirect = fields.Boolean( + string="Redirect", + help="If checked, this page will redirect to another URL.", + ) + redirect_url = fields.Char( + string="Redirect URL", + help="URL to redirect to when this page is accessed.", + ) + redirect_method = fields.Selection( + selection=[ + ("http", "HTTP"), + ("meta", "Meta Refresh"), + ("js-href", "JavaScript HREF"), + ("js-replace", "JavaScript Replace"), + ], + default="http", + help="Method to use for the redirect.", + ) + redirect_http_code = fields.Selection( + selection=[ + ("301", "301 Moved Permanently"), + ("302", "302 Found"), + ("303", "303 See Other"), + ("307", "307 Temporary Redirect"), + ("308", "308 Permanent Redirect"), + ], + string="Redirect HTTP Code", + default="301", + help="HTTP status code to use for the redirect.", + ) + redirect_delay = fields.Integer( + default=0, + help=( + "Delay before redirect (in seconds) for Meta-Refresh and JavaScript" + " redirect methods." + ), + ) + redirect_js_code = fields.Html( + compute="_compute_redirect_js_code", + sanitize=False, + string="Redirect JavaScript Code", + ) + + @api.depends("redirect_url", "redirect_delay", "redirect_method") + def _compute_redirect_js_code(self): + for page in self: + if page.redirect_method not in ("js-href", "js-replace"): + page.redirect_js_code = "" + continue + if page.redirect_method == "js-href": + function_body = f"window.location.href = '{page.redirect_url}';" + elif page.redirect_method == "js-replace": + function_body = f"window.location.replace('{page.redirect_url}');" + page.redirect_js_code = ( + '" + ) diff --git a/website_page_redirect/pyproject.toml b/website_page_redirect/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/website_page_redirect/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/website_page_redirect/readme/CONFIGURE.md b/website_page_redirect/readme/CONFIGURE.md new file mode 100644 index 0000000000..57d0da2ef6 --- /dev/null +++ b/website_page_redirect/readme/CONFIGURE.md @@ -0,0 +1,8 @@ +To configure a redirect for the page, you need to: + +1. Go to *Website \> Site \> Pages* +2. Open the page of interest +3. Check the *Redirect* checkbox +4. Configure the *Redirect URL* field +5. Configure the *Redirect Method* and related fields +6. Save the page diff --git a/website_page_redirect/readme/CONTRIBUTORS.md b/website_page_redirect/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..6d2e32234f --- /dev/null +++ b/website_page_redirect/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +* `CorporateHub `__ + + * Alexey Pelykh + diff --git a/website_page_redirect/readme/DESCRIPTION.md b/website_page_redirect/readme/DESCRIPTION.md new file mode 100644 index 0000000000..a17b3ec4ef --- /dev/null +++ b/website_page_redirect/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module allows to configure an redirect for specific website pages. diff --git a/website_page_redirect/static/description/index.html b/website_page_redirect/static/description/index.html new file mode 100644 index 0000000000..6175d3ec13 --- /dev/null +++ b/website_page_redirect/static/description/index.html @@ -0,0 +1,441 @@ + + + + + +Website Page Redirect + + + +
+

Website Page Redirect

+ + +

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

This module allows to configure an redirect for specific website pages.

+

Table of contents

+ +
+

Configuration

+

To configure a redirect for the page, you need to:

+
    +
  1. Go to Website > Site > Pages
  2. +
  3. Open the page of interest
  4. +
  5. Check the Redirect checkbox
  6. +
  7. Configure the Redirect URL field
  8. +
  9. Configure the Redirect Method and related fields
  10. +
  11. Save the page
  12. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • CorporateHub
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

alexey-pelykh

+

This module is part of the OCA/website project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/website_page_redirect/tests/__init__.py b/website_page_redirect/tests/__init__.py new file mode 100644 index 0000000000..e2983aa2a4 --- /dev/null +++ b/website_page_redirect/tests/__init__.py @@ -0,0 +1 @@ +from . import test_ir_http diff --git a/website_page_redirect/tests/test_ir_http.py b/website_page_redirect/tests/test_ir_http.py new file mode 100644 index 0000000000..c9c6bb4dbb --- /dev/null +++ b/website_page_redirect/tests/test_ir_http.py @@ -0,0 +1,297 @@ +# Copyright 2024 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import http +from odoo.tests import HOST, HttpCase, Opener, get_db_name, new_test_user + + +class TestIrHttp(HttpCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.website = cls.env["website"].sudo().get_current_website() + cls.website_designer = new_test_user( + cls.env, + "website_designer", + groups="base.group_user,website.group_website_designer", + ) + + def setUp(self): + super().setUp() + self.session = http.root.session_store.new() + self.session.update(http.get_default_session(), db=get_db_name()) + self.opener = Opener(self.env.cr) + self.opener.cookies.set("session_id", self.session.sid, domain=HOST, path="/") + + def test_404(self): + redirect_response = self.url_open( + "/non-existing-page", + allow_redirects=False, + ) + self.assertEqual(redirect_response.status_code, 404) + + def test_http_redirect(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "http-redirect", + "url": "/http-redirect", + "type": "qweb", + "arch": "http-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "http", + "redirect_http_code": "301", + "redirect_url": "https://corporatehub.eu", + } + ) + + redirect_response = self.url_open( + http_redirect_page.url, + allow_redirects=False, + ) + + self.assertEqual(redirect_response.status_code, 301) + self.assertEqual( + "https://corporatehub.eu", + redirect_response.headers["Location"], + ) + + def test_no_http_redirect_for_website_designer(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "http-redirect", + "url": "/http-redirect", + "type": "qweb", + "arch": "http-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "http", + "redirect_http_code": "301", + "redirect_url": "https://corporatehub.eu", + } + ) + + login_response = self.url_open( + "/web/login", + data={ + "login": self.website_designer.login, + "password": self.website_designer.login, + "csrf_token": http.Request.csrf_token(self), + }, + ) + login_response.raise_for_status() + + redirect_response = self.url_open( + http_redirect_page.url, + allow_redirects=False, + ) + + self.assertEqual(redirect_response.status_code, 200) + + def test_meta_redirect(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "meta-redirect", + "url": "/meta-redirect", + "type": "qweb", + "arch": "meta-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "meta", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertIn( + ( + "' + ), + redirect_response.content.decode("utf-8"), + ) + + def test_no_meta_redirect_for_website_designer(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "meta-redirect", + "url": "/meta-redirect", + "type": "qweb", + "arch": "meta-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "meta", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + login_response = self.url_open( + "/web/login", + data={ + "login": self.website_designer.login, + "password": self.website_designer.login, + "csrf_token": http.Request.csrf_token(self), + }, + ) + login_response.raise_for_status() + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertNotIn( + ( + "' + ), + redirect_response.content.decode("utf-8"), + ) + + def test_js_href_redirect(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "js-href-redirect", + "url": "/js-href-redirect", + "type": "qweb", + "arch": "js-href-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "js-href", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertIn( + ( + "setTimeout(\n" + " function() {" + " window.location.href = 'https://corporatehub.eu'; },\n" + " 5000,\n" + ");" + ), + redirect_response.content.decode("utf-8"), + ) + + def test_no_js_href_redirect_for_website_designer(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "js-href-redirect", + "url": "/js-href-redirect", + "type": "qweb", + "arch": "js-href-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "js-href", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + login_response = self.url_open( + "/web/login", + data={ + "login": self.website_designer.login, + "password": self.website_designer.login, + "csrf_token": http.Request.csrf_token(self), + }, + ) + login_response.raise_for_status() + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertNotIn( + ( + "setTimeout(\n" + " function() {" + " window.location.href = 'https://corporatehub.eu'; },\n" + " 5000,\n" + ");" + ), + redirect_response.content.decode("utf-8"), + ) + + def test_js_replace_redirect(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "js-replace-redirect", + "url": "/js-replace-redirect", + "type": "qweb", + "arch": "js-replace-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "js-replace", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertIn( + ( + "setTimeout(\n" + " function() {" + " window.location.replace('https://corporatehub.eu'); },\n" + " 5000,\n" + ");" + ), + redirect_response.content.decode("utf-8"), + ) + + def test_no_js_replace_redirect_for_website_designer(self): + http_redirect_page = self.env["website.page"].create( + { + "website_id": self.website.id, + "name": "js-replace-redirect", + "url": "/js-replace-redirect", + "type": "qweb", + "arch": "js-replace-redirect", + "is_published": True, + "is_redirect": True, + "redirect_method": "js-replace", + "redirect_delay": 5, + "redirect_url": "https://corporatehub.eu", + } + ) + + login_response = self.url_open( + "/web/login", + data={ + "login": self.website_designer.login, + "password": self.website_designer.login, + "csrf_token": http.Request.csrf_token(self), + }, + ) + login_response.raise_for_status() + + redirect_response = self.url_open(http_redirect_page.url) + + self.assertEqual(redirect_response.status_code, 200) + self.assertNotIn( + ( + "setTimeout(\n" + " function() {" + " window.location.replace('https://corporatehub.eu'); },\n" + " 5000,\n" + ");" + ), + redirect_response.content.decode("utf-8"), + ) diff --git a/website_page_redirect/views/website_layout.xml b/website_page_redirect/views/website_layout.xml new file mode 100644 index 0000000000..5286b36202 --- /dev/null +++ b/website_page_redirect/views/website_layout.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/website_page_redirect/views/website_page.xml b/website_page_redirect/views/website_page.xml new file mode 100644 index 0000000000..f95be11862 --- /dev/null +++ b/website_page_redirect/views/website_page.xml @@ -0,0 +1,56 @@ + + + + + + website.page.form + website.page + + + + + + + + + + + + + + + + website.page.properties.form.view + website.page + + + + + + + + + + + + + + + + +