Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions website_sale_variant_extra_fields/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

================================
Website Sale Variant Extra Field
================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9cc0cda167991a1639bba1b8c4b5918d46a6145dfe00601164f4bd2fc8ab7c30
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fe--commerce-lightgray.png?logo=github
:target: https://github.com/OCA/e-commerce/tree/19.0/website_sale_variant_extra_fields
:alt: OCA/e-commerce
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/e-commerce-19-0/e-commerce-19-0-website_sale_variant_extra_fields
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/e-commerce&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

The standard *Product Page Extra Fields* configuration lets a website
manager pick product fields to display on the shop product page. It only
supports char and binary fields on the product template, and the
displayed value is read once when the page loads, so it never updates
when the customer switches variants.

This module removes both limitations:

- product variant fields can be selected, in addition to product
template fields;
- more field types are supported: numbers, dates, datetimes, dropdown
(selection) values and linked records, in addition to char and binary
fields;
- when a variant field is selected, its value refreshes live on the
product page as soon as the customer picks a different combination of
attributes.

- decimal numbers are displayed with the number of decimals configured
for the selected field.

**Table of contents**

.. contents::
:local:

Usage
=====

1. Go to *Website > Configuration > Settings* and open the *Product Page
Extra Fields* configuration of the website.
2. Add a line and pick any field of a product or of a product variant.
Text, file, number, date, date and time, dropdown, and linked record
fields are available.
3. Go to the shop and open a product page. The label and the value of
each selected field are displayed below the product details (if the
field has a value on the displayed product or variant).
4. Select another variant attribute on that page. The values coming from
a product.variant field are updated immediately, while the values
coming from a product.template field stay unchanged.

A variant field with no value on the displayed variant is simply hidden,
and appears again as soon as the customer selects a variant that has a
value.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/e-commerce/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/e-commerce/issues/new?body=module:%20website_sale_variant_extra_fields%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Camptocamp

Contributors
------------

- `Camptocamp <https://www.camptocamp.com/>`__:

- Ricardoalso <ricardo.almeidasoares@camptocamp.com>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/e-commerce <https://github.com/OCA/e-commerce/tree/19.0/website_sale_variant_extra_fields>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions website_sale_variant_extra_fields/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).

from . import models
22 changes: 22 additions & 0 deletions website_sale_variant_extra_fields/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Camptocamp SA
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
{
"name": "Website Sale Variant Extra Field",
"summary": "Show variant fields and more field types in the product page "
"extra fields",
"version": "19.0.1.0.0",
"development_status": "Beta",
"category": "Website",
"website": "https://github.com/OCA/e-commerce",
"author": "Camptocamp, Odoo Community Association (OCA)",
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": ["website_sale"],
"data": ["views/templates.xml"],
"assets": {
"web.assets_frontend": [
"/website_sale_variant_extra_fields/static/src/js/*.esm.js",
],
},
}
2 changes: 2 additions & 0 deletions website_sale_variant_extra_fields/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import website_sale_extra_field
from . import product_template
33 changes: 33 additions & 0 deletions website_sale_variant_extra_fields/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2026 Camptocamp SA
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).

from odoo import models


class ProductTemplate(models.Model):
_inherit = "product.template"

def _get_combination_info(
self,
combination=False,
product_id=False,
add_qty=1.0,
uom_id=False,
only_template=False,
):
combination_info = super()._get_combination_info(
combination=combination,
product_id=product_id,
add_qty=add_qty,
uom_id=uom_id,
only_template=only_template,
)
website = self.env["website"].get_current_website()
extra_fields = website.shop_extra_field_ids.filtered("is_variant_field")
if not extra_fields:
return combination_info
variant = self.env["product.product"].browse(combination_info.get("product_id"))
combination_info["variant_extra_fields"] = extra_fields._get_rendered_values(
variant
)
return combination_info
123 changes: 123 additions & 0 deletions website_sale_variant_extra_fields/models/website_sale_extra_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright 2026 Camptocamp SA
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).

from markupsafe import Markup

from odoo import api, fields, models

VARIANT_MODEL = "product.product"

EXTRA_FIELD_MODELS = ["product.template", "product.product"]

EXTRA_FIELD_TTYPES = [
"char",
"binary",
"integer",
"float",
"date",
"datetime",
"selection",
"many2one",
"one2many",
"many2many",
]


class WebsiteSaleExtraField(models.Model):
_inherit = "website.sale.extra.field"

field_id = fields.Many2one(
domain=[
("model_id.model", "in", EXTRA_FIELD_MODELS),
("ttype", "in", EXTRA_FIELD_TTYPES),
],
)
is_variant_field = fields.Boolean(compute="_compute_is_variant_field")

@api.depends("field_id.model_id.model")
def _compute_is_variant_field(self):
for extra_field in self:
extra_field.is_variant_field = (
extra_field.field_id.sudo().model_id.model == VARIANT_MODEL
)

def _get_source_record(self, product, product_variant):
"""Return the record the extra field value must be read from.

Variant scoped fields are read from the currently displayed variant,
template scoped ones from the product template.
"""
self.ensure_one()
return product_variant if self.is_variant_field else product

def _get_render_options(self):
"""Return the ``t-options`` used to render the extra field value."""
self.ensure_one()
field_id = self.field_id.sudo()
ttype = field_id.ttype
options = {"widget": ttype}
field = self.env[field_id.model_id.model]._fields.get(self.name)
if not field:
return options
if ttype == "float":
# Without an explicit precision, `ir.qweb.field.float` falls back to
# a significant digits heuristic instead of the decimal precision
# configured on the field.
digits = field.get_digits(self.env)
if digits:
options["precision"] = digits[1]
elif ttype == "selection":
# `ir.qweb.field.selection` requires the labels mapping, which is
# only injected automatically when rendering a record, not a value.
options["selection"] = dict(field.get_description(self.env)["selection"])
return options

def _get_converter(self):
"""Return the ``ir.qweb.field`` model rendering the extra field value.

Some field types have no dedicated converter, QWeb falls back on the
base one for them.
"""
self.ensure_one()
model = f"ir.qweb.field.{self.field_id.sudo().ttype}"
return self.env[model] if model in self.env else self.env["ir.qweb.field"]

def _render_value(self, record):
"""Return the HTML rendering of the extra field value on ``record``."""
self.ensure_one()
if not record:
return ""
value = record.sudo()[self.name]
if not value:
return ""
field_id = self.field_id.sudo()
if field_id.ttype == "binary":
return Markup(
'<a target="_blank" href="/web/content/%s/%s/%s?download=1">'
'<i class="fa fa-file"/></a>'
) % (field_id.model_id.model, record.id, self.name)
rendered = self._get_converter().value_to_html(
value, self._get_render_options()
)
return Markup(rendered) if rendered else ""

def _get_rendered_values(self, record):
"""Return ``{field name: rendered value}`` for every field of ``self``."""
return {
extra_field.name: extra_field._render_value(record) for extra_field in self
}

def _has_content_to_display(self, product, product_variant):
"""Whether the extra fields block must be rendered for that product.

Variant scoped fields always reserve their place in the page: their value
is refreshed client side when the customer selects another variant, so an
empty value on the variant displayed first must not remove the block.
"""
return any(
extra_field.is_variant_field
or extra_field._render_value(
extra_field._get_source_record(product, product_variant)
)
for extra_field in self
)
3 changes: 3 additions & 0 deletions website_sale_variant_extra_fields/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
2 changes: 2 additions & 0 deletions website_sale_variant_extra_fields/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Camptocamp](https://www.camptocamp.com/):
- Ricardoalso \<<ricardo.almeidasoares@camptocamp.com>\>
17 changes: 17 additions & 0 deletions website_sale_variant_extra_fields/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The standard *Product Page Extra Fields* configuration lets a website manager
pick product fields to display on the shop product page. It only supports
char and binary fields on the product template, and the displayed value is
read once when the page loads, so it never updates when the customer switches
variants.

This module removes both limitations:

- product variant fields can be selected, in addition to product template
fields;
- more field types are supported: numbers, dates, datetimes, dropdown
(selection) values and linked records, in addition to char and binary
fields;
- when a variant field is selected, its value refreshes live on the product
page as soon as the customer picks a different combination of attributes.
- decimal numbers are displayed with the number of decimals configured for the
selected field.
12 changes: 12 additions & 0 deletions website_sale_variant_extra_fields/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
1. Go to *Website \> Configuration \> Settings* and open the
*Product Page Extra Fields* configuration of the website.
2. Add a line and pick any field of a product or of a product variant. Text,
file, number, date, date and time, dropdown, and linked record fields are
available.
3. Go to the shop and open a product page. The label and the value of each
selected field are displayed below the product details (if the field has a value on the displayed product or variant).
4. Select another variant attribute on that page. The values coming from a product.variant field are updated immediately,
while the values coming from a product.template field stay unchanged.

A variant field with no value on the displayed variant is simply hidden, and
appears again as soon as the customer selects a variant that has a value.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading