forked from OCA/e-commerce
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[IMP] product_template_multi_link: Makes links bi-directional
fixes OCA#307
- Loading branch information
Showing
17 changed files
with
695 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
product_template_multi_link/data/product_template_link_type.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- Copyright 2019 ACSONE SA/NV | ||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> | ||
|
||
<odoo noupdate="1"> | ||
|
||
<record model="product.template.link.type" id="product_template_link_type_cross_selling"> | ||
<field name="name">Cross Selling</field> | ||
</record> | ||
|
||
<record model="product.template.link.type" id="product_template_link_type_up_selling"> | ||
<field name="name">Up Selling</field> | ||
</record> | ||
|
||
</odoo> |
14 changes: 4 additions & 10 deletions
14
product_template_multi_link/demo/product_template_link.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,10 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
|
||
<record id="link_apple_1" model="product.template.link"> | ||
<field name="product_template_id" ref="product.product_product_7_product_template"/> | ||
<field name="linked_product_template_id" ref="product.product_product_9_product_template"/> | ||
<field name="link_type">cross_sell</field> | ||
</record> | ||
|
||
<record id="link_apple_2" model="product.template.link"> | ||
<field name="product_template_id" ref="product.product_product_9_product_template"/> | ||
<field name="linked_product_template_id" ref="product.product_product_7_product_template"/> | ||
<field name="link_type">cross_sell</field> | ||
<record id="link_cross_selling_1" model="product.template.link"> | ||
<field name="product_tmpl_id_left" ref="product.product_product_7_product_template"/> | ||
<field name="product_tmpl_id_right" ref="product.product_product_9_product_template"/> | ||
<field name="type_id" ref="product_template_multi_link.product_template_link_type_cross_selling"/> | ||
</record> | ||
|
||
</odoo> |
20 changes: 20 additions & 0 deletions
20
product_template_multi_link/demo/product_template_link_type.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- Copyright 2019 ACSONE SA/NV | ||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> | ||
|
||
<odoo> | ||
|
||
<record model="product.template.link.type" id="product_template_link_type_demo_cross_selling"> | ||
<field name="name">Cross Selling</field> | ||
</record> | ||
|
||
<record model="product.template.link.type" id="product_template_link_type_demo_up_selling"> | ||
<field name="name">Up Selling</field> | ||
</record> | ||
|
||
<record model="product.template.link.type" id="product_template_link_type_demo_range"> | ||
<field name="name">Upper Range</field> | ||
<field name="inverse_name">Lower Range</field> | ||
</record> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from . import product_template | ||
from . import product_template_link | ||
from . import product_template_link_type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 117 additions & 20 deletions
137
product_template_multi_link/models/product_template_link.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,143 @@ | ||
# Copyright 2017-Today GRAP (http://www.grap.coop). | ||
# @author Sylvain LE GAL <https://twitter.com/legalsylvain> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from contextlib import contextmanager | ||
|
||
from odoo import fields, models | ||
from psycopg2.extensions import AsIs | ||
|
||
from odoo import _, api, fields, models | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class ProductTemplateLink(models.Model): | ||
_name = "product.template.link" | ||
_order = "product_template_id, linked_product_template_id" | ||
_order = "product_tmpl_id_left, product_tmpl_id_right" | ||
_description = "Product link" | ||
|
||
_LINK_TYPE_SELECTION = [("cross_sell", "Cross-Sell"), ("up_sell", "Up-Sell")] | ||
|
||
product_template_id = fields.Many2one( | ||
product_tmpl_id_left = fields.Many2one( | ||
string="Source Product", | ||
comodel_name="product.template", | ||
required=True, | ||
ondelete="cascade", | ||
) | ||
|
||
linked_product_template_id = fields.Many2one( | ||
product_tmpl_id_right = fields.Many2one( | ||
string="Linked Product", | ||
comodel_name="product.template", | ||
required=True, | ||
ondelete="cascade", | ||
) | ||
|
||
link_type = fields.Selection( | ||
string="Link Type", | ||
selection=_LINK_TYPE_SELECTION, | ||
type_id = fields.Many2one( | ||
string="Link type", | ||
comodel_name="product.template.link.type", | ||
required=True, | ||
default="cross_sell", | ||
help="* Cross-Sell : suggest your customer to" | ||
" purchase an additional product\n" | ||
"* Up-Sell : suggest your customer to purchase a higher-end product," | ||
" an upgrade, etc.", | ||
ondelete="restrict", | ||
) | ||
|
||
sql_constraints = [ | ||
( | ||
"template_link_uniq", | ||
"unique (product_template_id, linked_product_template_id, link_type)", | ||
"The products and the link type combination must be unique", | ||
link_type_name = fields.Char(related="type_id.name") # left to right | ||
link_type_inverse_name = fields.Char( | ||
related="type_id.inverse_name" | ||
) # right to left | ||
|
||
@api.constrains("product_tmpl_id_left", "product_tmpl_id_right", "type_id") | ||
def _check_products(self): | ||
""" | ||
This method checks whether: | ||
- the two products are different | ||
- there is only one link between the same two templates for the same type | ||
:raise: ValidationError if not ok | ||
""" | ||
self.flush() # flush required since the method uses plain sql | ||
if any(rec.product_tmpl_id_left == rec.product_tmpl_id_right for rec in self): | ||
raise ValidationError( | ||
_("You can only create a link between 2 different products") | ||
) | ||
|
||
products = self.mapped("product_tmpl_id_left") + self.mapped( | ||
"product_tmpl_id_right" | ||
) | ||
] | ||
self.env.cr.execute( | ||
""" | ||
SELECT | ||
id, | ||
l2.duplicate or l3.duplicate | ||
FROM ( | ||
SELECT | ||
id, | ||
product_tmpl_id_left, | ||
product_tmpl_id_right, | ||
type_id | ||
FROM | ||
%s | ||
WHERE | ||
product_tmpl_id_left in %s | ||
AND product_tmpl_id_right in %s | ||
) as l1 | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
TRUE as duplicate | ||
FROM | ||
%s | ||
WHERE | ||
product_tmpl_id_right = l1.product_tmpl_id_left | ||
AND product_tmpl_id_left = l1.product_tmpl_id_right | ||
AND type_id = l1.type_id | ||
) l2 ON TRUE | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
TRUE as duplicate | ||
FROM | ||
%s | ||
WHERE | ||
product_tmpl_id_left = l1.product_tmpl_id_left | ||
AND product_tmpl_id_right = l1.product_tmpl_id_right | ||
AND type_id = l1.type_id | ||
AND id != l1.id | ||
) l3 ON TRUE | ||
""", | ||
( | ||
AsIs(self._table), | ||
tuple(products.ids), | ||
tuple(products.ids), | ||
AsIs(self._table), | ||
AsIs(self._table), | ||
), | ||
) | ||
res = self.env.cr.fetchall() | ||
is_duplicate_by_link_id = dict(res) | ||
if True in is_duplicate_by_link_id.values(): | ||
ids = [k for k, v in is_duplicate_by_link_id.items() if v] | ||
links = self.browse(ids) | ||
descrs = [] | ||
for l in links: | ||
descrs.append( | ||
u"{} <-> {} / {} <-> {}".format( | ||
l.product_tmpl_id_left.name, | ||
l.link_type_name, | ||
l.link_type_inverse_name, | ||
l.product_tmpl_id_right.name, | ||
) | ||
) | ||
links = "\n ".join(descrs) | ||
raise ValidationError( | ||
_( | ||
"Only one link with the same type is allowed between 2 " | ||
"products. \n %s" | ||
) | ||
% links | ||
) | ||
|
||
@contextmanager | ||
def _invalidate_links_on_product_template(self): | ||
yield | ||
self.env["product.template"].invalidate_cache(["product_template_link_ids"]) | ||
|
||
@api.model | ||
def create(self, vals_list): | ||
with self._invalidate_links_on_product_template(): | ||
return super().create(vals_list) | ||
|
||
def write(self, vals): | ||
with self._invalidate_links_on_product_template(): | ||
return super().write(vals) |
Oops, something went wrong.