Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][IMP] product_template_tags: use tags on both templates and variants #1562

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions product_template_tags/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Contributors
* `Camptocamp <https://www.camptocamp.com>`_

* Iván Todorovich <[email protected]>
* Silvio Gregorini <[email protected]>

Maintainers
~~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions product_template_tags/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import product_product
from . import product_template
from . import product_template_tag
17 changes: 17 additions & 0 deletions product_template_tags/models/product_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2017 ACSONE SA/NV
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ProductProduct(models.Model):
_inherit = "product.product"

tag_ids = fields.Many2many(
comodel_name="product.template.tag",
string="Tags",
relation="product_product_product_tag_rel",
column1="product_id",
column2="tag_id",
)
36 changes: 34 additions & 2 deletions product_template_tags/models/product_template.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
# Copyright 2017 ACSONE SA/NV
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models
from odoo import api, fields, models


class ProductTemplate(models.Model):

_inherit = "product.template"

@api.model
def _get_default_tag_propagation(self):
return "tmpl2prod"

tag_ids = fields.Many2many(
comodel_name="product.template.tag",
string="Tags",
relation="product_template_product_tag_rel",
column1="product_tmpl_id",
column2="tag_id",
compute="_compute_tag_ids",
inverse="_inverse_tag_ids",
store=True,
)
tag_propagation = fields.Selection(
string="Tag Propagation",
selection=[
("tmpl2prod", "From template to variants"),
("prod2tmpl", "From variants to template"),
],
default=lambda self: self._get_default_tag_propagation(),
required=True,
help="Defines how tags are propagated among templates and variants.\n"
"- From template to variants: variants' tags are read-only, and copied"
" from their template\n"
"- From variants to template: template's tags are read-only, and are"
" defined as a full list of all its variants' tags\n",
)

@api.depends("product_variant_ids.tag_ids", "tag_propagation")
def _compute_tag_ids(self):
# Update only templates whose tags are read from variants
for tmpl in self.filtered(lambda x: x.tag_propagation == "prod2tmpl"):
tmpl.tag_ids = tmpl.product_variant_ids.tag_ids

def _inverse_tag_ids(self):
# Update only variants whose tags are read from templates
for tmpl in self.filtered(lambda x: x.tag_propagation == "tmpl2prod"):
tmpl.product_variant_ids.tag_ids = tmpl.tag_ids
54 changes: 44 additions & 10 deletions product_template_tags/models/product_template_tag.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2017 ACSONE SA/NV
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
Expand All @@ -9,6 +10,10 @@ class ProductTemplateTag(models.Model):
_description = "Product Tag"
_order = "sequence, name"

@api.model
def _get_default_company_id(self):
return self.env.company

name = fields.Char(string="Name", required=True, translate=True)
sequence = fields.Integer(default=10)
color = fields.Integer(string="Color Index")
Expand All @@ -19,13 +24,23 @@ class ProductTemplateTag(models.Model):
column1="tag_id",
column2="product_tmpl_id",
)
products_count = fields.Integer(
string="# of Products", compute="_compute_products_count"
product_tmpl_count = fields.Integer(
string="# of Products", compute="_compute_product_tmpl_count"
)
product_prod_ids = fields.Many2many(
comodel_name="product.product",
string="Variants",
relation="product_product_product_tag_rel",
column1="tag_id",
column2="product_id",
)
product_prod_count = fields.Integer(
string="# of Variants", compute="_compute_product_prod_count"
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
default=lambda self: self.env.company,
default=lambda self: self._get_default_company_id(),
)

_sql_constraints = [
Expand All @@ -37,16 +52,35 @@ class ProductTemplateTag(models.Model):
]

@api.depends("product_tmpl_ids")
def _compute_products_count(self):
tag_id_product_count = {}
def _compute_product_tmpl_count(self):
tag_id_product_tmpl_count = {}
if self.ids:
self.env.cr.execute(
"""SELECT tag_id, COUNT(*)
"""
SELECT tag_id, COUNT(1)
FROM product_template_product_tag_rel
WHERE tag_id IN %s
GROUP BY tag_id""",
GROUP BY tag_id
""",
(tuple(self.ids),),
)
tag_id_product_tmpl_count = dict(self.env.cr.fetchall())
for tag in self:
tag.product_tmpl_count = tag_id_product_tmpl_count.get(tag.id, 0)

@api.depends("product_prod_ids")
def _compute_product_prod_count(self):
tag_id_product_prod_count = {}
if self.ids:
self.env.cr.execute(
"""
SELECT tag_id, COUNT(1)
FROM product_product_product_tag_rel
WHERE tag_id IN %s
GROUP BY tag_id
""",
(tuple(self.ids),),
)
tag_id_product_count = dict(self.env.cr.fetchall())
for rec in self:
rec.products_count = tag_id_product_count.get(rec.id, 0)
tag_id_product_prod_count = dict(self.env.cr.fetchall())
for tag in self:
tag.product_prod_count = tag_id_product_prod_count.get(tag.id, 0)
1 change: 1 addition & 0 deletions product_template_tags/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
* `Camptocamp <https://www.camptocamp.com>`_

* Iván Todorovich <[email protected]>
* Silvio Gregorini <[email protected]>
1 change: 1 addition & 0 deletions product_template_tags/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<li>Pimolnat Suntian &lt;<a class="reference external" href="mailto:pimolnats&#64;ecosoft.co.th">pimolnats&#64;ecosoft.co.th</a>&gt;</li>
<li><a class="reference external" href="https://www.camptocamp.com">Camptocamp</a><ul>
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;gmail.com">ivan.todorovich&#64;gmail.com</a>&gt;</li>
<li>Silvio Gregorini &lt;<a class="reference external" href="mailto:silvio.gregorini&#64;camptocamp.com">silvio.gregorini&#64;camptocamp.com</a>&gt;</li>
</ul>
</li>
</ul>
Expand Down
80 changes: 64 additions & 16 deletions product_template_tags/tests/test_product_template_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,77 @@ class TestProductTemplateTagBase(SavepointCase):
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.product_tmpl = cls.env["product.template"].create({"name": "Test Product"})
cls.tag = cls.env["product.template.tag"].create({"name": "Test Tag"})
cls.product_attr = cls.env["product.attribute"].create(
{
"name": "Test Attrib",
"value_ids": [
(0, 0, {"name": "Test Attrib Value %s" % str(x)}) for x in (1, 2)
],
}
)
cls.product_tmpl = cls.env["product.template"].create(
{
"name": "Test Product Tmpl",
"attribute_line_ids": [
(
0,
0,
{
"attribute_id": cls.product_attr.id,
"value_ids": [(6, 0, cls.product_attr.value_ids.ids)],
},
)
],
}
)


class TestProductTemplateTag(TestProductTemplateTagBase):
def test_product_template_tag(self):
product_tmpl_tag = self.env["product.template.tag"].create(
{"name": "Test Tag", "product_tmpl_ids": [(6, 0, [self.product_tmpl.id])]}
)
product_tmpl_tag._compute_products_count()
self.assertEqual(product_tmpl_tag.products_count, 1)

def test_product_template_tag_uniq(self):
product_tmpl_tag = self.env["product.template.tag"].create({"name": "Test Tag"})
self.assertTrue(product_tmpl_tag)
def test_00_product_template_tag_uniq(self):
# test same tag and same company
with mute_logger("odoo.sql_db"):
with self.assertRaises(IntegrityError):
with self.cr.savepoint():
self.env["product.template.tag"].create({"name": "Test Tag"})

# test same tag and different company
company = self.env["res.company"].create({"name": "Test"})
same_product_tmpl_tag_diff_company = self.env["product.template.tag"].create(
{"name": "Test Tag", "company_id": company.id}
)
self.assertTrue(same_product_tmpl_tag_diff_company)
vals = {"name": "Test Tag", "company_id": company.id}
self.assertTrue(self.env["product.template.tag"].create(vals))

def test_01_tag_propagation_tmpl2prod(self):
"""Test tag propagation from template to products

On templates where ``tag_propagation = "tmpl2prod"``, setting tags on the
template should propagate them to all the variants
"""
tag = self.tag
template = self.product_tmpl
variants = template.product_variant_ids
template.tag_propagation = "tmpl2prod"
template.tag_ids = tag
for variant in variants:
self.assertEqual(variant.tag_ids, tag)
self.assertEqual(tag.product_tmpl_ids, template)
self.assertEqual(tag.product_tmpl_count, 1)
self.assertEqual(tag.product_prod_ids, variants)
self.assertEqual(tag.product_prod_count, 2)

def test_02_tag_propagation_prod2tmpl(self):
"""Test tag propagation from products to template

On templates where ``tag_propagation = "prod2tmpl"``, setting tags on a variant
should propagate them to the template, not the other variants
"""
tag = self.tag
template = self.product_tmpl
variant_1, variant_2 = template.product_variant_ids
# Test tag propagation from products to template
template.tag_propagation = "prod2tmpl"
variant_1.tag_ids = tag
self.assertEqual(template.tag_ids, tag)
self.assertFalse(variant_2.tag_ids)
self.assertEqual(tag.product_tmpl_ids, template)
self.assertEqual(tag.product_tmpl_count, 1)
self.assertEqual(tag.product_prod_ids, variant_1)
self.assertEqual(tag.product_prod_count, 1)
25 changes: 25 additions & 0 deletions product_template_tags/views/product_product.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="product_normal_form_view_inherit" model="ir.ui.view">
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view" />
<field name="arch" type="xml">
<group name="group_general" position="inside">
<field name="tag_propagation" invisible="1" />
<field
name="tag_ids"
widget="many2many_tags"
options="{'color_field': 'color'}"
attrs="{'readonly': [('tag_propagation', '!=', 'prod2tmpl')]}"
/>
</group>
</field>
</record>

<record id="product_kanban_view" model="ir.ui.view">
<field name="model">product.product</field>
Expand All @@ -22,6 +37,16 @@
</field>
</record>

<record id="product_search_form_view" model="ir.ui.view">
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view" />
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="tag_ids" />
</field>
</field>
</record>

<record id="product_product_tree_view" model="ir.ui.view">
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view" />
Expand Down
7 changes: 5 additions & 2 deletions product_template_tags/views/product_template.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form</field>
<field name="name">product.template.form (in product_template_tags)</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" />
<field name="inherit_id" ref="product.product_template_only_form_view" />
<field name="arch" type="xml">
<group name="group_general" position="inside">
<field name="tag_propagation" />
<field
name="tag_ids"
widget="many2many_tags"
options="{'color_field': 'color'}"
attrs="{'readonly': [('tag_propagation', '!=', 'tmpl2prod')]}"
/>
</group>
</field>
Expand Down
15 changes: 14 additions & 1 deletion product_template_tags/views/product_template_tag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,24 @@
context="{'search_default_tag_ids': active_id}"
>
<field
name="products_count"
name="product_tmpl_count"
widget="statinfo"
string="Products"
/>
</button>
<button
name="%(product.product_normal_action)d"
type="action"
class="oe_stat_button"
icon="fa-tags"
context="{'search_default_tag_ids': active_id}"
>
<field
name="product_prod_count"
widget="statinfo"
string="Variants"
/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
Expand Down
Loading
Loading