[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>
This commit is contained in:
GitHub Copilot 2026-08-06 11:34:59 +02:00
parent 73660ee226
commit f2194c5367
28 changed files with 733 additions and 91 deletions

View file

@ -0,0 +1,56 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Website Sale - Aplicoop
Copyright 2025 Criptomart SL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
---
FULL LICENSE TEXT
=================
For the complete AGPL-3 license text, see:
https://www.gnu.org/licenses/agpl-3.0.html
---
SUMMARY OF RIGHTS
=================
When you distribute a modified version under AGPL-3, you must:
1. Keep the same license (AGPL-3)
2. Provide a copy of the license with your distribution
3. State what changes you made
4. Include the original copyright notices
5. If distributed over a network, provide source code access
Detailed information: https://www.gnu.org/licenses/agpl-3.0-standalone.html
---
ATTRIBUTION
===========
This module was developed by: Criptomart SL
Website: https://criptomart.net
Original inspiration: Aplicoop project
https://sourceforge.net/projects/aplicoop/
---
This file is part of the Website Sale - Aplicoop module for Odoo.

View file

@ -0,0 +1,106 @@
===============================
Website Sale - Disable Cart
===============================
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. image:: https://img.shields.io/badge/Odoo-18.0-blue
:alt: Odoo: 18.0
Turn the standard Odoo eCommerce shop into a **read-only product catalog**:
visitors can browse, search and open product pages, but the standard
``website_sale`` shopping cart is not reachable any more.
It is meant for sites where the actual ordering happens somewhere else — for
example the group order flow of ``website_sale_aplicoop`` (``/eskaera``), a
quotation request form, or an offline/phone process — while the public shop is
still useful as a catalog.
What the module does
====================
- Hides the cart link and badge from all 12 standard website header styles
(default, mobile, hamburger, stretch, vertical, search, boxed, sidebar and
sales one to four).
- Removes the "Add to cart" quick-add area from the product listing cards.
- Neutralizes the standard cart endpoints:
- ``/shop/cart`` and ``/shop/cart/update`` redirect to a configurable URL.
- ``/shop/cart/update_json`` returns an empty payload (no cart is written).
- ``/shop/cart/quantity`` always returns ``0``.
- ``/shop/cart/clear`` does nothing.
Only the **cart** is disabled. Product listing, search, product pages and the
rest of ``website_sale`` are untouched. Checkout and payment routes are left
untouched too; with an empty cart they redirect back to the cart route, and
therefore to the configured URL.
Configuration
=============
Go to *Website > Configuration > Settings > Disabled Cart* and set
**Cart Redirect URL**: the internal path visitors land on when they reach a
standard cart URL.
- Default: ``/shop``.
- Stored in the system parameter ``website_sale_disable_cart.redirect_url``.
- Only site-internal paths are accepted. Absolute URLs (``https://...``) and
protocol relative URLs (``//host/path``) are ignored — otherwise the shop
could be used as an open redirect — as is any path under ``/shop/cart``,
which would loop back into a disabled route. In those cases ``/shop`` is
used and a warning is logged.
Example: on a site running ``website_sale_aplicoop``, set it to ``/eskaera`` so
that every standard cart URL lands on the group order flow.
Usage
=====
There is nothing to do at runtime: once installed, the cart is disabled site
wide. To go back to the standard behaviour, uninstall the module — all template
modifications are removed with it and the ``website_sale`` cart works again as
usual.
Files involved
==============
- ``controllers/website_sale.py`` — overrides the ``website_sale`` cart routes.
- ``views/website_sale_disable_cart_templates.xml`` — templates that hide the
cart UI.
- ``models/res_config_settings.py`` and ``views/res_config_settings_views.xml``
— the redirect URL setting.
Installation
============
::
docker-compose run --rm odoo odoo -d odoo -i website_sale_disable_cart --stop-after-init
docker-compose up -d
Testing
=======
::
docker-compose run --rm odoo odoo -d odoo --test-enable --stop-after-init -u website_sale_disable_cart
Credits
=======
**Authors:**
* Criptomart
**History:**
This behaviour was originally implemented inside ``website_sale_aplicoop`` and
was extracted into this module so that it can be installed and configured
independently of the group order flow.
License
=======
AGPL-3. See ``LICENSE.txt`` or
https://www.gnu.org/licenses/agpl-3.0-standalone.html

View file

@ -0,0 +1,2 @@
from . import controllers
from . import models

View file

@ -0,0 +1,22 @@
# Copyright 2026 - Today Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{ # noqa: B018
"name": "Website Sale - Disable Cart",
"version": "18.0.1.0.0",
"category": "Website/Sale",
"summary": "Turn the eCommerce shop into a read-only catalog: hide the cart UI "
"and redirect the standard cart routes",
"author": "Criptomart",
"maintainers": ["Criptomart"],
"website": "https://git.criptomart.net/criptomart/addons-cm",
"license": "AGPL-3",
"depends": [
"website_sale",
],
"data": [
"views/res_config_settings_views.xml",
"views/website_sale_disable_cart_templates.xml",
],
"installable": True,
}

View file

@ -0,0 +1 @@
from . import website_sale

View file

@ -0,0 +1,102 @@
# 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

View file

@ -0,0 +1,52 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_sale_disable_cart
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Cart Redirect URL"
msgstr "URL de redirección del carrito"
#. module: website_sale_disable_cart
#: model:ir.model,name:website_sale_disable_cart.model_res_config_settings
msgid "Config Settings"
msgstr "Ajustes de configuración"
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Disabled Cart"
msgstr "Carrito desactivado"
#. module: website_sale_disable_cart
#: model:ir.model.fields,field_description:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid "Disabled Cart Redirect URL"
msgstr "URL de redirección del carrito desactivado"
#. module: website_sale_disable_cart
#: model:ir.model.fields,help:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid ""
"Internal path visitors are sent to when they reach a standard cart URL. Must "
"start with '/' and must not point back to /shop/cart; otherwise '/shop' is "
"used."
msgstr ""
"Ruta interna a la que se envía a las personas visitantes cuando llegan a una "
"URL estándar del carrito. Debe empezar por '/' y no puede apuntar de nuevo a "
"/shop/cart; en caso contrario se usa '/shop'."
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Internal path visitors land on when they reach a standard cart URL"
msgstr ""
"Ruta interna a la que se llega al entrar en una URL estándar del carrito"

View file

@ -0,0 +1,52 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_sale_disable_cart
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Cart Redirect URL"
msgstr "Saskiaren birbideratze URLa"
#. module: website_sale_disable_cart
#: model:ir.model,name:website_sale_disable_cart.model_res_config_settings
msgid "Config Settings"
msgstr "Konfigurazio ezarpenak"
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Disabled Cart"
msgstr "Saskia desgaituta"
#. module: website_sale_disable_cart
#: model:ir.model.fields,field_description:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid "Disabled Cart Redirect URL"
msgstr "Desgaitutako saskiaren birbideratze URLa"
#. module: website_sale_disable_cart
#: model:ir.model.fields,help:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid ""
"Internal path visitors are sent to when they reach a standard cart URL. Must "
"start with '/' and must not point back to /shop/cart; otherwise '/shop' is "
"used."
msgstr ""
"Bisitariak saskiaren URL estandar batera iristean bidaltzen zaien barne "
"bidea. '/' karakterearekin hasi behar da eta ezin du /shop/cart-era itzuli; "
"bestela '/shop' erabiltzen da."
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Internal path visitors land on when they reach a standard cart URL"
msgstr ""
"Saskiaren URL estandar batera iristean bisitariak iristen diren barne bidea"

View file

@ -0,0 +1,47 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_sale_disable_cart
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Cart Redirect URL"
msgstr ""
#. module: website_sale_disable_cart
#: model:ir.model,name:website_sale_disable_cart.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Disabled Cart"
msgstr ""
#. module: website_sale_disable_cart
#: model:ir.model.fields,field_description:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid "Disabled Cart Redirect URL"
msgstr ""
#. module: website_sale_disable_cart
#: model:ir.model.fields,help:website_sale_disable_cart.field_res_config_settings__disabled_cart_redirect_url
msgid ""
"Internal path visitors are sent to when they reach a standard cart URL. Must "
"start with '/' and must not point back to /shop/cart; otherwise '/shop' is "
"used."
msgstr ""
#. module: website_sale_disable_cart
#: model_terms:ir.ui.view,arch_db:website_sale_disable_cart.res_config_settings_view_form
msgid "Internal path visitors land on when they reach a standard cart URL"
msgstr ""

View file

@ -0,0 +1 @@
from . import res_config_settings # noqa: F401

View file

@ -0,0 +1,18 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import fields
from odoo import models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
disabled_cart_redirect_url = fields.Char(
string="Disabled Cart Redirect URL",
config_parameter="website_sale_disable_cart.redirect_url",
default="/shop",
help="Internal path visitors are sent to when they reach a standard "
"cart URL. Must start with '/' and must not point back to /shop/cart; "
"otherwise '/shop' is used.",
)

View file

@ -0,0 +1,15 @@
Go to *Website > Configuration > Settings > Disabled Cart* and set
**Cart Redirect URL**: the internal path visitors land on when they reach a
standard cart URL.
* Default: ``/shop``.
* It is stored in the system parameter
``website_sale_disable_cart.redirect_url``.
* Only site-internal paths are accepted. Absolute URLs (``https://...``) and
protocol relative URLs (``//host/path``) are ignored — otherwise the shop
could be used as an open redirect — as is any path under ``/shop/cart``,
which would loop back into a disabled route. In those cases ``/shop`` is
used and a warning is logged.
Example: on a site running ``website_sale_aplicoop``, set it to ``/eskaera`` so
that every standard cart URL lands on the group order flow.

View file

@ -0,0 +1,6 @@
* `Criptomart <https://criptomart.net>`_:
* Development and maintenance
Extracted from ``website_sale_aplicoop``, where this behaviour was originally
implemented.

View file

@ -0,0 +1,9 @@
**Authors:**
* Criptomart
**Other credits:**
This module extracts the "shop without cart" behaviour that was originally
implemented inside ``website_sale_aplicoop``, so that it can be installed (and
configured) independently of the group order flow.

View file

@ -0,0 +1,25 @@
This module turns the standard Odoo eCommerce shop into a **read-only product
catalog**: visitors can browse, search and open product pages, but the standard
``website_sale`` shopping cart is not reachable any more.
It is meant for sites where the actual ordering happens somewhere else — for
example the group order flow of ``website_sale_aplicoop`` (``/eskaera``), a
quotation request form, or an offline/phone process — while the public shop is
still useful as a catalog.
What it does:
* Hides the cart link and badge from all 12 standard website header styles
(default, mobile, hamburger, stretch, vertical, search, boxed, sidebar and
sales one to four).
* Removes the "Add to cart" quick-add area from the product listing cards.
* Neutralizes the standard cart endpoints:
* ``/shop/cart`` and ``/shop/cart/update`` redirect to a configurable URL.
* ``/shop/cart/update_json`` returns an empty payload (no cart is written).
* ``/shop/cart/quantity`` always returns ``0``.
* ``/shop/cart/clear`` does nothing.
The redirect target is configurable, so the module stays generic: point it to
``/shop`` (default) to keep visitors in the catalog, or to whatever route
implements the real ordering flow of your site.

View file

@ -0,0 +1,13 @@
Install it like any other addon::
docker-compose run --rm odoo odoo -d odoo -i website_sale_disable_cart --stop-after-init
docker-compose up -d
**Upgrading from website_sale_aplicoop <= 18.0.1.11.0**: that module used to
carry these templates itself, under the same XPaths. Update it first so its old
views are dropped, and only then install this module — doing both in a single
Odoo run fails, because obsolete records are only cleaned up at the very end of
the run and both sets of templates would collide::
docker-compose run --rm odoo odoo -d odoo -u website_sale_aplicoop --stop-after-init
docker-compose run --rm odoo odoo -d odoo -i website_sale_disable_cart --stop-after-init

View file

@ -0,0 +1,10 @@
There is nothing to do at runtime: once installed, the cart is disabled site
wide.
To go back to the standard behaviour, simply uninstall the module. All the
template modifications are removed with it and the ``website_sale`` cart works
again as usual.
Note that this module only disables the **cart**. Checkout and payment routes
are left untouched; with an empty cart they redirect back to the cart route,
and therefore to the configured URL.

View file

@ -0,0 +1 @@
from . import test_disable_cart

View file

@ -0,0 +1,103 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import json
from odoo import http
from odoo.tests import tagged
from odoo.tests.common import HttpCase
REDIRECT_URL_PARAM = "website_sale_disable_cart.redirect_url"
@tagged("post_install", "-at_install")
class TestDisableCart(HttpCase):
"""Check the standard cart endpoints are neutralized."""
def _set_redirect_url(self, url):
self.env["ir.config_parameter"].sudo().set_param(REDIRECT_URL_PARAM, url)
def _location_of(self, path):
"""Return the redirect target of a GET on ``path``."""
response = self.url_open(path, allow_redirects=False)
self.assertIn(response.status_code, (301, 302, 303, 307, 308))
return response.headers.get("Location", "")
def test_cart_redirects_to_default_url(self):
"""Without configuration, /shop/cart falls back to /shop."""
self.env["ir.config_parameter"].sudo().search(
[("key", "=", REDIRECT_URL_PARAM)]
).unlink()
self.assertTrue(self._location_of("/shop/cart").endswith("/shop"))
def test_cart_redirects_to_configured_url(self):
"""The configured internal path is honored."""
self._set_redirect_url("/eskaera")
self.assertTrue(self._location_of("/shop/cart").endswith("/eskaera"))
def test_external_redirect_url_is_rejected(self):
"""An external URL must not be used (no open redirect)."""
self._set_redirect_url("https://example.com/phishing")
self.assertTrue(self._location_of("/shop/cart").endswith("/shop"))
def test_protocol_relative_redirect_url_is_rejected(self):
"""A protocol relative URL is external too."""
self._set_redirect_url("//example.com/phishing")
self.assertTrue(self._location_of("/shop/cart").endswith("/shop"))
def test_cart_redirect_url_cannot_loop(self):
"""Pointing the redirect back to the cart must not loop."""
self._set_redirect_url("/shop/cart")
self.assertTrue(self._location_of("/shop/cart").endswith("/shop"))
def _json_rpc(self, path, params=None):
"""Call a JSON route and return its result."""
response = self.url_open(
path,
data=json.dumps(
{"jsonrpc": "2.0", "method": "call", "params": params or {}}
),
headers={"Content-Type": "application/json"},
)
payload = response.json()
self.assertNotIn("error", payload, payload.get("error"))
return payload["result"]
def test_cart_update_redirects_without_creating_an_order(self):
"""POSTing to /shop/cart/update redirects and leaves no order behind."""
self._set_redirect_url("/shop")
product = self.env["product.product"].create(
{"name": "Disabled Cart Product", "list_price": 10.0, "is_published": True}
)
orders_before = self.env["sale.order"].search_count([])
# A public session is needed to build a valid CSRF token, otherwise the
# request is rejected before reaching the controller.
self.authenticate(None, None)
response = self.url_open(
"/shop/cart/update",
data={
"product_id": product.id,
"add_qty": 1,
"csrf_token": http.Request.csrf_token(self),
},
allow_redirects=False,
)
self.assertIn(response.status_code, (302, 303))
self.assertTrue(response.headers.get("Location", "").endswith("/shop"))
self.assertEqual(self.env["sale.order"].search_count([]), orders_before)
def test_cart_update_json_is_a_noop(self):
"""The JSON update endpoint answers an empty payload and writes nothing."""
product = self.env["product.product"].create(
{"name": "Disabled Cart Product", "list_price": 10.0, "is_published": True}
)
orders_before = self.env["sale.order"].search_count([])
result = self._json_rpc(
"/shop/cart/update_json", {"product_id": product.id, "add_qty": 1}
)
self.assertEqual(result, {})
self.assertEqual(self.env["sale.order"].search_count([]), orders_before)
def test_cart_quantity_is_zero(self):
"""The JSON quantity endpoint always answers 0."""
self.assertEqual(self._json_rpc("/shop/cart/quantity"), 0)

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.disable.cart</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//block[@id='website_info_settings']" position="after">
<h2>Disabled Cart</h2>
<div class="row mt16 o_settings_container" id="disable_cart_settings">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="disabled_cart_redirect_url" string="Cart Redirect URL"/>
<div class="text-muted">
Internal path visitors land on when they reach a standard cart URL
</div>
<div class="content-group">
<div class="mt16">
<field name="disabled_cart_redirect_url" class="oe_inline" placeholder="/shop"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- ==========================================
DISABLE STANDARD WEBSITE_SALE CART
Convert /shop into a simple product catalog
========================================== -->
<!-- Hide the cart link from the header by removing the t-call to website_sale.header_cart_link
from the various header templates (more robust than targeting the cart template internals) -->
<template id="hide_cart_in_header_default" inherit_id="website.template_header_default" name="Hide Cart Link in Header Default">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_mobile" inherit_id="website.template_header_mobile" name="Hide Cart Link in Header Mobile">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_hamburger" inherit_id="website.template_header_hamburger" name="Hide Cart Link in Header Hamburger">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_stretch" inherit_id="website.template_header_stretch" name="Hide Cart Link in Header Stretch">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_vertical" inherit_id="website.template_header_vertical" name="Hide Cart Link in Header Vertical">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_search" inherit_id="website.template_header_search" name="Hide Cart Link in Header Search">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_sales_one" inherit_id="website.template_header_sales_one" name="Hide Cart Link in Header Sales One">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_sales_two" inherit_id="website.template_header_sales_two" name="Hide Cart Link in Header Sales Two">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_sales_three" inherit_id="website.template_header_sales_three" name="Hide Cart Link in Header Sales Three">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_sales_four" inherit_id="website.template_header_sales_four" name="Hide Cart Link in Header Sales Four">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_boxed" inherit_id="website.template_header_boxed" name="Hide Cart Link in Header Boxed">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<template id="hide_cart_in_header_sidebar" inherit_id="website.template_header_sidebar" name="Hide Cart Link in Header Sidebar">
<xpath expr="//t[@t-call='website_sale.header_cart_link']" position="replace"/>
</template>
<!-- Remove "Add to Cart" button from product items in the shop -->
<template id="products_item_no_add_to_cart" inherit_id="website_sale.products_item" name="Product Item Without Add to Cart">
<!-- Remove the form action that points to /shop/cart/update -->
<xpath expr="//form[hasclass('oe_product_cart')]" position="attributes">
<attribute name="action">/</attribute>
<attribute name="method">get</attribute>
</xpath>
<!-- Hide the quick add button area completely -->
<xpath expr="//div[hasclass('o_wsale_product_btn')]" position="attributes">
<attribute name="class" add="d-none" separator=" "/>
</xpath>
</template>
<!-- Hide cart suggestion snippets and related cart features -->
<template id="suggested_products_list_hidden" inherit_id="website_sale.suggested_products_list" name="Hide Suggested Products" active="False">
<xpath expr="//*[hasclass('js_cart_lines')]" position="attributes">
<attribute name="class" add="d-none" separator=" "/>
</xpath>
</template>
</data>
</odoo>