Skip to content

[20.0][MIG] website_sale_product_multi_website - #1372

Open
JrAdhoc wants to merge 17 commits into
OCA:20.0from
adhoc-dev:20.0-t-76621-jr
Open

JrAdhoc wants to merge 17 commits into
OCA:20.0from
adhoc-dev:20.0-t-76621-jr

Conversation

@JrAdhoc

@JrAdhoc JrAdhoc commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Migration of website_sale_product_multi_website to 20.0.

Why it is not a straight port

Odoo 20 removed can_access_from_current_website() — the hook this module overrode to restrict a product to several websites. There is no definition and no caller left in the whole core. The check moved inline into ir.http._pre_dispatch():

website_id = request.env.context.get('website_id') or request.env.context.get('host_id')
for record in args.values():
    if isinstance(record, models.BaseModel) and 'website_id' in record._fields:
        if record.website_id and record.website_id.id != website_id:
            raise werkzeug.exceptions.NotFound()

The product page takes the product.template as a typed route argument, so this runs on every product URL. With website_id computed as website_ids[:1], a product published on two websites would answer 404 on all of them but the first.

What this does instead

website_id now answers the website being browsed: computed per context, it returns the current website whenever the product is assigned to it, and the first one of website_ids otherwise. A search method resolves website_id searches through website_ids.

That is exactly what the core needs to match a product against a website, so three overrides are no longer necessary and are dropped:

  • website.website_domain() and sale_product_domain() — the search method already resolves them. The multi_website_domain context and its Domain.custom() go with them, which also sidesteps Domain.custom(to_sql=...) changing signature in 20.0 ((model, alias, query) → (table)).
  • website.snippet.filter — it only existed to turn that context on.
  • _compute_website_published() and _search_website_published() — the core computes both correctly from website_id on its own.

The compute reads website_ids with sudo(): it is not readable by the public user, and the core still evaluates the website domain in memory through filtered_domain() on accessory and alternative products.

Views

The product.product list view inherit is dropped: website_sale no longer shows website_id in product_product_website_tree_view in 20.0, so the xpath had no anchor left and there is no core column to mirror. The form and the product.template list view inherits are unchanged, and both anchors were checked against 20.0.

Tests

test_01 and test_02 no longer call the removed method; they assert the HTTP status of the product page per website instead, which is what _pre_dispatch now controls. Four tests are added: website_id answering the browsed website, searching by website_id through website_ids (both for the in operator the core uses and for a text operator a custom filter builds), and a website in use refusing to be deleted.

Deleting a website

website_ids becomes ondelete="restrict". Dropping the stored website_id drops its foreign key along with it, and a many2many cascades by default: deleting a website would have emptied website_ids silently, and an empty website_ids means "every website" — so a product exclusive to the deleted website would have ended up published on all the remaining ones. A test covers it.

Notes for reviewers

  • website_id becomes non-stored on a field the core declares stored, so the product_template.website_id column stays behind on databases that already had the module. Nothing writes to it any more and Odoo only logs it as a removed column; I left it rather than adding a migration script that nulls out data.
  • The .pot (and i18n/it.po) still carry entries for model_website and model_website_snippet_filter, which this module no longer inherits. I left them for the bot to regenerate after the merge rather than hand-editing a generated file — say the word if you prefer them cleaned up here.
  • The test suite was not run locally (no database at hand); it is on CI.

pablohmontenegro and others added 17 commits October 22, 2026 12:48
Avoid replacing `website_ids` from the `website_id` inverse.

In this addon, `website_ids` is the source of truth and `website_id`
is kept as a computed compatibility field containing the first assigned
website.

The previous inverse replaced the full `website_ids` relation with the
single `website_id` value. This could silently drop website assignments
when editing `website_ids` or when legacy code wrote directly on
`website_id`.

Make the inverse non-destructive by adding the written `website_id` to
`website_ids` instead of replacing the whole relation.

TT61624
…to product snippets

Dynamic product snippets, such as alternative products, build their product
domain through website.website_domain(). This only takes website_id into
account by default, so products assigned to the current website through
website_ids were not shown.

Apply the existing multi_website_domain context when products are searched for
snippets, and clear it again before rendering product values to avoid leaking
that product-specific domain to other models rendered later in the same flow.

TT62939
Currently translated at 100.0% (7 of 7 strings)

Translation: e-commerce-17.0/e-commerce-17.0-website_sale_product_multi_website
Translate-URL: https://translation.odoo-community.org/projects/e-commerce-17-0/e-commerce-17-0-website_sale_product_multi_website/it/
…s and sample data

Avoid applying `with_context` when dynamic product snippets return no
records, as in "Recently Viewed Products".

TT64743
Odoo 20 removed can_access_from_current_website(), the hook this module
overrode to restrict a product to several websites. The check now lives
inline in ir.http._pre_dispatch(), which compares record.website_id with
the website being browsed and returns a 404 when they differ -- with
website_id computed as website_ids[:1], a product published on two
websites would 404 on all but the first.

So website_id answers the website being browsed instead: it is now
computed per context, returning the current website whenever the product
is assigned to it and the first one otherwise, with a search method that
reads website_ids. That is what the core needs to match a product against
a website, and it makes three overrides unnecessary:

- website.website_domain() and sale_product_domain(), because the search
  method already resolves website_id searches through website_ids. The
  multi_website_domain context and its Domain.custom() go away with them
  (Domain.custom's to_sql callback changed signature in 20.0 anyway).
- website.snippet.filter, which only existed to turn that context on.
- product.template._compute_website_published() and
  _search_website_published(), which the core now computes correctly on
  its own from website_id.

The compute reads website_ids with sudo(): it is not readable by the
public user, and the core evaluates the website domain in memory through
filtered_domain() on accessory and alternative products.

website_ids becomes ondelete="restrict". Dropping the stored website_id
drops its foreign key as well, and the many2many cascades by default:
deleting a website would have emptied website_ids silently, turning a
product exclusive to that website into one published on every remaining
website.

The product.product list view inherit is dropped: website_sale no longer
shows website_id in product_product_website_tree_view, so the xpath had
no anchor left and the column has nothing to mirror.
@OCA-git-bot OCA-git-bot added mod:website_sale_product_multi_website Module website_sale_product_multi_website series:20.0 labels Oct 22, 2026
@JrAdhoc JrAdhoc mentioned this pull request Oct 22, 2026
50 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:website_sale_product_multi_website Module website_sale_product_multi_website series:20.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants