diff --git a/website_sale_product_minimal_price/__manifest__.py b/website_sale_product_minimal_price/__manifest__.py index e8375ea80c..32832eb328 100644 --- a/website_sale_product_minimal_price/__manifest__.py +++ b/website_sale_product_minimal_price/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Website Sale Product Minimal Price", "summary": "Display minimal price for products that has variants", - "version": "19.0.1.1.0", + "version": "19.0.1.1.1", "development_status": "Production/Stable", "maintainers": ["sergio-teruel"], "category": "Website", 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/readme/HISTORY.md b/website_sale_product_minimal_price/readme/HISTORY.md new file mode 100644 index 0000000000..0cd86b42b8 --- /dev/null +++ b/website_sale_product_minimal_price/readme/HISTORY.md @@ -0,0 +1,11 @@ +## 19.0.1.1.1 (2026-08-03) + +### Bugfixes + +- Fix `AttributeError: 'product.pricelist' object has no attribute '__ior__'` + raised on the shop page for multi-variant products whose pricelist has + sub-pricelists (`compute_price = formula`, `base_pricelist_id` set). In + Odoo 19 `request.pricelist` is a `lazy()` proxy, so the in-place union + `visited_pricelists |= pricelist` in `_get_pricelist_variant_items` delegated + to the now-removed `product.pricelist.__ior__`. + `_get_website_current_pricelist` now returns a materialised recordset. 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: