+
-
-
- :
+
+
+ :
+
+ ,
+
+
diff --git a/addons/website_sale_comparison/controllers/main.py b/addons/website_sale_comparison/controllers/main.py
index f40ea82c865..c977063665f 100644
--- a/addons/website_sale_comparison/controllers/main.py
+++ b/addons/website_sale_comparison/controllers/main.py
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
-from odoo import http, _
+from odoo import http
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
import json
-from collections import OrderedDict
class WebsiteSaleProductComparison(WebsiteSale):
@@ -17,21 +16,6 @@ class WebsiteSaleProductComparison(WebsiteSale):
# use search to check read access on each record/ids
products = request.env['product.product'].search([('id', 'in', product_ids)])
values['products'] = products.with_context(display_default_code=False)
-
- res = OrderedDict()
- attrs = products.mapped('product_tmpl_id.valid_product_template_attribute_line_ids.attribute_id').filtered(lambda x: x.create_variant)
- for attr in attrs.sorted(lambda att: (att.category_id.sequence, att.sequence)):
- cat_name = attr.category_id.name or _('Uncategorized')
- res.setdefault(cat_name, OrderedDict()).setdefault(attr.name, [' - '] * len(products))
- for num, product in enumerate(products):
- for var in product.product_tmpl_id.valid_product_template_attribute_line_ids.filtered(lambda x: x.attribute_id.create_variant):
- cat_name = var.attribute_id.category_id.name or _('Uncategorized')
- att_name = var.attribute_id.name
- val = product.product_template_attribute_value_ids.product_attribute_value_id.filtered(lambda x: x.attribute_id == var.attribute_id)
- if val:
- res[cat_name][att_name][num] = val[0].name
- values['specs'] = res
- values['compute_currency'] = self._get_compute_currency_and_context(products[:1].product_tmpl_id)[0]
return request.render("website_sale_comparison.product_compare", values)
@http.route(['/shop/get_product_data'], type='json', auth="public", website=True)
@@ -39,7 +23,6 @@ class WebsiteSaleProductComparison(WebsiteSale):
ret = {}
pricelist_context, pricelist = self._get_pricelist_context()
prods = request.env['product.product'].with_context(pricelist_context, display_default_code=False).search([('id', 'in', product_ids)])
- compute_currency = self._get_compute_currency(pricelist, prods[:1].product_tmpl_id)
if cookies is not None:
ret['cookies'] = json.dumps(request.env['product.product'].search([('id', 'in', list(set(product_ids + cookies)))]).ids)
@@ -49,7 +32,7 @@ class WebsiteSaleProductComparison(WebsiteSale):
ret[prod.id] = {
'render': request.env['ir.ui.view'].render_template(
"website_sale_comparison.product_product",
- {'compute_currency': compute_currency, 'product': prod, 'website': request.website}
+ {'product': prod, 'website': request.website}
),
'product': dict(id=prod.id, name=prod.name, display_name=prod.display_name),
}
diff --git a/addons/website_sale_comparison/models/website_sale_comparison.py b/addons/website_sale_comparison/models/website_sale_comparison.py
index 0cf2c04e950..a74cd358b81 100644
--- a/addons/website_sale_comparison/models/website_sale_comparison.py
+++ b/addons/website_sale_comparison/models/website_sale_comparison.py
@@ -1,31 +1,80 @@
# -*- coding: utf-8 -*-
+
from collections import OrderedDict
-from odoo import fields, models, _
+from odoo import fields, models
class ProductAttributeCategory(models.Model):
_name = "product.attribute.category"
_description = "Product Attribute Category"
- _order = 'sequence'
+ _order = 'sequence, id'
- name = fields.Char("Category Name", required=True)
- sequence = fields.Integer("Sequence", default=10)
+ name = fields.Char("Category Name", required=True, translate=True)
+ sequence = fields.Integer("Sequence", default=10, index=True)
+
+ attribute_ids = fields.One2many('product.attribute', 'category_id', string="Related Attributes", domain="[('category_id', '=', False)]")
class ProductAttribute(models.Model):
_inherit = 'product.attribute'
+ _order = 'category_id, sequence, id'
- category_id = fields.Many2one('product.attribute.category', string="Category",
+ category_id = fields.Many2one('product.attribute.category', string="Category", index=True,
help="Set a category to regroup similar attributes under "
"the same section in the Comparison page of eCommerce")
-class ProductTemplate(models.Model):
- _inherit = 'product.template'
+class ProductTemplateAttributeLine(models.Model):
+ _inherit = 'product.template.attribute.line'
- def get_variant_groups(self):
- res = OrderedDict()
- for var in self.valid_product_template_attribute_line_ids:
- res.setdefault(var.attribute_id.category_id.name or _('Uncategorized'), []).append(var)
- return res
+ def _prepare_categories_for_display(self):
+ """On the product page group together the attribute lines that concern
+ attributes that are in the same category.
+
+ The returned categories are ordered following their default order.
+
+ :return: OrderedDict [{
+ product.attribute.category: [product.template.attribute.line]
+ }]
+ """
+ attributes = self.attribute_id
+ categories = OrderedDict([(cat, self.env['product.template.attribute.line']) for cat in attributes.category_id.sorted()])
+ if any(not pa.category_id for pa in attributes):
+ # category_id is not required and the mapped does not return empty
+ categories[self.env['product.attribute.category']] = self.env['product.template.attribute.line']
+ for ptal in self:
+ categories[ptal.attribute_id.category_id] |= ptal
+ return categories
+
+
+class ProductProduct(models.Model):
+ _inherit = 'product.product'
+
+ def _prepare_categories_for_display(self):
+ """On the comparison page group on the same line the values of each
+ product that concern the same attributes, and then group those
+ attributes per category.
+
+ The returned categories are ordered following their default order.
+
+ :return: OrderedDict [{
+ product.attribute.category: OrderedDict [{
+ product.attribute: OrderedDict [{
+ product: [product.template.attribute.value]
+ }]
+ }]
+ }]
+ """
+ attributes = self.product_tmpl_id.valid_product_template_attribute_line_ids._without_no_variant_attributes().attribute_id.sorted()
+ categories = OrderedDict([(cat, OrderedDict()) for cat in attributes.category_id.sorted()])
+ if any(not pa.category_id for pa in attributes):
+ # category_id is not required and the mapped does not return empty
+ categories[self.env['product.attribute.category']] = OrderedDict()
+ for pa in attributes:
+ categories[pa.category_id][pa] = OrderedDict([(
+ product,
+ product.product_template_attribute_value_ids.filtered(lambda ptav: ptav.attribute_id == pa)
+ ) for product in self])
+
+ return categories
diff --git a/addons/website_sale_comparison/static/tests/tours/website_sale_comparison.js b/addons/website_sale_comparison/static/tests/tours/website_sale_comparison.js
index 8c115eba011..0dc5bfb88b6 100644
--- a/addons/website_sale_comparison/static/tests/tours/website_sale_comparison.js
+++ b/addons/website_sale_comparison/static/tests/tours/website_sale_comparison.js
@@ -239,7 +239,7 @@ odoo.define('website_sale_comparison.tour_comparison', function (require) {
},
{
content: "check there is the correct attribute",
- trigger: '.Uncategorized:contains("color"):contains("red")',
+ trigger: '.o_ws_category_0:contains("color"):contains("red")',
run: function () {},
},
]);
diff --git a/addons/website_sale_comparison/tests/test_website_sale_comparison.py b/addons/website_sale_comparison/tests/test_website_sale_comparison.py
index 7cba6d9caf4..21e1db939d7 100644
--- a/addons/website_sale_comparison/tests/test_website_sale_comparison.py
+++ b/addons/website_sale_comparison/tests/test_website_sale_comparison.py
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from collections import OrderedDict
+from lxml import etree
+
import odoo.tests
@@ -60,5 +63,125 @@ class TestWebsiteSaleComparison(odoo.tests.TransactionCase):
@odoo.tests.tagged('post_install', '-at_install')
class TestUi(odoo.tests.HttpCase):
+ def setUp(self):
+ super(TestUi, self).setUp()
+ self.template_margaux = self.env['product.template'].create({
+ 'name': "Château Margaux",
+ 'website_published': True,
+ 'list_price': 0,
+ })
+ self.attribute_varieties = self.env['product.attribute'].create({
+ 'name': 'Grape Varieties',
+ 'sequence': 2,
+ })
+ self.attribute_vintage = self.env['product.attribute'].create({
+ 'name': 'Vintage',
+ 'sequence': 1,
+ })
+ self.values_varieties = self.env['product.attribute.value'].create({
+ 'name': n,
+ 'attribute_id': self.attribute_varieties.id,
+ 'sequence': i,
+ } for i, n in enumerate(['Cabernet Sauvignon', 'Merlot', 'Cabernet Franc', 'Petit Verdot']))
+ self.values_vintage = self.env['product.attribute.value'].create({
+ 'name': n,
+ 'attribute_id': self.attribute_vintage.id,
+ 'sequence': i,
+ } for i, n in enumerate(['2018', '2017', '2016', '2015']))
+ self.attribute_line_varieties = self.env['product.template.attribute.line'].create([{
+ 'product_tmpl_id': self.template_margaux.id,
+ 'attribute_id': self.attribute_varieties.id,
+ 'value_ids': [(6, 0, v.ids)],
+ } for v in self.values_varieties])
+ self.attribute_line_vintage = self.env['product.template.attribute.line'].create({
+ 'product_tmpl_id': self.template_margaux.id,
+ 'attribute_id': self.attribute_vintage.id,
+ 'value_ids': [(6, 0, self.values_vintage.ids)],
+ })
+ self.variants_margaux = self.template_margaux._get_possible_variants_sorted()
+
+ for variant, price in zip(self.variants_margaux, [487.32, 394.05, 532.44, 1047.84]):
+ variant.product_template_attribute_value_ids.filtered(lambda ptav: ptav.attribute_id == self.attribute_vintage).price_extra = price
+
def test_01_admin_tour_product_comparison(self):
self.start_tour("/", 'product_comparison', login='admin')
+
+ def test_02_attribute_multiple_lines(self):
+ # Case product page with "Product attributes table" disabled (website_sale standard case)
+ self.env['website'].viewref('website_sale_comparison.product_attributes_body').active = False
+ res = self.url_open('/shop/product/%d' % self.template_margaux.id)
+ self.assertEqual(res.status_code, 200)
+ root = etree.fromstring(res.content, etree.HTMLParser())
+
+ p = root.xpath('//div[@id="product_attributes_simple"]/p')[0]
+ text = etree.tostring(p, encoding='unicode', method='text')
+ self.assertEqual(text.replace(' ', '').replace('\n', ''), "GrapeVarieties:CabernetSauvignon,Merlot,CabernetFranc,PetitVerdot")
+
+ # Case product page with "Product attributes table" enabled
+ self.env['website'].viewref('website_sale_comparison.product_attributes_body').active = True
+ res = self.url_open('/shop/product/%d' % self.template_margaux.id)
+ self.assertEqual(res.status_code, 200)
+ root = etree.fromstring(res.content, etree.HTMLParser())
+
+ tr_vintage = root.xpath('//div[@id="product_specifications"]//tr')[0]
+ text_vintage = etree.tostring(tr_vintage, encoding='unicode', method='text')
+ self.assertEqual(text_vintage.replace(' ', '').replace('\n', ''), "Vintage2018or2017or2016or2015")
+
+ tr_varieties = root.xpath('//div[@id="product_specifications"]//tr')[1]
+ text_varieties = etree.tostring(tr_varieties, encoding='unicode', method='text')
+ self.assertEqual(text_varieties.replace(' ', '').replace('\n', ''), "GrapeVarietiesCabernetSauvignon,Merlot,CabernetFranc,PetitVerdot")
+
+ # Case compare page
+ res = self.url_open('/shop/compare/?products=%s' % ','.join(str(id) for id in self.variants_margaux.ids))
+ self.assertEqual(res.status_code, 200)
+ root = etree.fromstring(res.content, etree.HTMLParser())
+
+ table = root.xpath('//table[@id="o_comparelist_table"]')[0]
+
+ products = table.xpath('//a[@class="o_product_comparison_table"]')
+ self.assertEqual(len(products), 4)
+ for product, name in zip(products, ['ChâteauMargaux(2018)', 'ChâteauMargaux(2017)', 'ChâteauMargaux(2016)', 'ChâteauMargaux(2015)']):
+ text = etree.tostring(product, encoding='unicode', method='text')
+ self.assertEqual(text.replace(' ', '').replace('\n', ''), name)
+
+ tr_vintage = table.xpath('tbody/tr')[0]
+ text_vintage = etree.tostring(tr_vintage, encoding='unicode', method='text')
+ self.assertEqual(text_vintage.replace(' ', '').replace('\n', ''), "Vintage2018201720162015")
+
+ tr_varieties = table.xpath('tbody/tr')[1]
+ text_varieties = etree.tostring(tr_varieties, encoding='unicode', method='text')
+ self.assertEqual(text_varieties.replace(' ', '').replace('\n', ''), "GrapeVarieties" + 4 * "CabernetSauvignon,Merlot,CabernetFranc,PetitVerdot")
+
+ def test_03_category_order(self):
+ """Test that categories are shown in the correct order when the
+ attributes are in a different order."""
+ category_vintage = self.env['product.attribute.category'].create({
+ 'name': 'Vintage',
+ 'sequence': 2,
+ })
+ category_varieties = self.env['product.attribute.category'].create({
+ 'name': 'Varieties',
+ 'sequence': 1,
+ })
+ self.attribute_vintage.category_id = category_vintage
+ self.attribute_varieties.category_id = category_varieties
+
+ prep_categories = self.template_margaux.valid_product_template_attribute_line_ids._prepare_categories_for_display()
+ self.assertEqual(prep_categories, OrderedDict([
+ (category_varieties, self.attribute_line_varieties),
+ (category_vintage, self.attribute_line_vintage),
+ ]))
+
+ prep_categories = self.variants_margaux[0]._prepare_categories_for_display()
+ self.assertEqual(prep_categories, OrderedDict([
+ (category_varieties, OrderedDict([
+ (self.attribute_varieties, OrderedDict([
+ (self.template_margaux.product_variant_id, self.attribute_line_varieties.product_template_value_ids)
+ ]))
+ ])),
+ (category_vintage, OrderedDict([
+ (self.attribute_vintage, OrderedDict([
+ (self.template_margaux.product_variant_id, self.attribute_line_vintage.product_template_value_ids[0])
+ ]))
+ ])),
+ ]))
diff --git a/addons/website_sale_comparison/views/website_sale_comparison_template.xml b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
index 20f579ff108..0d00ca84e06 100644
--- a/addons/website_sale_comparison/views/website_sale_comparison_template.xml
+++ b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
@@ -34,23 +34,36 @@
-
-
+
+
Specifications for
-
-
-
- |
+
+
+
+ |
+
+ Uncategorized
+ |
-
- |
+
+ |
- or
-
+
+ or
+
+ |
+
+
+
+ |
+
+
+ ,
+
|
@@ -89,8 +102,9 @@
Compare Products
+
- |
+ |
@@ -119,19 +133,23 @@
|
-
-
-
-
- |
+
+
+
+
+ | Uncategorized |
+
+
+
+ |
+
+
+ ,
+
+ |
-
- |
- |
-
-
-
+
diff --git a/addons/website_sale_comparison/views/website_sale_comparison_view.xml b/addons/website_sale_comparison/views/website_sale_comparison_view.xml
index ffce1b53c78..426c3970795 100644
--- a/addons/website_sale_comparison/views/website_sale_comparison_view.xml
+++ b/addons/website_sale_comparison/views/website_sale_comparison_view.xml
@@ -5,9 +5,10 @@
product.attribute.category.tree
product.attribute.category
-
-
+
+
+