[ADD] website_sale_comparison: add comparison feature on eCommerce
This commit is contained in:
@@ -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.""")
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import models
|
||||
import controllers
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import main
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import website_sale_comparison
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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())>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) > 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 ) > 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>&nbsp;Add to Cart</a>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="specs.keys()" t-as="spec">
|
||||
<t t-if="len(specs.keys())>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 ) > 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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user