From 7de6832167ac45abfca1e136ef5fe64310b6fcc8 Mon Sep 17 00:00:00 2001 From: Antoine Guex Date: Mon, 3 Aug 2026 14:24:33 +0200 Subject: [PATCH] [FIX] website_sale_product_minimal_price: materialise lazy request.pricelist (v19) In Odoo 19 request.pricelist is a lazy() proxy (website_sale/models/ir_http.py). _get_website_current_pricelist returned it as-is, so the in-place union in _get_pricelist_variant_items (visited_pricelists |= pricelist) delegated to product.pricelist.__ior__, removed from recordsets in v19, raising AttributeError on the shop page for multi-variant products whose pricelist has sub-pricelists. Materialise the pricelist into a real recordset. --- .../models/product_template.py | 8 +++++++- .../readme/CONTRIBUTORS.md | 4 ++++ .../tests/test_product_template.py | 17 ++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/website_sale_product_minimal_price/models/product_template.py b/website_sale_product_minimal_price/models/product_template.py index 385d6a4d8d..f3df60411f 100644 --- a/website_sale_product_minimal_price/models/product_template.py +++ b/website_sale_product_minimal_price/models/product_template.py @@ -13,7 +13,13 @@ class ProductTemplate(models.Model): def _get_website_current_pricelist(self, website=None): website = website or self.env["website"].get_current_website() if request and getattr(request, "pricelist", False): - return request.pricelist + # In Odoo 19 request.pricelist is a lazy() proxy + # (website_sale/models/ir_http.py). Returning it as-is makes the + # in-place union in _get_pricelist_variant_items + # ("visited_pricelists |= pricelist") delegate to + # product.pricelist.__ior__, which no longer exists on recordsets, + # raising AttributeError. Materialise it into a real recordset. + return self.env["product.pricelist"].browse(request.pricelist.ids) pricelist = website._get_and_cache_current_pricelist() if pricelist: return pricelist diff --git a/website_sale_product_minimal_price/readme/CONTRIBUTORS.md b/website_sale_product_minimal_price/readme/CONTRIBUTORS.md index 4bf8e26a92..a02741d868 100644 --- a/website_sale_product_minimal_price/readme/CONTRIBUTORS.md +++ b/website_sale_product_minimal_price/readme/CONTRIBUTORS.md @@ -9,3 +9,7 @@ - [Studio73](https://www.studio73.es): - Alex Garcia + +- [Akyado](https://www.akyado.com): + + - Antoine Guex diff --git a/website_sale_product_minimal_price/tests/test_product_template.py b/website_sale_product_minimal_price/tests/test_product_template.py index 4572a8c1eb..d5d2128e8b 100644 --- a/website_sale_product_minimal_price/tests/test_product_template.py +++ b/website_sale_product_minimal_price/tests/test_product_template.py @@ -1,7 +1,8 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import patch from odoo.http import request from odoo.tests import TransactionCase, tagged +from odoo.tools import lazy @tagged("post_install", "-at_install") @@ -48,10 +49,20 @@ def setUpClass(cls): def test_get_website_current_pricelist(self): from odoo.addons.website_sale.tests.common import MockRequest + pricelist = self.env["product.pricelist"].create({"name": "Request Pricelist"}) with MockRequest(self.env, website=self.website): - request.pricelist = MagicMock() + # In Odoo 19 ``request.pricelist`` is a ``lazy()`` proxy. The method + # must return a materialised recordset, otherwise the in-place union + # in ``_get_pricelist_variant_items`` ("visited_pricelists |= ...") + # raises ``AttributeError: 'product.pricelist' object has no + # attribute '__ior__'``. + request.pricelist = lazy(lambda: pricelist) res = self.product_tmpl._get_website_current_pricelist() - self.assertEqual(res, request.pricelist) + self.assertEqual(res, pricelist) + # Regression: the result must support in-place set operations. + accumulator = self.env["product.pricelist"] + accumulator |= res + self.assertEqual(accumulator, pricelist) with patch.object( type(self.website), "_get_and_cache_current_pricelist" ) as mock_get_cache: