[IMP] website: add facets/attributes on product template. Displayed in the left categ...
authorChristophe Matthieu <chm@openerp.com>
Tue, 15 Oct 2013 08:27:26 +0000 (10:27 +0200)
committerChristophe Matthieu <chm@openerp.com>
Tue, 15 Oct 2013 08:27:26 +0000 (10:27 +0200)
bzr revid: chm@openerp.com-20131015082726-6f1tkyhoaju27p85

addons/website/views/website_templates.xml
addons/website_sale/__openerp__.py
addons/website_sale/controllers/main.py
addons/website_sale/models/__init__.py
addons/website_sale/static/src/js/website_sale.js
addons/website_sale/views/website_sale.xml

index 1abda81..96bcbd2 100644 (file)
@@ -59,6 +59,7 @@
                             <script t-if="not translatable" type="text/javascript" src="/website/static/lib/ace/ace.js"></script>
                             <script type="text/javascript" src="/website/static/lib/vkbeautify/vkbeautify.0.99.00.beta.js"></script>
                             <script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
+                            <link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
                             <!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
                             <script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
                             <script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
index 1ca7c32..634078f 100644 (file)
@@ -13,6 +13,7 @@ OpenERP E-Commerce
     'data': [
         'website_sale_data.xml',
         'views/website_sale.xml',
+        'views/website_sale_backend.xml',
         'security/ir.model.access.csv',
         'security/website_sale.xml',
     ],
index 02fab32..0999267 100644 (file)
@@ -7,6 +7,8 @@ from openerp.addons.web.http import request
 from openerp.addons.website.models import website
 import random
 import uuid
+import urllib
+import simplejson
 
 def get_order(order_id=None):
     order_obj = request.registry.get('sale.order')
@@ -60,6 +62,11 @@ class Ecommerce(http.Controller):
 
     _order = 'website_sequence desc, website_published desc'
 
+    def get_attribute_ids(self):
+        attributes_obj = request.registry.get('product.attribute')
+        attributes_ids = attributes_obj.search(request.cr, request.uid, [(1, "=", 1)], context=request.context)
+        return attributes_obj.browse(request.cr, request.uid, attributes_ids, context=request.context)
+
     def get_categories(self):
         domain = [('parent_id', '=', False)]
 
@@ -214,15 +221,70 @@ class Ecommerce(http.Controller):
         product_ids = [id for id in product_ids if id in product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)]
         return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
 
-    @website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
-    def category(self, cat_id=0, page=0, **post):
+    def has_search_attributes(self, attribute_id, value_id=None):
+        if request.httprequest.args.get('attributes'):
+            attributes = simplejson.loads(request.httprequest.args['attributes'])
+        else:
+            attributes = []
+        for key_val in attributes:
+            if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
+                return key_val
+        return False
+
+    @website.route(['/shop/attributes/'], type='http', auth="public", multilang=True)
+    def attributes(self, **post):
+        attributes = []
+        index = []
+        for key, val in post.items():
+            cat = key.split("-")
+            if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
+                continue
+            cat_id = int(cat[1])
+            if cat[2] == 'min':
+                minmem = float(post.pop("att-%s-minmem" % cat[1]))
+                maxmem = float(post.pop("att-%s-maxmem" % cat[1]))
+                _max = int(post.pop("att-%s-max" % cat[1]))
+                _min = int(val)
+                if (minmem != _min or maxmem != _max) and cat_id not in index:
+                    attributes.append([cat_id , [_min, _max] ])
+                    index.append(cat_id)
+            elif cat_id not in index:
+                attributes.append([ cat_id, int(cat[2]) ])
+                index.append(cat_id)
+            else:
+                attributes[index.index(cat_id)].append( int(cat[2]) )
+            post.pop(key)
+
+        return request.redirect("/shop/?attributes=%s&%s" % (simplejson.dumps(attributes).replace(" ", ""), urllib.urlencode(post)))
+
+    def attributes_to_ids(self, attributes):
+        req = "SELECT product_id FROM product_attribute_product WHERE (1 = 1) "
+        for key_val in attributes:
+
+            req += "AND (attribute_id = %s AND (" % int(key_val[0])
+            if isinstance(key_val[1], list):
+                req += "value >= %s AND value <= %s" % (int(key_val[1][0]), int(key_val[1][1]))
+            else:
+                nb = 0
+                for val in key_val[1:]:
+                    if nb:
+                        req += " OR "
+                    req += "value_id = %s" % int(val)
+                    nb += 1
+            req += ")) "
+        req += "GROUP BY product_id"
+
+        request.cr.execute(req)
+        return [r[0] for r in request.cr.fetchall()]
+
+    @website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
+    def category(self, category=0, page=0, **post):
 
         if 'promo' in post:
             self.change_pricelist(post.get('promo'))
         product_obj = request.registry.get('product.template')
 
         domain = [("sale_ok", "=", True)]
-        #domain += [('website_published', '=', True)]
 
         if post.get("search"):
             domain += ['|', '|', '|',
@@ -230,13 +292,19 @@ class Ecommerce(http.Controller):
                 ('description', 'ilike', "%%%s%%" % post.get("search")),
                 ('website_description', 'ilike', "%%%s%%" % post.get("search")),
                 ('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % post.get("search"))]
-        if cat_id:
-            cat_id = int(cat_id)
+        if post.get('category'):
+            cat_id = int(post.get('category'))
             domain += [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
 
+        if post.get('attributes'):
+            attributes = simplejson.loads(post['attributes'])
+            if attributes:
+                ids = self.attributes_to_ids(attributes)
+                domain += [('id', 'in', ids or [0] )]
+
         step = 20
         product_count = len(product_obj.search(request.cr, request.uid, domain, context=request.context))
-        pager = request.website.pager(url="/shop/category/%s/" % cat_id, total=product_count, page=page, step=step, scope=7, url_args=post)
+        pager = request.website.pager(url="/shop/%s" % post, total=product_count, page=page, step=step, scope=7, url_args=post)
 
         request.context['pricelist'] = self.get_pricelist()
 
@@ -250,21 +318,18 @@ class Ecommerce(http.Controller):
             styles = style_obj.browse(request.cr, request.uid, style_ids, context=request.context)
 
         values = {
-            'get_categories': self.get_categories,
-            'category_id': cat_id,
+            'Ecommerce': self,
             'product_ids': product_ids,
             'product_ids_for_holes': fill_hole,
-            'get_bin_packing_products': self.get_bin_packing_products,
-            'get_products': self.get_products,
-            'search': post.get("search"),
+            'search': post or dict(),
             'pager': pager,
             'styles': styles,
-            'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids]
+            'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
         }
         return request.website.render("website_sale.products", values)
 
     @website.route(['/shop/product/<int:product_id>/'], type='http', auth="public", multilang=True)
-    def product(self, cat_id=0, product_id=0, **post):
+    def product(self, product_id=0, **post):
 
         if 'promo' in post:
             self.change_pricelist(post.get('promo'))
@@ -284,13 +349,12 @@ class Ecommerce(http.Controller):
         product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
 
         values = {
-            'category_id': post.get('category_id') and int(post.get('category_id')) or None,
+            'Ecommerce': self,
             'category': category,
-            'search': post.get("search"),
-            'get_categories': self.get_categories,
             'category_list': category_list,
             'main_object': product,
             'product': product,
+            'search': post or dict(),
         }
         return request.website.render("website_sale.product", values)
 
index 3f4b71e..44b6569 100644 (file)
@@ -1,4 +1,5 @@
 import website_styles
 import product
+import product_attributes
 import website_sale
 import website
index e191a99..ab10c04 100644 (file)
@@ -119,4 +119,36 @@ $(document).ready(function () {
             });
     });
 
+
+    $(".js_slider").each(function() {
+        var $slide = $(this);
+        var $slider = $('<div>'+
+                '<input type="hidden" name="att-'+$slide.data("id")+'-minmem" value="'+$slide.data("min")+'"/>'+
+                '<input type="hidden" name="att-'+$slide.data("id")+'-maxmem" value="'+$slide.data("max")+'"/>'+
+            '</div>');
+        var $min = $("<input readonly name='att-"+$slide.data("id")+"-min'/>")
+            .css("border", "0").css("width", "50%")
+            .val($slide.data("min"));
+        var $max = $("<input readonly name='att-"+$slide.data("id")+"-max'/>")
+            .css("border", "0").css("width", "50%").css("text-align", "right")
+            .val($slide.data("max"));
+        $slide.append($min);
+        $slide.append($max);
+        $slide.append($slider);
+        $slider.slider({
+            range: true,
+            min: +$slide.data("min"),
+            max: +$slide.data("max"),
+            values: [
+                $slide.data("value-min") ? +$slide.data("value-min") : +$slide.data("min"),
+                $slide.data("value-max") ? +$slide.data("value-max") : +$slide.data("max")
+            ],
+            slide: function( event, ui ) {
+                $min.val( ui.values[ 0 ] );
+                $max.val( ui.values[ 1 ] );
+            }
+        });
+        $min.val( $slider.slider( "values", 0 ) );
+        $max.val( $slider.slider( "values", 1 ) );
+    });
 });
index dc19abc..38ced8f 100644 (file)
@@ -2,75 +2,6 @@
 <openerp>
 <data>
 
-    <record id="product_normal_form_view" model="ir.ui.view">
-        <field name="name">product.normal.form.inherit</field>
-        <field name="model">product.product</field>
-        <field name="inherit_id" ref="product.product_normal_form_view"/>
-        <field name="arch" type="xml">
-            <!-- add state field in header -->
-            <xpath expr="//sheet" position="before">
-                <div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
-                    <p attrs="{'invisible': [('website_published', '=', True)]}">
-                        This product is <b>not available</b> for public user in your ecommerce.
-                    </p>
-                    <p attrs="{'invisible': [('website_published', '=', False)]}">
-                        This product is <b>available</b> for public user in your ecommerce.
-                    </p>
-                    <p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
-                </div>
-            </xpath>
-
-            <group name="sale" position="inside">
-                <group name="website" string="Website">
-                    <field name="website_published"/>
-                    <field name="suggested_product_ids" widget="many2many_tags"/>
-                    <field name="website_style_ids" widget="many2many_tags"/>
-                </group>
-            </group>
-        </field>
-    </record>
-
-    <record model="ir.ui.view" id="product_pricelist_view">
-        <field name="name">product.pricelist.website.form</field>
-        <field name="model">product.pricelist</field>
-        <field name="inherit_id" ref="product.product_pricelist_view"/>
-        <field name="arch" type="xml">
-            <field name="active" position="after">
-                <field name="code"/>
-            </field>
-        </field>
-    </record>
-
-    <record model="ir.ui.view" id="product_template_form_view">
-        <field name="name">product.template.product.website.form</field>
-        <field name="model">product.template</field>
-        <field name="inherit_id" ref="product.product_template_form_view"/>
-        <field name="arch" type="xml">
-            <!-- add state field in header -->
-            <xpath expr="//sheet" position="before">
-                <div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('sale_ok', '=', False)]}">
-                    <p attrs="{'invisible': [('website_published', '=', True)]}">
-                        This product is <b>not available</b> for public user in your ecommerce.
-                    </p>
-                    <p attrs="{'invisible': [('website_published', '=', False)]}">
-                        This product is <b>available</b> for public user in your ecommerce.
-                    </p>
-                    <p>Website view: <field class="oe_inline" name="website_url" widget="url"/></p>
-                </div>
-            </xpath>
-
-            <xpath expr="//group[@string='Product Type']" position="inside">
-                    <field name="website_published"/>
-            </xpath>
-            <xpath expr="//page[@string='Information']" position="inside">
-                <group colspan="4" string="Products On Ecommerce">
-                    <field name="suggested_product_ids" widget="many2many_tags"/>
-                    <field name="website_style_ids" widget="many2many_tags"/>
-                </group>
-            </xpath>
-        </field>
-    </record>
-    
     <!-- Layout add nav and footer -->
 
     <template id="header" inherit_id="website.layout" name="Custom Header">
             </li>
         </xpath>
     </template>
+
     <!-- List of categories -->
 
     <template id="categories_recursive">
-        <li t-att-class="category.id == category_id and 'active' or ''">
-            <a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/category/#{ category.id }/" t-field="category.name"></a>
+        <li t-att-class="category.id == search.get('category') and 'active' or ''">
+            <a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/?category=#{ category.id }" t-field="category.name" t-keep-query="search,facettes"></a>
             <ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
                 <t t-foreach="category.child_id" t-as="category">
                     <t t-if="category.id in categ[1] or editable">
         <div class="ribbon">Promo</div>
       </div>
       <div class="oe_product_description">
-          <a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
+          <a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,facettes">
               <b t-field="product.name"/>
           </a>
       </div>
           </b>
       </div>
       <div class="oe_product_image text-center">
-          <a t-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
+          <a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,facettes">
               <img class="img" t-att-src="product.img('image')"/>
           </a>
       </div>
                     <div class="col-sm-4">
                         <h1>Our Products</h1>
                     </div><div class="col-sm-2 pagination text-center">
-                        <a t-if="editable" t-href="/shop/#{ category_id and ('category/%s/' % category_id) or ''}add_product/" class="btn btn-primary btn-default">New Product</a>
+                        <a t-if="editable" t-href="/shop/add_product/" class="btn btn-primary btn-default" t-keep-query="category,search,facettes">New Product</a>
                     </div><div class="col-sm-6">
                         <t t-call="website.pager">
                             <t t-set="classname">pull-right</t>
                         </t>
-                        <form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;">
+                        <form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;" t-keep-query="category,search,facettes">
                             <div class="form-group">
-                                <input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
+                                <input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
                             </div>
                         </form>
                     </div>
                   <div class="col-md-12" id="products_grid">
                     <table width="100%">
                       <tbody>
-                        <t t-set="table_products" t-value="get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
+                        <t t-set="table_products" t-value="Ecommerce.get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
                         <tr t-foreach="table_products" t-as="tr_product">
                           <t t-foreach="tr_product" t-as="td_product">
                             <t t-if="td_product">
                             <t t-set="product" t-value="td_product['product']"/>
                             <td t-att-colspan="td_product['x']"
+                                t-attf-width="#{td_product['x']*25}%"
                                 t-att-rowspan="td_product['y']"
                                 t-attf-class="oe_product oe-height-#{td_product['y']*2} #{ td_product['class'] }">
 
     <template id="list_view" inherit_option_id="website_sale.products" name="List View">
         <xpath expr="//div[@id='products_grid']/table" position="replace">
           <div class="row">
-            <t t-set="products" t-value="get_products(product_ids)"/>
+            <t t-set="products" t-value="Ecommerce.get_products(product_ids)"/>
             <t t-foreach="products" t-as="product">
               <div class="col-md-12 oe_list_products oe-height-1">
                 <t t-call="website_sale.products_cart"/>
                   <div class="col-sm-5">
                       <ol class="breadcrumb">
                           <li><a t-href="/shop">Products</a></li>
-                          <li t-if="category"><a t-att-href="'/shop/category/%s' % (category_id,)"><span t-field="category.name"/></a></li>
+                          <li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
                           <li class="active"><span t-field="product.name"></span></li>
                       </ol>
                   </div><div class="col-sm-3">
                         </li>
                       </t>
                   </div><div class="col-sm-3 col-sm-offset-1">
-                      <form t-action="/shop/#{ category_id and ('category/%s/' % category_id) or ''}" method="get" class="pull-right">
+                      <form t-action="/shop/" method="get" class="pull-right" t-keep-query="category,search,facettes">
                           <div class="input-group">
+                              <t t-if="search">
+                              <t foreach="search.items()" t-as="key">
+                                  <input t-att-name="key[0]" t-att-value="key[1]"/>
+                              </t>
+                              </t>
                               <span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
                               <input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
                           </div>
 
     <template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
         <xpath expr="//div[@id='products_grid']" position="before">
-            <div class="col-md-3">
+            <div id="categories" class="col-md-3">
                 <ul class="nav nav-pills nav-stacked mt16">
-                    <li t-att-class=" '' if category_id else 'active' "><a t-href="/shop/">All Products</a></li>
-                    <t t-set="categ" t-value="get_categories()"/>
+                    <li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
+                    <t t-set="categ" t-value="Ecommerce.get_categories()"/>
                     <t t-foreach="categ[0]" t-as="category">
                         <t t-call="website_sale.categories_recursive"/>
                     </t>
         </xpath>
     </template>
 
+    <template id="products_attributes" inherit_option_id="website_sale.products_categories" name="Product Attributes">
+        <xpath expr="//div[@id='categories']" position="inside">
+            <form t-action="/shop/attributes/" method="post" t-keep-query="category,search,attributes">
+                <ul class="nav nav-pills nav-stacked mt16">
+                    <t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
+                    <t t-foreach="attribute_ids" t-as="attribute_id">
+                        <li t-if="attribute_id.value_ids and attribute_id.type == 'distinct'">
+                            <div t-field="attribute_id.name"/>
+                            <ul class="nav nav-pills nav-stacked">
+                                <t t-foreach="attribute_id.value_ids" t-as="value_id">
+                                    <li t-att-class="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'active' or ''">
+                                        <label style="margin: 0 20px;">
+                                            <input type="checkbox" t-att-name="'att-%s-%s' % (attribute_id.id, value_id.id)"
+                                                t-att-checked="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'checked' or ''"/>
+                                            <span style="font-weight: normal" t-field="value_id.name"/>
+                                        </label>
+                                    </li>
+                                </t>
+                            </ul>
+                        </li>
+                        <li t-if="attribute_id.type == 'float' and attribute_id.float_min != attribute_id.float_max">
+                            <div t-field="attribute_id.name"/>
+                            <t t-set="attribute" t-value="Ecommerce.has_search_attributes(attribute_id.id)"/>
+                            <div style="margin: 0 20px;" class="js_slider"
+                              t-att-data-id="attribute_id.id"
+                              t-att-data-value-min="attribute and attribute[1][0] or attribute_id.float_min"
+                              t-att-data-value-max="attribute and attribute[1][1] or attribute_id.float_max"
+                              t-att-data-min="attribute_id.float_min"
+                              t-att-data-max="attribute_id.float_max"></div>
+                        </li>
+                    </t>
+                </ul>
+                <button class="btn btn-xs btn-primary mt16">Apply filter</button>
+            </form>
+        </xpath>
+    </template>
 
     <template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in list view">
         <xpath expr="//table[@id='mycart_products']" position="after">