Do not simply copy One2Many from product linked by template and product

Closes #14739
(grafted from a15addd781e0cac818b6a7d2a969ee30d68932ba)

--HG--
branch : 7.0
This commit is contained in:
Cédric Krier
2026-04-02 17:25:24 +02:00
parent 672a72a964
commit 53dc0afa85
4 changed files with 90 additions and 6 deletions
+4 -2
View File
@@ -4,10 +4,12 @@
from trytond.pool import Pool
from . import category, configuration, ir, product, uom
from .product import price_digits, round_price
from .product import (
copy_product_filtered, copy_template_filtered, price_digits, round_price)
from .uom import uom_conversion_digits
__all__ = [price_digits, round_price, uom_conversion_digits]
__all__ = [price_digits, round_price, uom_conversion_digits,
copy_template_filtered, copy_product_filtered]
def register():
+69 -1
View File
@@ -30,7 +30,9 @@ except ImportError:
from .exceptions import InvalidIdentifierCode
from .ir import price_decimal
__all__ = ['price_digits', 'round_price', 'TemplateFunction']
__all__ = [
'price_digits', 'round_price', 'TemplateFunction',
'copy_template_filtered', 'copy_product_filtered']
logger = logging.getLogger(__name__)
TYPES = [
@@ -54,6 +56,72 @@ def round_price(value, rounding=None):
Decimal(1) / 10 ** price_digits[1], rounding=rounding)
def copy_template_filtered(
field_name, template_name='template', product_name='product'):
class TemplateCopyFiltered:
__slots__ = ()
@classmethod
def copy(cls, templates, default=None):
Target = getattr(cls, field_name).get_target()
assert (getattr(Target, template_name).model_name
== 'product.template')
assert (getattr(Target, product_name).model_name
== 'product.product')
default = default.copy() if default is not None else {}
copy_field = field_name not in default
default.setdefault(field_name)
new_templates = super().copy(templates, default=default)
if copy_field:
old2new, to_copy = {}, []
for template, new_template in zip(templates, new_templates):
to_copy.extend(
l for l in getattr(template, field_name)
if not getattr(l, product_name))
old2new[template.id] = new_template.id
if to_copy:
Target.copy(to_copy, {
template_name: lambda d: old2new[d[template_name]],
})
return new_templates
return TemplateCopyFiltered
def copy_product_filtered(
field_name, template_name='template', product_name='product'):
class ProductCopyFiltered:
__slots__ = ()
@classmethod
def copy(cls, products, default=None):
Target = getattr(cls, field_name).get_target()
assert (getattr(Target, template_name).model_name
== 'product.template')
assert (getattr(Target, product_name).model_name
== 'product.product')
default = default.copy() if default is not None else {}
copy_field = field_name not in default
default.setdefault(field_name)
new_products = super().copy(products, default=default)
if copy_field:
template2new, product2new, to_copy = {}, {}, []
for product, new_product in zip(products, new_products):
if targets := getattr(product, field_name):
to_copy.extend(targets)
template2new[product.template.id] = (
new_product.template.id)
product2new[product.id] = new_product.id
if to_copy:
Target.copy(to_copy, {
product_name: lambda d: product2new[
d[product_name]],
template_name: lambda d: template2new[
d[template_name]],
})
return new_products
return ProductCopyFiltered
class Template(
DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):
"Product Template"
+15 -2
View File
@@ -9,6 +9,15 @@ import PIL.Image
from trytond.config import config
from trytond.model import (
MatchMixin, ModelSQL, ModelView, Unique, fields, sequence_ordered)
try:
from trytond.modules.product import (
copy_product_filtered, copy_template_filtered)
except ImportError:
class copy_product_filtered:
pass
class copy_template_filtered:
pass
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, If
from trytond.tools import slugify
@@ -76,13 +85,17 @@ class ImageURLMixin:
yield image
class Template(ImageURLMixin, metaclass=PoolMeta):
class Template(
copy_template_filtered('images'),
ImageURLMixin, metaclass=PoolMeta):
__name__ = 'product.template'
__image_url__ = '/product/image'
images = fields.One2Many('product.image', 'template', "Images")
class Product(ImageURLMixin, metaclass=PoolMeta):
class Product(
copy_product_filtered('images'),
ImageURLMixin, metaclass=PoolMeta):
__name__ = 'product.product'
__image_url__ = '/product/variant/image'
images = fields.One2Many(
+2 -1
View File
@@ -7,7 +7,8 @@ from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
class Template(metaclass=PoolMeta):
class Template(
metaclass=PoolMeta):
__name__ = 'product.template'
gross_price = fields.MultiValue(fields.Numeric(