Skip to content

Commit 4c89c9a

Browse files
committed
[MIG] website_user_login_redirect_custom: Migration to 18.0
- Update module version to 18.0.1.0.0 in __manifest__.py - Replace deprecated SavepointCase with TransactionCase in tests - Refactored res_config_settings_views.xml Task: 5058
1 parent 6ffd368 commit 4c89c9a

File tree

8 files changed

+281
-44
lines changed

8 files changed

+281
-44
lines changed

website_user_login_redirect_custom/README.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ Website User Login Redirect Custom
1717
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
1818
:alt: License: AGPL-3
1919
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github
20-
:target: https://github.com/OCA/website/tree/14.0/website_user_login_redirect_custom
20+
:target: https://github.com/OCA/website/tree/18.0/website_user_login_redirect_custom
2121
:alt: OCA/website
2222
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23-
:target: https://translation.odoo-community.org/projects/website-14-0/website-14-0-website_user_login_redirect_custom
23+
:target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_user_login_redirect_custom
2424
:alt: Translate me on Weblate
2525
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26-
:target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=14.0
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0
2727
:alt: Try me on Runboat
2828

2929
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -68,7 +68,7 @@ Bug Tracker
6868
Bugs are tracked on `GitHub Issues <https://github.com/OCA/website/issues>`_.
6969
In case of trouble, please check there if your issue has already been reported.
7070
If you spotted it first, help us to smash it by providing a detailed and welcomed
71-
`feedback <https://github.com/OCA/website/issues/new?body=module:%20website_user_login_redirect_custom%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
71+
`feedback <https://github.com/OCA/website/issues/new?body=module:%20website_user_login_redirect_custom%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
7272

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

@@ -101,6 +101,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
101101
mission is to support the collaborative development of Odoo features and
102102
promote its widespread use.
103103

104-
This module is part of the `OCA/website <https://github.com/OCA/website/tree/14.0/website_user_login_redirect_custom>`_ project on GitHub.
104+
This module is part of the `OCA/website <https://github.com/OCA/website/tree/18.0/website_user_login_redirect_custom>`_ project on GitHub.
105105

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

website_user_login_redirect_custom/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"name": "Website User Login Redirect Custom",
66
"summary": "Redirect website/portal user to custom URL after login or signup",
7-
"version": "14.0.1.0.0",
7+
"version": "18.0.1.0.0",
88
"category": "Website",
99
"author": "Cetmix OÜ, Odoo Community Association (OCA)",
1010
"license": "AGPL-3",

website_user_login_redirect_custom/controllers/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
33

44
from odoo.http import request
5-
from odoo.tools import misc
5+
from odoo.tools import str2bool
66

7-
from odoo.addons.web.controllers.main import Home
7+
from odoo.addons.web.controllers.home import Home
88

99

1010
class WebsiteRedirectCustom(Home):
@@ -27,7 +27,7 @@ def _should_redirect_custom(self):
2727
.sudo()
2828
.get_param("website_user_login_redirect_custom.enabled", "False")
2929
)
30-
return misc.str2bool(enabled)
30+
return str2bool(enabled)
3131

3232
def _get_custom_redirect_url(self):
3333
"""Get custom redirect URL or False if not set/valid"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["whool"]
3+
build-backend = "whool.buildapi"
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright (C) 2025 Cetmix OÜ
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
33

4-
from . import test_website_login_redirect # noqa
4+
from . import test_website_login_redirect
5+
from . import test_website_login_redirect_controller

website_user_login_redirect_custom/tests/test_website_login_redirect.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@tagged("post_install", "-at_install")
9-
class TestWebsiteLoginRedirect(common.SavepointCase):
9+
class TestWebsiteLoginRedirect(common.TransactionCase):
1010
"""Test website login redirect functionality"""
1111

1212
@classmethod
@@ -116,3 +116,28 @@ def test_is_valid_redirect_url_edge_cases(self):
116116

117117
# Test empty string
118118
self.assertFalse(settings.is_valid_redirect_url(""))
119+
120+
def test_check_url_format_skips_empty(self):
121+
"""_check_url_format should skip empty URLs"""
122+
settings = self.env["res.config.settings"].create({})
123+
settings._check_url_format() # Should not raise
124+
125+
def test_is_valid_redirect_url_rejects_external(self):
126+
"""Reject external URLs with scheme or domain"""
127+
settings = self.env["res.config.settings"].create({})
128+
self.assertFalse(settings.is_valid_redirect_url("http://evil.com"))
129+
130+
def test_check_url_format_raises_validation_error(self):
131+
"""Invalid URL triggers ValidationError during create"""
132+
with self.assertRaises(ValidationError):
133+
self.env["res.config.settings"].create(
134+
{"website_login_redirect_url": "javascript:alert(1)"}
135+
)
136+
137+
def test_onchange_strips_url_whitespace(self):
138+
"""Onchange removes leading/trailing spaces in URL"""
139+
settings = self.env["res.config.settings"].create(
140+
{"website_login_redirect_url": " /clean-me "}
141+
)
142+
settings._onchange_website_login_redirect_url()
143+
self.assertEqual(settings.website_login_redirect_url, "/clean-me")
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Copyright (C) 2025 Cetmix OÜ
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from unittest.mock import MagicMock, patch
5+
6+
from odoo.tests import common, tagged
7+
8+
from odoo.addons.website_user_login_redirect_custom.controllers.main import (
9+
WebsiteRedirectCustom,
10+
)
11+
12+
13+
@tagged("post_install", "-at_install")
14+
class TestWebsiteLoginRedirectController(common.TransactionCase):
15+
"""Unit tests for WebsiteRedirectCustom controller"""
16+
17+
@classmethod
18+
def setUpClass(cls):
19+
super().setUpClass()
20+
cls.controller = WebsiteRedirectCustom()
21+
cls.config_parameter = cls.env["ir.config_parameter"].sudo()
22+
23+
cls.portal_user = cls.env["res.users"].create(
24+
{
25+
"name": "Portal User",
26+
"login": "[email protected]",
27+
"groups_id": [(4, cls.env.ref("base.group_portal").id)],
28+
}
29+
)
30+
31+
def _fake_request(self, user, enabled=True, url="/shop"):
32+
"""Create mock request for controller tests"""
33+
mock_req = MagicMock()
34+
mock_req.session.uid = user.id
35+
36+
mock_user = MagicMock()
37+
mock_user.id = user.id
38+
if user.login.startswith("internal"):
39+
mock_user.has_group.side_effect = lambda g: g == "base.group_user"
40+
else:
41+
mock_user.has_group.side_effect = lambda g: g == "base.group_portal"
42+
43+
mock_env = MagicMock()
44+
mock_env.user = mock_user
45+
46+
def getitem(name):
47+
if name == "ir.config_parameter":
48+
ir_mock = MagicMock()
49+
ir_mock.sudo.return_value = ir_mock
50+
ir_mock.get_param.side_effect = lambda key, default=None: {
51+
"website_user_login_redirect_custom.enabled": "True"
52+
if enabled
53+
else "False",
54+
"website_user_login_redirect_custom.url": url,
55+
}.get(key, default)
56+
return ir_mock
57+
if name == "res.config.settings":
58+
settings_mock = MagicMock()
59+
settings_mock.is_valid_redirect_url.side_effect = (
60+
lambda value: value
61+
and value.startswith("/")
62+
and not value.startswith("//")
63+
)
64+
return settings_mock
65+
raise KeyError(name)
66+
67+
mock_env.__getitem__.side_effect = getitem
68+
mock_req.env = mock_env
69+
return mock_req
70+
71+
def test_should_redirect_custom_portal_user_enabled(self):
72+
"""Portal user with feature enabled should redirect"""
73+
mock_request = self._fake_request(self.portal_user, enabled=True)
74+
with patch(
75+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
76+
mock_request,
77+
):
78+
self.assertTrue(self.controller._should_redirect_custom())
79+
80+
def test_should_redirect_custom_internal_user(self):
81+
"""Internal users should not redirect"""
82+
internal_user = self.env["res.users"].create(
83+
{
84+
"name": "Internal User",
85+
"login": "[email protected]",
86+
"groups_id": [(4, self.env.ref("base.group_user").id)],
87+
}
88+
)
89+
mock_request = self._fake_request(internal_user, enabled=True)
90+
91+
with patch(
92+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
93+
mock_request,
94+
):
95+
result = self.controller._should_redirect_custom()
96+
self.assertFalse(result)
97+
98+
def test_get_custom_redirect_url(self):
99+
"""Test custom URL validation and retrieval"""
100+
# Valid URL
101+
mock_request = self._fake_request(self.portal_user, url="/shop")
102+
with patch(
103+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
104+
mock_request,
105+
):
106+
self.assertEqual(self.controller._get_custom_redirect_url(), "/shop")
107+
108+
# Invalid URL
109+
mock_request = self._fake_request(self.portal_user, url="http://evil.com")
110+
with patch(
111+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
112+
mock_request,
113+
):
114+
self.assertFalse(self.controller._get_custom_redirect_url())
115+
116+
def test_should_redirect_custom_disabled(self):
117+
"""Feature disabled → no redirect should occur"""
118+
mock_request = self._fake_request(self.portal_user, enabled=False)
119+
with patch(
120+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
121+
mock_request,
122+
):
123+
self.assertFalse(self.controller._should_redirect_custom())
124+
125+
def test_login_redirect_behavior(self):
126+
"""Verify custom redirect applies when feature enabled"""
127+
mock_request = self._fake_request(
128+
self.portal_user, enabled=True, url="/thank-you"
129+
)
130+
with (
131+
patch(
132+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
133+
mock_request,
134+
),
135+
patch(
136+
"odoo.addons.web.controllers.home.Home._login_redirect",
137+
return_value="/my",
138+
),
139+
):
140+
result = self.controller._login_redirect(self.portal_user.id)
141+
self.assertEqual(result, "/thank-you")
142+
143+
def test_should_redirect_custom_no_session(self):
144+
"""Should not redirect if user session missing"""
145+
mock_request = MagicMock()
146+
mock_request.session.uid = None
147+
mock_request.env.user.has_group.return_value = False
148+
with patch(
149+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
150+
mock_request,
151+
):
152+
self.assertFalse(self.controller._should_redirect_custom())
153+
154+
def test_get_custom_redirect_url_empty(self):
155+
"""Should return False if URL param is empty"""
156+
mock_request = MagicMock()
157+
mock_request.env[
158+
"ir.config_parameter"
159+
].sudo.return_value.get_param.return_value = ""
160+
mock_request.env[
161+
"res.config.settings"
162+
].is_valid_redirect_url.return_value = True
163+
with patch(
164+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
165+
mock_request,
166+
):
167+
self.assertFalse(self.controller._get_custom_redirect_url())
168+
169+
def test_login_redirect_returns_parent_redirect_when_disabled(self):
170+
"""Return parent redirect when feature disabled"""
171+
mock_request = self._fake_request(self.portal_user, enabled=False, url="/shop")
172+
with (
173+
patch(
174+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
175+
mock_request,
176+
),
177+
patch(
178+
"odoo.addons.web.controllers.home.Home._login_redirect",
179+
return_value="/custom",
180+
),
181+
):
182+
result = self.controller._login_redirect(self.portal_user.id)
183+
self.assertEqual(result, "/custom")
184+
185+
def test_login_redirect_applies_custom_url_on_my_path(self):
186+
"""Custom redirect applies when parent_redirect is /my"""
187+
mock_request = self._fake_request(
188+
self.portal_user, enabled=True, url="/special"
189+
)
190+
with (
191+
patch(
192+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
193+
mock_request,
194+
),
195+
patch(
196+
"odoo.addons.web.controllers.home.Home._login_redirect",
197+
return_value="/my",
198+
),
199+
):
200+
result = self.controller._login_redirect(self.portal_user.id)
201+
self.assertEqual(result, "/special")
202+
203+
def test_login_redirect_returns_parent_when_no_custom_url(self):
204+
"""Return parent redirect if no valid custom URL found"""
205+
mock_request = self._fake_request(self.portal_user, enabled=True, url=None)
206+
with (
207+
patch(
208+
"odoo.addons.website_user_login_redirect_custom.controllers.main.request",
209+
mock_request,
210+
),
211+
patch(
212+
"odoo.addons.web.controllers.home.Home._login_redirect",
213+
return_value="/my",
214+
),
215+
):
216+
result = self.controller._login_redirect(self.portal_user.id)
217+
self.assertEqual(result, "/my")

website_user_login_redirect_custom/views/res_config_settings_views.xml

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,34 @@
77
<field name="model">res.config.settings</field>
88
<field name="inherit_id" ref="website.res_config_settings_view_form" />
99
<field name="arch" type="xml">
10-
<xpath expr="//div[@id='google_maps_setting']" position="before">
11-
<div class="col-12 col-lg-6 o_setting_box" id="login_redirect_setting">
12-
<div class="o_setting_left_pane">
13-
<field name="website_login_redirect_enabled" />
14-
</div>
15-
<div class="o_setting_right_pane">
16-
<label
17-
for="website_login_redirect_enabled"
18-
string="Redirect on Login/Signup​"
19-
/>
20-
<div class="text-muted">
21-
Redirect users to a custom page after authentication.
22-
</div>
23-
<div
24-
class="content-group"
25-
attrs="{'invisible': [('website_login_redirect_enabled', '=', False)]}"
26-
>
27-
<div class="row mt16">
28-
<label
29-
class="col-3 o_light_label"
30-
string="Custom URL"
31-
for="website_login_redirect_url"
10+
<xpath expr="//block[@id='website_settings']" position="inside">
11+
<setting
12+
id="website_login_redirect_setting"
13+
string="Redirect on Login/Signup"
14+
help="Redirect users to a custom page after they log in or sign up."
15+
>
16+
<field name="website_login_redirect_enabled" />
17+
18+
<toggle field="website_login_redirect_enabled">
19+
<div class="row mt8">
20+
<label
21+
class="col-lg-3 o_light_label"
22+
string="Custom URL"
23+
for="website_login_redirect_url"
24+
/>
25+
<div class="col-lg-9">
26+
<field
27+
name="website_login_redirect_url"
28+
class="w-100"
3229
/>
33-
<div class="col-9">
34-
<field
35-
name="website_login_redirect_url"
36-
class="w-100"
37-
/>
38-
<div class="text-muted mt-1">
39-
Relative path, e.g. <code>/shop</code> or <code
40-
>/welcome</code>.
41-
</div>
30+
<div class="text-muted mt-1">
31+
Relative path, e.g. <code>/shop</code> or <code
32+
>/welcome</code>.
4233
</div>
4334
</div>
4435
</div>
45-
</div>
46-
</div>
36+
</toggle>
37+
</setting>
4738
</xpath>
4839
</field>
4940
</record>

0 commit comments

Comments
 (0)