addons-cm/website_sale_disable_cart/controllers/website_sale.py
GitHub Copilot f2194c5367 [ADD] website_sale_disable_cart: extract cart restriction from website_sale_aplicoop
The "shop as a read-only catalog" behaviour lived inside website_sale_aplicoop,
so every site that wanted the eskaera flow also lost the standard cart. It now
ships as its own installable addon, with the redirect target configurable
instead of hardcoded to /eskaera.

- website_sale_disable_cart: hides the cart UI (12 header styles + the product
  card quick-add) and overrides the standard cart endpoints. Redirect URL is
  configurable in Website settings (default /shop); only site-internal paths are
  accepted, so a misconfigured value cannot turn the shop into an open redirect
  nor loop back into a disabled route.
- Fixes carried over from the original code: /shop/cart/quantity is the Odoo 18
  path (it was /shop/cart_quantity, which never matched), the boxed, sidebar and
  sales two/three/four headers were not covered (the cart link stayed visible on
  them), and the routes now override the standard methods instead of registering
  duplicate ones.
- website_sale_aplicoop 18.0.1.12.0: drops the view file and the four redirect
  routes; installing it no longer touches the standard shop.

Upgrade order matters: update website_sale_aplicoop first, then install
website_sale_disable_cart in a second Odoo run — both use the same XPaths and
obsolete records are only cleaned up at the end of a run.

Tests: 8/8 in website_sale_disable_cart, aplicoop unaffected (its 2 failures
predate this change). Verified live on a DB clone: /shop/cart returns 303 to the
configured URL and no cart markup remains on /shop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 11:34:59 +02:00

102 lines
3.3 KiB
Python

# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import logging
from odoo import http
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
_logger = logging.getLogger(__name__)
DEFAULT_REDIRECT_URL = "/shop"
REDIRECT_URL_PARAM = "website_sale_disable_cart.redirect_url"
class WebsiteSaleDisableCart(WebsiteSale):
"""Neutralize the standard ``website_sale`` cart endpoints.
The shop keeps working as a plain catalog (listing, search and product
pages are untouched); only the cart itself becomes unreachable. HTTP
routes redirect to the configured URL, JSON routes answer with an inert
payload so any leftover frontend call is a no-op instead of an error.
"""
def _get_cart_redirect_url(self):
"""Return the internal path the disabled cart routes redirect to."""
url = (
request.env["ir.config_parameter"]
.sudo()
.get_param(REDIRECT_URL_PARAM, DEFAULT_REDIRECT_URL)
)
url = (url or "").strip()
# Only site-internal paths are accepted: an absolute or protocol
# relative URL would turn the shop into an open redirect, and a path
# back under /shop/cart would loop through the disabled routes.
if (
not url.startswith("/")
or url.startswith("//")
or url.startswith("/shop/cart")
):
_logger.warning(
"[DISABLE_CART] Invalid redirect URL %r, falling back to %s",
url,
DEFAULT_REDIRECT_URL,
)
return DEFAULT_REDIRECT_URL
return url
def _redirect_disabled_cart(self, route):
"""Redirect a disabled cart route to the configured URL."""
url = self._get_cart_redirect_url()
_logger.info("[DISABLE_CART] %s%s", route, url)
return request.redirect(url)
@http.route()
def cart(self, access_token=None, revive="", **post):
"""Cart page is disabled: send the visitor to the configured URL."""
return self._redirect_disabled_cart("/shop/cart")
@http.route()
def cart_update(
self,
product_id=None,
add_qty=1,
set_qty=0,
product_custom_attribute_values=None,
no_variant_attribute_value_ids=None,
**kwargs,
):
"""Adding to cart is disabled: nothing is written, just redirect."""
return self._redirect_disabled_cart("/shop/cart/update")
@http.route()
def cart_update_json(
self,
product_id=None,
line_id=None,
add_qty=None,
set_qty=None,
display=True,
product_custom_attribute_values=None,
no_variant_attribute_value_ids=None,
**kwargs,
):
"""Adding to cart is disabled.
An empty dict is the response ``website_sale`` already returns when the
order cannot be updated, so callers handle it without breaking.
"""
_logger.info("[DISABLE_CART] /shop/cart/update_json ignored")
return {}
@http.route()
def cart_quantity(self):
"""The cart is always empty while this module is installed."""
return 0
@http.route()
def clear_cart(self):
"""Nothing to clear: the cart is never fed through the standard shop."""
return None