summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Kersten <jke@odoo.com>2017-02-24 19:06:17 +0100
committerJeremy Kersten <jke@odoo.com>2017-02-24 20:29:17 +0100
commit7b902fca022d9e9eeba72b59874c6e8186c2ccda (patch)
tree3b1852d0327f9e14718ae68dcc453d21908c0056
parent2be56f9d81a279ecfb5235d7764a397dd2e247e3 (diff)
[ADD] website_sale_comparison: add comparison feature on eCommerce
-rw-r--r--addons/website_sale/models/res_config.py3
-rw-r--r--addons/website_sale/views/res_config_view.xml11
-rw-r--r--addons/website_sale_comparison/__init__.py5
-rw-r--r--addons/website_sale_comparison/__manifest__.py21
-rw-r--r--addons/website_sale_comparison/controllers/__init__.py2
-rw-r--r--addons/website_sale_comparison/controllers/main.py52
-rw-r--r--addons/website_sale_comparison/data/website_sale_comparison_data.xml7
-rw-r--r--addons/website_sale_comparison/data/website_sale_comparison_demo.xml313
-rw-r--r--addons/website_sale_comparison/models/__init__.py2
-rw-r--r--addons/website_sale_comparison/models/website_sale_comparison.py31
-rw-r--r--addons/website_sale_comparison/security/ir.model.access.csv3
-rw-r--r--addons/website_sale_comparison/static/src/js/website_sale_comparison.js159
-rw-r--r--addons/website_sale_comparison/static/src/less/website_sale_comparison.less54
-rw-r--r--addons/website_sale_comparison/static/src/xml/comparison.xml27
-rw-r--r--addons/website_sale_comparison/views/website_sale_comparison_template.xml149
-rw-r--r--addons/website_sale_comparison/views/website_sale_comparison_view.xml50
16 files changed, 888 insertions, 1 deletions
diff --git a/addons/website_sale/models/res_config.py b/addons/website_sale/models/res_config.py
index 67f73085efe..d30213724d3 100644
--- a/addons/website_sale/models/res_config.py
+++ b/addons/website_sale/models/res_config.py
@@ -36,8 +36,9 @@ class WebsiteConfigSettings(models.TransientModel):
module_website_sale_options = fields.Boolean("Optional Products", help='Installs *e-Commerce Optional Products*')
module_website_sale_digital = fields.Boolean("Digital Content")
-
module_website_sale_wishlist = fields.Boolean("Wishlists ", help='Installs *e-Commerce Wishlist*')
+ module_website_sale_comparison = fields.Boolean("Product Comparator", help='Installs *e-Commerce Comparator*')
+
module_sale_stock = fields.Boolean("Delivery Orders")
module_portal = fields.Boolean("Activate the customer portal", help="""Give your customers access to their documents.""")
diff --git a/addons/website_sale/views/res_config_view.xml b/addons/website_sale/views/res_config_view.xml
index 2eaf895ca0f..dac56e614bc 100644
--- a/addons/website_sale/views/res_config_view.xml
+++ b/addons/website_sale/views/res_config_view.xml
@@ -157,6 +157,17 @@
</div>
</div>
</div>
+ <div class="col-xs-12 col-md-6 o_setting_box" id="comparator_option_setting">
+ <div class="o_setting_left_pane">
+ <field name="module_website_sale_comparison"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="module_website_sale_comparison"/>
+ <div class="text-muted">
+ Allow shoppers to compare products based on their attributes
+ </div>
+ </div>
+ </div>
</div>
<h2>Checkout Process</h2>
diff --git a/addons/website_sale_comparison/__init__.py b/addons/website_sale_comparison/__init__.py
new file mode 100644
index 00000000000..5efbd253ebb
--- /dev/null
+++ b/addons/website_sale_comparison/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import models
+import controllers
diff --git a/addons/website_sale_comparison/__manifest__.py b/addons/website_sale_comparison/__manifest__.py
new file mode 100644
index 00000000000..d84ed90f478
--- /dev/null
+++ b/addons/website_sale_comparison/__manifest__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': 'Product Comparison And Product Attribute Category',
+ 'description': 'Product Comparison, Product Attribute Category and specification table',
+ 'author': 'Odoo SA',
+ 'website': 'https://www.odoo.com',
+ 'category': 'Website',
+ 'version': '1.0',
+ 'depends': ['website_sale'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/website_sale_comparison_template.xml',
+ 'views/website_sale_comparison_view.xml',
+ ],
+ 'demo': [
+ 'data/website_sale_comparison_data.xml',
+ 'data/website_sale_comparison_demo.xml',
+ ],
+ 'installable': True,
+}
diff --git a/addons/website_sale_comparison/controllers/__init__.py b/addons/website_sale_comparison/controllers/__init__.py
new file mode 100644
index 00000000000..a84d81a791b
--- /dev/null
+++ b/addons/website_sale_comparison/controllers/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import main
diff --git a/addons/website_sale_comparison/controllers/main.py b/addons/website_sale_comparison/controllers/main.py
new file mode 100644
index 00000000000..18c98f639e2
--- /dev/null
+++ b/addons/website_sale_comparison/controllers/main.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from odoo import http, _
+from odoo.http import request
+from odoo.addons.website_sale.controllers.main import WebsiteSale
+import json
+
+
+class WebsiteSaleProductComparison(WebsiteSale):
+
+ @http.route('/shop/compare/', type='http', auth="public", website=True)
+ def product_compare(self, **post):
+ values = {}
+ product_ids = [int(i) for i in post.get('products', '').split(',') if i.isdigit()]
+ if not product_ids:
+ return request.redirect("/shop")
+ # 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 = {}
+ for num, product in enumerate(products):
+ for var in product.attribute_line_ids:
+ cat_name = var.attribute_id.category_id.name or _('Uncategorized')
+ att_name = var.attribute_id.name
+ if not product.attribute_value_ids: # create_variant = False
+ continue
+ res.setdefault(cat_name, {}).setdefault(att_name, [' - '] * len(products))
+ val = product.attribute_value_ids.filtered(lambda x: x.attribute_id == var.attribute_id)
+ res[cat_name][att_name][num] = val[0].name
+ values['specs'] = res
+ values['compute_currency'] = self._get_compute_currency_and_context()[0]
+ return request.render("website_sale_comparison.product_compare", values)
+
+ @http.route(['/shop/get_product_data'], type='json', auth="public", website=True)
+ def get_product_data(self, product_ids, cookies=None):
+ ret = {}
+ compute_currency, pricelist_context, _ = self._get_compute_currency_and_context()
+ prods = request.env['product.product'].with_context(pricelist_context, display_default_code=False).search([('id', 'in', product_ids)])
+
+ if cookies is not None:
+ ret['cookies'] = json.dumps(request.env['product.product'].search([('id', 'in', list(set(product_ids + cookies)))]).ids)
+
+ prods.mapped('name')
+ for prod in prods:
+ 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': dict(id=prod.id, name=prod.name, display_name=prod.display_name),
+ }
+ return ret
diff --git a/addons/website_sale_comparison/data/website_sale_comparison_data.xml b/addons/website_sale_comparison/data/website_sale_comparison_data.xml
new file mode 100644
index 00000000000..42b029e6537
--- /dev/null
+++ b/addons/website_sale_comparison/data/website_sale_comparison_data.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="product_attribute_category_general_features" model="product.attribute.category">
+ <field name="name">General Features</field>
+ <field name="sequence">1</field>
+ </record>
+</odoo> \ No newline at end of file
diff --git a/addons/website_sale_comparison/data/website_sale_comparison_demo.xml b/addons/website_sale_comparison/data/website_sale_comparison_demo.xml
new file mode 100644
index 00000000000..0b109d05ea1
--- /dev/null
+++ b/addons/website_sale_comparison/data/website_sale_comparison_demo.xml
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="product_attribute_category_1" model="product.attribute.category">
+ <field name="name">Camera</field>
+ <field name="sequence">3</field>
+ </record>
+ <record id="product_attribute_category_2" model="product.attribute.category">
+ <field name="name">Dimensions</field>
+ <field name="sequence">7</field>
+ </record>
+ <record id="product_attribute_category_3" model="product.attribute.category">
+ <field name="name">Display</field>
+ <field name="sequence">6</field>
+ </record>
+ <record id="product_attribute_category_4" model="product.attribute.category">
+ <field name="name">Platform</field>
+ <field name="sequence">10</field>
+ </record>
+ <record id="product_attribute_category_5" model="product.attribute.category">
+ <field name="name">Battery</field>
+ <field name="sequence">8</field>
+ </record>
+ <record id="product_attribute_category_6" model="product.attribute.category">
+ <field name="name">Memory And Storage</field>
+ <field name="sequence">9</field>
+ </record>
+ <record id="product_attribute_category_8" model="product.attribute.category">
+ <field name="name">Internet And Connectivity</field>
+ <field name="sequence">4</field>
+ </record>
+ <record id="product_attribute_category_9" model="product.attribute.category">
+ <field name="name">Multimedia</field>
+ <field name="sequence">2</field>
+ </record>
+ <record id="product_attribute_category_10" model="product.attribute.category">
+ <field name="name">Other Features</field>
+ <field name="sequence">11</field>
+ </record>
+ <record id="product_attribute_category_11" model="product.attribute.category">
+ <field name="name">Warranty</field>
+ <field name="sequence">5</field>
+ </record>
+
+ <record id="product.product_attribute_1" model="product.attribute">
+ <field name="category_id" ref="product_attribute_category_6"/>
+ </record>
+ <record id="product.product_attribute_2" model="product.attribute">
+ <field name="category_id" ref="product_attribute_category_general_features"/>
+ </record>
+ <record id="product.product_attribute_3" model="product.attribute">
+ <field name="category_id" ref="product_attribute_category_8"/>
+ </record>
+
+ <record id="product_attribute_1" model="product.attribute">
+ <field name="name">Brand</field>
+ <field name="category_id" ref="product_attribute_category_general_features"/>
+ </record>
+ <record id="product_attribute_value_1" model="product.attribute.value">
+ <field name="name">Apple</field>
+ <field name="attribute_id" ref="product_attribute_1"/>
+ </record>
+
+ <record id="product_attribute_2" model="product.attribute">
+ <field name="name">Processor</field>
+ <field name="category_id" ref="product_attribute_category_general_features"/>
+ </record>
+ <record id="product_attribute_value_2" model="product.attribute.value">
+ <field name="name">Dual Core A5</field>
+ <field name="attribute_id" ref="product_attribute_2"/>
+ </record>
+
+ <record id="product_attribute_3" model="product.attribute">
+ <field name="name">Video Formats Supported</field>
+ <field name="category_id" ref="product_attribute_category_9"/>
+ </record>
+ <record id="product_attribute_value_3" model="product.attribute.value">
+ <field name="name">.mp4, .avi, .m4v, H.264, AAC-LC, .mov, MPEG-4, .m4v, .mp4, .mov, Motion JPEG (M-JPEG)</field>
+ <field name="attribute_id" ref="product_attribute_3"/>
+ </record>
+
+ <record id="product_attribute_4" model="product.attribute">
+ <field name="name">Audio Formats Supported</field>
+ <field name="category_id" ref="product_attribute_category_9"/>
+ </record>
+ <record id="product_attribute_value_4" model="product.attribute.value">
+ <field name="name">3, 4, Protected AAC, MP3, MP3 VBR, AAX and AAX), Apple Lossless, WAV, AAC, HE-AAC, Audible (Formats 2, Audible Enhanced Audio, AIFF</field>
+ <field name="attribute_id" ref="product_attribute_4"/>
+ </record>
+
+ <record id="product_attribute_5" model="product.attribute">
+ <field name="name">Primary Camera</field>
+ <field name="category_id" ref="product_attribute_category_1"/>
+ </record>
+ <record id="product_attribute_value_5" model="product.attribute.value">
+ <field name="name">5 megapixels</field>
+ <field name="attribute_id" ref="product_attribute_5"/>
+ </record>
+
+ <record id="product_attribute_6" model="product.attribute">
+ <field name="name">Secondary Camera</field>
+ <field name="category_id" ref="product_attribute_category_1"/>
+ </record>
+ <record id="product_attribute_value_6" model="product.attribute.value">
+ <field name="name">1.2 megapixels</field>
+ <field name="attribute_id" ref="product_attribute_6"/>
+ </record>
+
+ <record id="product_attribute_7" model="product.attribute">
+ <field name="name">Weight</field>
+ <field name="category_id" ref="product_attribute_category_2"/>
+ </record>
+ <record id="product_attribute_value_7" model="product.attribute.value">
+ <field name="name">308 g</field>
+ <field name="attribute_id" ref="product_attribute_7"/>
+ </record>
+
+ <record id="product_attribute_8" model="product.attribute">
+ <field name="name">Dimensions</field>
+ <field name="category_id" ref="product_attribute_category_2"/>
+ </record>
+ <record id="product_attribute_value_8" model="product.attribute.value">
+ <field name="name">134.7 x 200 x 7.2 mm</field>
+ <field name="attribute_id" ref="product_attribute_8"/>
+ </record>
+
+ <record id="product_attribute_9" model="product.attribute">
+ <field name="name">4G</field>
+ <field name="category_id" ref="product_attribute_category_8"/>
+ </record>
+ <record id="product_attribute_value_9" model="product.attribute.value">
+ <field name="name">No</field>
+ <field name="attribute_id" ref="product_attribute_9"/>
+ </record>
+
+ <record id="product_attribute_10" model="product.attribute">
+ <field name="name">3G</field>
+ <field name="category_id" ref="product_attribute_category_8"/>
+ </record>
+ <record id="product_attribute_value_10" model="product.attribute.value">
+ <field name="name">Yes</field>
+ <field name="attribute_id" ref="product_attribute_10"/>
+ </record>
+
+ <record id="product_attribute_11" model="product.attribute">
+ <field name="name">Display Type</field>
+ <field name="category_id" ref="product_attribute_category_3"/>
+ </record>
+ <record id="product_attribute_value_11" model="product.attribute.value">
+ <field name="name">7.9 inch LCD Assistive Touchscreen with 1024 x 768 pixels</field>
+ <field name="attribute_id" ref="product_attribute_11"/>
+ </record>
+
+ <record id="product_attribute_12" model="product.attribute">
+ <field name="name">Other Display Features</field>
+ <field name="category_id" ref="product_attribute_category_3"/>
+ </record>
+ <record id="product_attribute_value_12" model="product.attribute.value">
+ <field name="name">LED Backlit Multi-touch Display with IPS Technology, Fingerprint-resistant Oleophobic Coating</field>
+ <field name="attribute_id" ref="product_attribute_12"/>
+ </record>
+
+ <record id="product_attribute_13" model="product.attribute">
+ <field name="name">Warranty Summary</field>
+ <field name="category_id" ref="product_attribute_category_11"/>
+ </record>
+ <record id="product_attribute_value_13" model="product.attribute.value">
+ <field name="name">1 Year Warranty</field>
+ <field name="attribute_id" ref="product_attribute_13"/>
+ </record>
+
+ <record id="product_attribute_14" model="product.attribute">
+ <field name="name">Battery Type</field>
+ <field name="category_id" ref="product_attribute_category_5"/>
+ </record>
+ <record id="product_attribute_value_14" model="product.attribute.value">
+ <field name="name">Lithium - Polymer</field>
+ <field name="attribute_id" ref="product_attribute_14"/>
+ </record>
+
+ <record id="product_attribute_15" model="product.attribute">
+ <field name="name">Play Time</field>
+ <field name="category_id" ref="product_attribute_category_5"/>
+ </record>
+ <record id="product_attribute_value_15" model="product.attribute.value">
+ <field name="name">10 hrs</field>
+ <field name="attribute_id" ref="product_attribute_15"/>
+ </record>
+
+ <record id="product_attribute_16" model="product.attribute">
+ <field name="name">Operating System</field>
+ <field name="category_id" ref="product_attribute_category_4"/>
+ </record>
+ <record id="product_attribute_value_16" model="product.attribute.value">
+ <field name="name">iOS 6</field>
+ <field name="attribute_id" ref="product_attribute_16"/>
+ </record>
+
+ <record id="product_attribute_17" model="product.attribute">
+ <field name="name">Sensors</field>
+ <field name="category_id" ref="product_attribute_category_4"/>
+ </record>
+ <record id="product_attribute_value_17" model="product.attribute.value">
+ <field name="name">Accelerometer, Three-axis Gyro, Ambient Light Sensor, Digital Compass</field>
+ <field name="attribute_id" ref="product_attribute_17"/>
+ </record>
+
+ <record id="product_attribute_18" model="product.attribute">
+ <field name="name">Bluetooth</field>
+ <field name="category_id" ref="product_attribute_category_8"/>
+ </record>
+ <record id="product_attribute_value_18" model="product.attribute.value">
+ <field name="name">v4</field>
+ <field name="attribute_id" ref="product_attribute_18"/>
+ </record>
+
+ <record id="product_attribute_line_1" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_1"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_1')])]"/>
+ </record>
+ <record id="product_attribute_line_2" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_2"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_2')])]"/>
+ </record>
+ <record id="product_attribute_line_3" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_3"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_3')])]"/>
+ </record>
+ <record id="product_attribute_line_4" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_4"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_4')])]"/>
+ </record>
+ <record id="product_attribute_line_5" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_5"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_5')])]"/>
+ </record>
+ <record id="product_attribute_line_6" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_6"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_6')])]"/>
+ </record>
+ <record id="product_attribute_line_7" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_7"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_7')])]"/>
+ </record>
+ <record id="product_attribute_line_8" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_8"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_8')])]"/>
+ </record>
+ <record id="product_attribute_line_9" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_9"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_9')])]"/>
+ </record>
+ <record id="product_attribute_line_10" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_10"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_10')])]"/>
+ </record>
+ <record id="product_attribute_line_11" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_11"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_11')])]"/>
+ </record>
+ <record id="product_attribute_line_12" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_12"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_12')])]"/>
+ </record>
+ <record id="product_attribute_line_13" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_13"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_13')])]"/>
+ </record>
+ <record id="product_attribute_line_14" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_14"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_14')])]"/>
+ </record>
+ <record id="product_attribute_line_15" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_15"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_15')])]"/>
+ </record>
+ <record id="product_attribute_line_16" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_16"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_16')])]"/>
+ </record>
+ <record id="product_attribute_line_17" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_17"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_17')])]"/>
+ </record>
+ <record id="product_attribute_line_18" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product_attribute_18"/>
+ <field name="value_ids" eval="[(6,0,[ref('product_attribute_value_18')])]"/>
+ </record>
+ <record id="product_attribute_line_19" model="product.attribute.line">
+ <field name="product_tmpl_id" ref="product.product_product_6_product_template"/>
+ <field name="attribute_id" ref="product.product_attribute_2"/>
+ <field name="value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/>
+ </record>
+
+</odoo>
diff --git a/addons/website_sale_comparison/models/__init__.py b/addons/website_sale_comparison/models/__init__.py
new file mode 100644
index 00000000000..7e9ada39cd5
--- /dev/null
+++ b/addons/website_sale_comparison/models/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+import website_sale_comparison \ No newline at end of file
diff --git a/addons/website_sale_comparison/models/website_sale_comparison.py b/addons/website_sale_comparison/models/website_sale_comparison.py
new file mode 100644
index 00000000000..5a1ea69ca84
--- /dev/null
+++ b/addons/website_sale_comparison/models/website_sale_comparison.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+from collections import OrderedDict
+
+from odoo import fields, models, _
+
+
+class ProductAttributeCategory(models.Model):
+ _name = "product.attribute.category"
+ _description = "Product Attribute Category"
+ _order = 'sequence'
+
+ name = fields.Char("Category Name", required=True)
+ sequence = fields.Integer("Sequence", default=10)
+
+
+class ProductAttribute(models.Model):
+ _inherit = 'product.attribute'
+
+ category_id = fields.Many2one('product.attribute.category', string="Category",
+ 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'
+
+ def get_variant_groups(self):
+ res = OrderedDict()
+ for var in self.attribute_line_ids:
+ res.setdefault(var.attribute_id.category_id.name or _('Uncategorized'), []).append(var)
+ return res
diff --git a/addons/website_sale_comparison/security/ir.model.access.csv b/addons/website_sale_comparison/security/ir.model.access.csv
new file mode 100644
index 00000000000..b19845b70d7
--- /dev/null
+++ b/addons/website_sale_comparison/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_product_attribute_category_public,product.attribute.category public,model_product_attribute_category,,1,0,0,0
+access_product_attribute_category_public_saleman,product.attribute.category sale manager,model_product_attribute_category,sales_team.group_sale_manager,1,1,1,1 \ No newline at end of file
diff --git a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js
new file mode 100644
index 00000000000..9286dc47778
--- /dev/null
+++ b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js
@@ -0,0 +1,159 @@
+odoo.define('website_sale_comparison.comparison', function (require) {
+"use strict";
+
+var ajax = require('web.ajax');
+var core = require('web.core');
+var _t = core._t;
+var utils = require('web.utils');
+var Widget = require('web.Widget');
+var website = require('web_editor.base');
+var website_sale_utils = require('website_sale.utils');
+
+if(!$('.oe_website_sale').length) {
+ return $.Deferred().reject("DOM doesn't contain '.oe_website_sale'");
+}
+
+var qweb = core.qweb;
+ajax.loadXML('/website_sale_comparison/static/src/xml/comparison.xml', qweb);
+
+var ProductComparison = Widget.extend({
+ template:"product_comparison_template",
+ events: {
+ 'click .o_product_panel_header': 'toggle_panel',
+ },
+ product_data: {},
+ init: function(){
+ this.comparelist_product_ids = JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]');
+ this.product_compare_limit = 4;
+ },
+ start:function(){
+ var self = this;
+ self.load_products(this.comparelist_product_ids).then(function() {
+ self.update_content(self.comparelist_product_ids, true);
+ if (self.comparelist_product_ids.length) {
+ $('.o_product_feature_panel').show();
+ self.update_comparelist_view();
+ }
+ });
+
+ self.popover = $('#comparelist .o_product_panel_header').popover({
+ trigger: 'manual',
+ animation: true,
+ html: true,
+ title: function () {
+ return _t("Compare Products");
+ },
+ container: '.o_product_feature_panel',
+ placement: 'top',
+ template: '<div style="width:600px;" class="popover comparator-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
+ content: function() {
+ return $('#comparelist .o_product_panel_content').html();
+ }
+ });
+ $('.oe_website_sale .o_add_compare, .oe_website_sale .o_add_compare_dyn').click(function (e){
+ if (self.comparelist_product_ids.length < self.product_compare_limit) {
+ var prod = $(this).data('product-product-id');
+ if (e.currentTarget.classList.contains('o_add_compare_dyn')) {
+ prod = parseInt($(this).parent().find('.product_id').val());
+ }
+ self.add_new_products(prod);
+ website_sale_utils.animate_clone($('#comparelist .o_product_panel_header'), $(this).closest('form'), -50, 10);
+ } else {
+ self.$('.o_comparelist_limit_warning').show();
+ self.show_panel(true);
+ }
+ });
+
+ $('body').on('click', '.comparator-popover .o_comparelist_products .o_remove', function (e){
+ self.rm_from_comparelist(e);
+ });
+ $("#o_comparelist_table tr").click(function(){
+ $($(this).data('target')).children().slideToggle(100);
+ $(this).find('.fa-chevron-circle-down, .fa-chevron-circle-right').toggleClass('fa-chevron-circle-down fa-chevron-circle-right');
+ });
+ },
+ load_products:function(product_ids) {
+ var self = this;
+ return ajax.jsonRpc('/shop/get_product_data', 'call', {
+ 'product_ids': product_ids,
+ 'cookies': JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]'),
+ }).then(function (data) {
+ self.comparelist_product_ids = JSON.parse(data.cookies);
+ delete data.cookies;
+ _.each(data, function(product) {
+ self.product_data[product.product.id] = product;
+ });
+ });
+ },
+ toggle_panel: function() {
+ $('#comparelist .o_product_panel_header').popover('toggle');
+ },
+ show_panel: function(force) {
+ if ((!$('.comparator-popover').length) || force) {
+ $('#comparelist .o_product_panel_header').popover('show');
+ }
+ },
+ refresh_panel: function() {
+ if ($('.comparator-popover').length) {
+ $('#comparelist .o_product_panel_header').popover('show');
+ }
+ },
+ add_new_products:function(product_id){
+ var self = this;
+ $('.o_product_feature_panel').show();
+ if (!_.contains(self.comparelist_product_ids, product_id)) {
+ self.comparelist_product_ids.push(product_id);
+ if(_.has(self.product_data, product_id)){
+ self.update_content([product_id], false);
+ } else {
+ self.load_products([product_id]).then(function(){
+ self.update_content([product_id], false);
+ });
+ }
+ }
+ self.update_cookie();
+ },
+ update_content:function(product_ids, reset) {
+ var self = this;
+ if (reset) {
+ self.$('.o_comparelist_products .o_product_row').remove();
+ }
+ _.each(product_ids, function(res) {
+ var $template = self.product_data[res].render;
+ self.$('.o_comparelist_products').append($template);
+ });
+ this.refresh_panel();
+ },
+ rm_from_comparelist: function(e){
+ this.comparelist_product_ids = _.without(this.comparelist_product_ids, $(e.currentTarget).data('product_product_id'));
+ $(e.currentTarget).parents('.o_product_row').remove();
+ this.update_cookie();
+ $('.o_comparelist_limit_warning').hide();
+ // force refresh to reposition popover
+ this.update_content(this.comparelist_product_ids, true);
+ },
+ update_cookie: function(){
+ document.cookie = 'comparelist_product_ids=' + JSON.stringify(this.comparelist_product_ids) + '; path=/';
+ this.update_comparelist_view();
+ },
+ update_comparelist_view: function() {
+ this.$('.o_product_circle').text(this.comparelist_product_ids.length);
+ this.$('.o_comparelist_button').hide();
+ if (_.isEmpty(this.comparelist_product_ids)) {
+ $('.o_product_feature_panel').hide();
+ this.toggle_panel();
+ } else {
+ this.$('.o_comparelist_products').show();
+ if (this.comparelist_product_ids.length >=2) {
+ this.$('.o_comparelist_button').show();
+ this.$('.o_comparelist_button a').attr('href', '/shop/compare/?products='+this.comparelist_product_ids.toString());
+ }
+ }
+ }
+});
+
+website.ready().done(function() {
+ new ProductComparison().appendTo('.oe_website_sale');
+});
+
+});
diff --git a/addons/website_sale_comparison/static/src/less/website_sale_comparison.less b/addons/website_sale_comparison/static/src/less/website_sale_comparison.less
new file mode 100644
index 00000000000..ff167568e8b
--- /dev/null
+++ b/addons/website_sale_comparison/static/src/less/website_sale_comparison.less
@@ -0,0 +1,54 @@
+.o_product_feature_panel {
+ position: fixed;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index:10;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ border-bottom: 0px;
+ padding: 10px 20px;
+ text-align: center;
+ border: 2px solid @brand-primary;
+ border-bottom: 0px;
+ background-color: white;
+ opacity: 0.95;
+
+ .o_product_panel {
+ position: relative;
+ .o_product_panel_header {
+ color: @navbar-default-link-color;
+ margin: 0 10px 0 10px;
+ cursor: pointer;
+ .o_product_icon {
+ margin-right: 5px;
+ }
+ .o_product_text {
+ text-transform: uppercase;
+ vertical-align: middle;
+ font-size: 16px;
+ }
+ .o_product_circle {
+ vertical-align: 6px;
+ padding: 0 3px;
+ line-height: 14px;
+ }
+ }
+ .o_product_panel_content {
+ display: none !important;
+ }
+ }
+}
+
+.oe_website_sale {
+ .product_summary > *{
+ display: block;
+ margin: 15px 0 15px 0;
+ }
+ .table-comparator {
+ .o_product_comparison_collpase {
+ margin-right: 8px;
+ }
+ }
+}
+
diff --git a/addons/website_sale_comparison/static/src/xml/comparison.xml b/addons/website_sale_comparison/static/src/xml/comparison.xml
new file mode 100644
index 00000000000..76009b7c657
--- /dev/null
+++ b/addons/website_sale_comparison/static/src/xml/comparison.xml
@@ -0,0 +1,27 @@
+<templates id="compare_products" xml:space="preserve">
+
+ <t t-name="product_comparison_template">
+ <div class="o_product_feature_panel hidden-xs css_editable_mode_hidden" style="display:none">
+ <span class="o_product_panel" id="comparelist">
+ <span class="o_product_panel_header">
+ <span class="o_product_icon"><i class="fa fa-exchange"></i></span>
+ <span class="o_product_text">Compare</span>
+ <span class="o_product_circle o_animate_blink label label-primary">0</span>
+ </span>
+ <span class="o_product_panel_content">
+ <div class="o_comparelist_products">
+ <div class="o_comparelist_limit_warning" style="display:none">
+ <div class="o_shortlog alert alert-warning">
+ <span><i class="fa fa-warning text-danger"></i> You can compare max 4 products.</span>
+ </div>
+ </div>
+ </div>
+ <div class="o_comparelist_button" style='display:none'>
+ <a class="btn btn-primary btn-block" href="#"><i class="fa fa-exchange mr4"></i>Compare</a>
+ </div>
+ </span>
+ </span>
+ </div>
+ </t>
+
+</templates> \ No newline at end of file
diff --git a/addons/website_sale_comparison/views/website_sale_comparison_template.xml b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
new file mode 100644
index 00000000000..07f0616c1d4
--- /dev/null
+++ b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <template id="assets_frontend" inherit_id="website.assets_frontend" name="Comparison assets frontend">
+ <xpath expr="." position="inside">
+ <link type="text/less" rel='stylesheet' href='/website_sale_comparison/static/src/less/website_sale_comparison.less'/>
+ <script type="text/javascript" src="/website_sale_comparison/static/src/js/website_sale_comparison.js"></script>
+ </xpath>
+ </template>
+
+ <template id="add_to_compare" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Comparison List">
+ <xpath expr="//div[@class='product_price']" position="inside">
+ <a class="hidden-xs btn btn-default btn-xs o_add_compare" title="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></a>
+ </xpath>
+ </template>
+
+ <template id="product_add_to_compare" name='Add to comparison in product page' inherit_id="website_sale.product">
+ <xpath expr="//a[@id='add_to_cart']" position="after">
+ <t t-if="product.env.ref('website_sale_comparison.add_to_compare').active">
+ <a class="hidden-xs btn btn-default btn-lg mt8 o_add_compare_dyn" title="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></a>
+ </t>
+ </xpath>
+ </template>
+
+ <template id="product_attributes_body" inherit_id="website_sale.product" customize_show="True" active="True" name="Product attributes table">
+ <xpath expr="//hr[@id='hr_product_attributes_simple']" position="replace"/>
+ <xpath expr="//p[@id='product_attributes_simple']" position="replace"/>
+ <xpath expr="//div[@id='product_full_description']" position="after">
+ <section class="container" id="product_full_spec">
+ <t t-set="atts" t-value="product.get_variant_groups()"/>
+ <t t-if='len(atts)'>
+ <h3 class="text-center mb32">Specifications for <t t-esc="product.name"/></h3>
+ <div class="row">
+ <div class='col-md-8 col-md-offset-2' id='product_specifications'>
+ <table class='table table-striped table-condensed table-hover'>
+ <t t-foreach="atts.keys()" t-as="spec">
+ <t t-if="len(atts.keys())&gt;1">
+ <tr class="success text-left breadcrumb clickable" data-toggle="collapse" t-att-data-target="'.'+spec.split()[0]">
+ <th t-att-colspan="2"><t t-esc="spec"/></th>
+ </tr>
+ </t>
+ <tr t-foreach="atts[spec]" t-as="att" t-att-class="'collapse in ' + spec.split()[0]">
+ <td t-esc="att.attribute_id.name"/>
+ <td>
+ <t t-set='or_separator'>or</t>
+ <t t-esc="(' %s ' % or_separator).join(att.value_ids.mapped('name'))"/>
+ </td>
+ </tr>
+ </t>
+ </table>
+ </div>
+ </div>
+ </t>
+ </section>
+ </xpath>
+ </template>
+
+ <template id='recommended_product' inherit_id="website_sale.recommended_products" name="Alternative Products">
+ <xpath expr="//h3" position="replace">
+ <t t-set="ids" t-value="str(product.id) + ','+ ','.join([str(a) for a in product.alternative_product_ids.ids])"></t>
+ <div><span class='h3'>Suggested alternatives: </span><a class="btn btn-primary" t-attf-href="/shop/compare/?products=#{ids}"><i class="fa fa-exchange"></i> Compare</a></div>
+ </xpath>
+ </template>
+
+ <template id="product_compare" name="Comparator Page">
+ <t t-call="website.layout">
+ <t t-set="additional_title">Shop Comparator</t>
+ <div id="wrap" class="js_sale">
+ <div class="oe_structure"/>
+ <div class="container oe_website_sale">
+ <section class="container">
+ <h3>Comparator</h3>
+ <table class="table table-bordered table-hover text-center mt16 table-comparator" id="o_comparelist_table">
+ <thead>
+ <td class='td-top-left'></td>
+ <td t-foreach="products" t-as="product">
+ <a t-attf-href="/shop/compare/?products=#{','.join([str(id) for id in products._ids if id != product.id])}" t-att-data-product_product_id="product.id" class="o_comparelist_remove" t-if="len(products) &gt; 2">
+ <strong class='pull-right'>x</strong>
+ </a>
+ <a t-att-href="'/shop/product/%s' % slug(product.product_tmpl_id)">
+ <img t-attf-src="/web/image/product.product/#{product.id}/image/200x150" class="img img-responsive" style="margin:auto;"/>
+ </a>
+ <div class='product_summary'>
+ <a class="o_product_comparison_table" t-attf-href="/shop/product/#{ slug(product.product_tmpl_id) }">
+ <span t-field='product.display_name'></span><br/>
+ </a>
+
+ <span>
+ <strong>Price:</strong>
+ <t t-if="(compute_currency(product.lst_price) - product.website_price ) &gt; 0.01 and website.get_current_pricelist().discount_policy=='without_discount'">
+ <del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" />
+ </t>
+ <span t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span>
+ </span>
+
+ <form action="/shop/cart/update" method="post" class="text-center">
+ <input name="product_id" t-att-value="product.id" type="hidden"/>
+ <a class="btn btn-primary btn-block a-submit"><i class="fa fa-shopping-cart"></i>&amp;nbsp;Add to Cart</a>
+ </form>
+ </div>
+ </td>
+ </thead>
+ <tbody>
+ <t t-foreach="specs.keys()" t-as="spec">
+ <t t-if="len(specs.keys())&gt;1">
+ <tr class="success text-left breadcrumb clickable" data-toggle="collapse" t-att-data-target="'.'+spec.split()[0]">
+ <th t-att-colspan="len(products) + 1"><i class="fa fa-chevron-circle-down o_product_comparison_collpase"></i><t t-esc="spec"/></th>
+ </tr>
+ </t>
+ <tr t-foreach="specs[spec].keys()" t-as="att" t-att-class="'collapse in ' + spec.split()[0]">
+ <td t-esc="att"/>
+ <td t-foreach="specs[spec][att]" t-as="val"><t t-esc="val"/></td>
+ </tr>
+ </t>
+ </tbody>
+ </table>
+ </section>
+ </div>
+ </div>
+ </t>
+ </template>
+
+ <template id="product_product" name="Comparator - Product row in comparator popover">
+ <div class="row mt8 o_product_row" t-att-data-category_ids="product.public_categ_ids.ids">
+ <div class="col-sm-3 col-xs-3 mb8">
+ <img class="img img-responsive" t-attf-src="/web/image/product.product/#{product.id}/image_small" style="max-height: 50px;" />
+ </div>
+ <div class="col-sm-7 col-xs-7 mb8 text-left">
+ <h5>
+ <strong><a t-attf-href="/shop/product/#{product.product_tmpl_id[0].id}"><t t-esc="product.display_name" /></a></strong>
+ </h5>
+ <h5 class="mt0 mb0">
+ <strong>
+ <t t-if="(compute_currency(product.lst_price) - product.website_price ) &gt; 0.01 and website.get_current_pricelist().discount_policy=='without_discount'">
+ <del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" />
+ </t>
+ <span t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"></span>
+ </strong>
+ </h5>
+ </div>
+ <div class="col-sm-1 col-xs-1 mt32">
+ <a href='#' class="o_remove" title="Remove" t-att-data-product_product_id="product.id">
+ <i class="fa fa-trash" ></i>
+ </a>
+ </div>
+ </div>
+ </template>
+
+</odoo>
diff --git a/addons/website_sale_comparison/views/website_sale_comparison_view.xml b/addons/website_sale_comparison/views/website_sale_comparison_view.xml
new file mode 100644
index 00000000000..8be3711c313
--- /dev/null
+++ b/addons/website_sale_comparison/views/website_sale_comparison_view.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+ <record id="product_attribute_category_tree_view" model="ir.ui.view">
+ <field name="name">product.attribute.category.tree</field>
+ <field name="model">product.attribute.category</field>
+ <field name="arch" type="xml">
+ <tree string="Product Attribute Category" editable="top">
+ <field name="sequence"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="product_attribute_category_action" model="ir.actions.act_window">
+ <field name="name">Website attribute categories</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">product.attribute.category</field>
+ <field name="view_mode">tree</field>
+ <field name="view_type">form</field>
+ </record>
+
+ <menuitem action="product_attribute_category_action"
+ id="menu_attribute_category_action"
+ parent="website_sale.menu_ecommerce_settings" sequence="11"/>
+
+ <record id="product_attribute_tree_view_inherit" model="ir.ui.view">
+ <field name="name">product.attribute.tree.inherit</field>
+ <field name="model">product.attribute</field>
+ <field name="inherit_id" ref="product.attribute_tree_view"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="category_id"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="product_attribute_view_form" model="ir.ui.view">
+ <field name="name">product.attribute.form.inherit</field>
+ <field name="model">product.attribute</field>
+ <field name="inherit_id" ref="website_sale.product_attribute_view_form"/>
+ <field name="priority" eval="8"/>
+ <field name="arch" type="xml">
+ <field name='name' position='after'>
+ <field name="category_id"/>
+ </field>
+ </field>
+ </record>
+
+</odoo>