[ADD] website_sale_aplicoop: serve Eskaera per website

A co-op can run the plain shop on one website and the group orders on another,
but routes are registered process-wide: every /eskaera page answered on every
website of the database, and Odoo had already copied the Eskaera menu to all of
them.

`website.eskaera_enabled` decides which websites serve it, on by default so
installing changes nothing. It reaches the settings screen through `website_id`,
so it follows the website selector there. Where it is off the routes raise
NotFound and the menu is hidden.

The menu is hidden rather than deleted, by extending `_compute_visible`. That
keeps the record and any manual rename or reordering, so switching the feature
back on restores it as it was.

Note that a JSON route reports the 404 inside the JSON-RPC payload and still
answers HTTP 200; that is the transport, not a hole in the guard, and a test
pins it so the next reader does not take it for one.

The three remaining settings (lazy loading, products per page, low stock
threshold) are still `config_parameter`, so they stay global to the database.
Two websites share their values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-17 17:31:03 +02:00
parent 817ff31d39
commit 5a2f5d120f
9 changed files with 313 additions and 0 deletions

View file

@ -0,0 +1,208 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""Eskaera is served per website, not database-wide.
A co-op can run the plain shop on one website and the group orders on another.
`website.eskaera_enabled` decides which is which: where it is off the /eskaera
routes answer 404 and the Eskaera menu is hidden.
"""
from datetime import datetime
from datetime import timedelta
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.tests.common import TransactionCase
class EskaeraWebsiteCommon:
def setUp(self):
super().setUp()
self.website = self.env.ref("website.default_website")
def _eskaera_menus(self, website):
return self.env["website.menu"].search(
[("url", "=like", "/eskaera%"), ("website_id", "=", website.id)]
)
@tagged("post_install", "-at_install")
class TestEskaeraEnabledField(EskaeraWebsiteCommon, TransactionCase):
"""The switch itself, and what it does to the menu."""
def test_enabled_by_default(self):
"""Installing the addon leaves every website serving Eskaera."""
self.assertTrue(self.website.eskaera_enabled)
self.assertTrue(
all(website.eskaera_enabled for website in self.env["website"].search([]))
)
def test_new_website_serves_eskaera(self):
"""A website created later also starts with Eskaera on."""
new_website = self.env["website"].create({"name": "Brand New Site"})
self.assertTrue(new_website.eskaera_enabled)
def test_menu_is_visible_while_enabled(self):
"""The Eskaera menu shows on a website that serves it."""
menus = self._eskaera_menus(self.website)
self.assertTrue(menus, "the website should have an Eskaera menu")
self.assertTrue(all(menu.is_visible for menu in menus))
def test_menu_is_hidden_when_disabled(self):
"""Switching Eskaera off hides its menu on that website only."""
other_website = self.env["website"].create({"name": "Shop Only Site"})
self.website.eskaera_enabled = False
self.env["website.menu"].invalidate_model(["is_visible"])
self.assertFalse(any(m.is_visible for m in self._eskaera_menus(self.website)))
# The other website is untouched.
other_menus = self._eskaera_menus(other_website)
if other_menus:
self.assertTrue(all(menu.is_visible for menu in other_menus))
def test_menu_is_kept_not_deleted(self):
"""Turning the feature back on restores the menu as it was."""
menus = self._eskaera_menus(self.website)
menu_ids = menus.ids
self.website.eskaera_enabled = False
self.website.eskaera_enabled = True
self.env["website.menu"].invalidate_model(["is_visible"])
restored = self._eskaera_menus(self.website)
self.assertEqual(restored.ids, menu_ids)
self.assertTrue(all(menu.is_visible for menu in restored))
def test_other_menus_are_left_alone(self):
"""The override must not touch menus that are not Eskaera's."""
self.website.eskaera_enabled = False
self.env["website.menu"].invalidate_model(["is_visible"])
home = self.env["website.menu"].search(
[("url", "=", "/"), ("website_id", "=", self.website.id)], limit=1
)
if home:
self.assertTrue(home.is_visible)
def test_setting_is_exposed_per_website(self):
"""The switch reaches the settings screen through website_id."""
settings = self.env["res.config.settings"].create(
{"website_id": self.website.id}
)
self.assertTrue(settings.eskaera_enabled)
settings.eskaera_enabled = False
settings.execute()
self.assertFalse(self.website.eskaera_enabled)
@tagged("post_install", "-at_install")
class TestEskaeraRoutesPerWebsite(EskaeraWebsiteCommon, HttpCase):
"""The /eskaera routes answer only where the feature is on."""
def setUp(self):
super().setUp()
self.group = self.env["res.partner"].create(
{
"name": "Enabled Test Group",
"is_company": True,
"is_group": True,
}
)
self.member_partner = self.env["res.partner"].create(
{
"name": "Enabled Test Member",
"group_ids": [(6, 0, [self.group.id])],
}
)
self.login = "enabled.member@test.com"
self.env["res.users"].create(
{
"name": "Enabled Test Member",
"login": self.login,
"password": self.login,
"partner_id": self.member_partner.id,
"groups_id": [(4, self.env.ref("base.group_portal").id)],
}
)
start_date = datetime.now().date()
self.group_order = self.env["group.order"].create(
{
"name": "Enabled Test Order",
"group_ids": [(6, 0, [self.group.id])],
"type": "regular",
"start_date": start_date,
"end_date": start_date + timedelta(days=7),
"period": "weekly",
"pickup_day": "3",
"cutoff_day": "0",
}
)
self.group_order.action_open()
def _routes(self):
return [
"/eskaera",
f"/eskaera/{self.group_order.slug}",
f"/eskaera/{self.group_order.slug}/checkout",
]
def test_routes_answer_when_enabled(self):
"""With the feature on, the pages render."""
self.authenticate(self.login, self.login)
for route in self._routes():
response = self.url_open(route, allow_redirects=True)
self.assertEqual(response.status_code, 200, msg=f"{route} should be served")
def test_routes_are_404_when_disabled(self):
"""With the feature off, the pages are gone from that website."""
self.website.eskaera_enabled = False
self.authenticate(self.login, self.login)
for route in self._routes():
response = self.url_open(route, allow_redirects=True)
self.assertEqual(
response.status_code, 404, msg=f"{route} should not be served"
)
def _call_labels(self):
return self.url_open(
"/eskaera/labels",
data=b'{"jsonrpc": "2.0", "method": "call", "params": {}}',
headers={"Content-Type": "application/json"},
).json()
def test_json_endpoint_answers_when_enabled(self):
"""With the feature on, the labels endpoint returns its dictionary."""
self.authenticate(self.login, self.login)
payload = self._call_labels()
self.assertIn("result", payload)
self.assertIn("total", payload["result"])
def test_json_endpoint_is_gated_when_disabled(self):
"""The JSON endpoints are gated too, not just the pages.
A JSON route reports the 404 inside the payload and still answers
HTTP 200: that is how Odoo's JSON-RPC transport surfaces exceptions.
"""
self.website.eskaera_enabled = False
self.authenticate(self.login, self.login)
payload = self._call_labels()
self.assertNotIn("result", payload)
self.assertEqual(payload["error"]["code"], 404)
self.assertEqual(
payload["error"]["data"]["name"], "werkzeug.exceptions.NotFound"
)