[IMP] website_sale: bin packing product for sweet layout

bzr revid: chm@openerp.com-20130925180538-qnq699csoof8jrt5
This commit is contained in:
Christophe Matthieu 2013-09-25 20:05:38 +02:00
parent b0f8591ec2
commit b8f1ffaafc
9 changed files with 262 additions and 52 deletions

View File

@ -1,3 +1,4 @@
import controllers
import website_styles
import product
import website_sale

View File

@ -68,6 +68,125 @@ class Ecommerce(http.Controller):
return (categories, full_category_ids)
def bin_packing_product_ids(self, domain, limit, offset):
product_obj = request.registry.get('product.template')
data_obj = request.registry.get('ir.model.data')
product_ids = product_obj.search(request.cr, request.uid, domain, limit=limit, offset=offset, context=request.context)
size_ids = {}
data_domain = [('model', '=', 'website.product.style'), ('name', 'like', 'size%')]
data_ids = data_obj.search(request.cr, SUPERUSER_ID, data_domain, context=request.context)
for data in data_obj.read(request.cr, SUPERUSER_ID, data_ids, ['name', 'res_id'], context=request.context):
size_ids[data['res_id']] = [int(data['name'][-3]), int(data['name'][-1])]
product_list = []
bin_packing = {}
bin_packing[0] = {}
for product in product_obj.browse(request.cr, SUPERUSER_ID, product_ids, context=request.context):
index = len(product_list)
# get size and all html classes
_class = ""
x = 1
y = 1
for style_id in product.website_style_ids:
if style_id.id in size_ids:
size = size_ids[style_id.id]
x = size[0]
y = size[1]
elif style_id.html_class:
_class += " " + style_id.html_class
product_list.append({'product': product, 'x': x, 'y': y, 'class': _class })
# bin packing products
insert = False
line = 0
while not insert:
# if not full column get next line
if len(bin_packing.setdefault(line, {})) >= 4:
line += 1
continue
col = 0
while col < 4:
if bin_packing[line].get(col, None) != None:
col += 1
continue
insert = True
copy_line = line
copy_y = y
while copy_y > 0:
# check if the box can be inserted
copy_col = col
copy_x = x
while copy_x > 0:
if copy_col >= 4 or bin_packing.setdefault(copy_line, {}).get(copy_col, None) != None:
insert = False
break
copy_col += 1
copy_x -= 1
if not insert:
break
copy_line += 1
copy_y -= 1
if not insert:
col += 1
continue
copy_y = y
while copy_y > 0:
copy_y -= 1
copy_x = x
while copy_x > 0:
copy_x -= 1
bin_packing[line + copy_y][col + copy_x] = False
bin_packing[line + copy_y][col + copy_x] = product_list[index]
break
if not insert:
line += 1
else:
break
# complete empty cell and packaging in list (from dict)
nb_empty = 0
line = 0
length = len(bin_packing)
while line < length:
col = 0
while col < 4:
if bin_packing[line].get(col) == None:
nb_empty += 1
col += 1
line += 1
products = []
if nb_empty:
product_ids = product_obj.search(request.cr, request.uid, domain, limit=nb_empty, offset=offset+limit, context=request.context)
if product_ids:
for product in product_obj.browse(request.cr, SUPERUSER_ID, product_ids, context=request.context):
products.append(product)
products.reverse()
bin_packing_list = []
line = 0
while line < length:
bin_packing_list.append([])
col = 0
while col < 4:
if products and bin_packing[line].get(col) == None:
bin_packing[line][col] = {'product': products.pop(), 'x': 1, 'y': 1, 'class': _class }
bin_packing_list[line].append(bin_packing[line].get(col, False))
col += 1
line += 1
return bin_packing_list
@website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public")
def category(self, cat_id=0, page=0, **post):
@ -92,14 +211,12 @@ class Ecommerce(http.Controller):
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)
product_ids = product_obj.search(request.cr, request.uid, domain, limit=step, offset=pager['offset'], context=request.context)
request.context['pricelist'] = self.get_pricelist()
values = {
'get_categories': self.get_categories,
'category_id': cat_id,
'products': product_obj.browse(request.cr, SUPERUSER_ID, product_ids, context=request.context),
'table_products': self.bin_packing_product_ids(domain, limit=step, offset=pager['offset']),
'search': post.get("search"),
'pager': pager,
}

View File

@ -37,14 +37,7 @@ class product_template(osv.Model):
'website_description': fields.html('Description for the website'),
'suggested_product_id': fields.many2one('product.template', 'Suggested For Product'),
'suggested_product_ids': fields.one2many('product.template', 'suggested_product_id', 'Suggested Products'),
'website_sizex': fields.selection(map(lambda x: (str(x+1),str(x+1)), range(12)), 'Size X'),
'website_sizey': fields.selection(map(lambda x: (str(x+1),str(x+1)), range(6)), 'Size Y'),
'website_product_class': fields.selection([('','Default'), ('oe_image_full','Image Full')], 'Size Y'),
}
_defaults = {
'website_sizex': '3',
'website_sizey': '2',
'website_product_class': '',
'website_style_ids' : fields.many2many('website.product.style','product_website_style_rel', 'product_id', 'style_id', 'Styles'),
}
def recommended_products(self, cr, uid, ids, context=None):

View File

@ -116,6 +116,7 @@
border: 1px solid rgba(100, 100, 100, 0.2);
max-width: 100%;
max-height: 140px;
position: relative;
}
.oe_list_products .oe_product_image {
position: absolute;
@ -142,3 +143,7 @@
right: 0;
border-top: 1px solid #dddddd;
}
.oe_website_sale .row .row .col-md-12 {
float: none;
}

View File

@ -110,6 +110,7 @@
border: 1px solid rgba(100, 100, 100, 0.2)
max-width: 100%
max-height: 140px
position: relative
.oe_product_image
position: absolute
left: 15px
@ -132,3 +133,6 @@
left: 180px
right: 0
border-top: 1px solid #dddddd
.oe_website_sale .row .row .col-md-12
float: none

View File

@ -81,6 +81,35 @@
<!-- Product list -->
<template id="products_cart">
<div class="oe_product_description">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<b t-field="product.name"/>
</a>
<!-- This should be an option -->
<div t-if="product.description_sale" class="text-muted oe_subdescription">
<div id="product_description" t-att-data-name="product.id"/>
</div>
<div>
<b>
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
<span class="text-danger" style="text-decoration: line-through;">
<t t-esc="product.product_variant_ids[0].lst_price" />
</span>&amp;nbsp;
</t>
<t t-esc="product.product_variant_ids[0].price" />
</b>
<span id="add_to_cart" t-att-data-name="product.id"/>
</div>
</div>
<div class="oe_product_image text-center">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<img class="img" t-att-src="product.img('image')"/>
</a>
</div>
</template>
<template id="products" page="True">
<t t-call="website.layout">
<t t-set="head">
@ -110,41 +139,32 @@
</div>
</div>
<div class='row style_default'>
<div class="col-md-12" id="products_grid">
<t t-foreach="products" t-as="product">
<div t-attf-class="col-md-#{ search and 3 or product.website_sizex } oe_product oe-height-#{ search and 2 or product.website_sizey } #{ product.website_product_class}" t-att-data-publish="product.website_published and 'on' or 'off'" data-name="products_layout">
<div class="oe_product_description">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<b t-field="product.name"/>
</a>
<!-- This should be an option -->
<div t-if="product.description_sale" class="text-muted oe_subdescription">
<div id="product_description" t-att-data-name="product.id"/>
</div>
<div>
<b>
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
<span class="text-danger" style="text-decoration: line-through;">
<t t-esc="product.product_variant_ids[0].lst_price" />
</span>&amp;nbsp;
</t>
<t t-esc="product.product_variant_ids[0].price" />
</b>
<span id="add_to_cart" t-att-data-name="product.id"/>
</div>
</div>
<div class="oe_product_image text-center">
<a t-attf-href="/shop/product/#{ product.id }/?#{ search and ('search=%s' % search) or ''}#{ category_id and ('&amp;category_id=%s' % category_id) or ''}">
<img class="img" t-att-src="product.img('image')"/>
</a>
</div>
</div>
</t>
</div>
<div class='style_default'>
<table class="col-md-12" id="products_grid">
<colgroup>
<col style="width: 1px; margin-right: -1px;" />
<col class="col-md-3" />
<col class="col-md-3" />
<col class="col-md-3" />
<col class="col-md-3" />
</colgroup>
<tbody>
<tr t-foreach="table_products" t-as="tr_product">
<td class="oe-height-2"></td>
<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-att-rowspan="td_product['y']"
t-attf-class="oe_product #{ td_product['class'] }"
t-att-data-publish="product.website_published and 'on' or 'off'">
<t t-call="website_sale.products_cart"/>
</td>
</t>
</t>
</tr>
</tbody>
</table>
</div>
<div class="text-center">
<t t-call="website.pager" />
@ -178,7 +198,7 @@
<!-- List view of products -->
<template id="list_view" inherit_option_id="website_sale.products" name="List View">
<xpath expr="//div[@data-name='products_layout']" position="attributes">
<xpath expr="//div[@id='products_grid']" position="replace">
<attribute name="class">col-md-12 oe_list_products oe-height-1</attribute>
</xpath>
</template>

View File

@ -12,5 +12,47 @@
<field name="state">open</field>
</record>
<record id="website_sale.image_full" model="website.product.style">
<field name="name">Image Full</field>
<field name="html_class">oe_image_full</field>
</record>
<record id="website_sale.size2x1" model="website.product.style">
<field name="name">Size 2x1</field>
<field name="html_class">col-md-6 oe-height-1</field>
</record>
<record id="website_sale.size3x1" model="website.product.style">
<field name="name">Size 3x1</field>
<field name="html_class">col-md-9 oe-height-1</field>
</record>
<record id="website_sale.size1x2" model="website.product.style">
<field name="name">Size 1x2</field>
<field name="html_class">col-md-3 oe-height-2</field>
</record>
<record id="website_sale.size1x3" model="website.product.style">
<field name="name">Size 1x3</field>
<field name="html_class">col-md-3 oe-height-2</field>
</record>
<record id="website_sale.size2x2" model="website.product.style">
<field name="name">Size 2x2</field>
<field name="html_class">col-md-6 oe-height-2</field>
</record>
<record id="website_sale.size2x3" model="website.product.style">
<field name="name">Size 2x3</field>
<field name="html_class">col-md-6 oe-height-3</field>
</record>
<record id="website_sale.size3x2" model="website.product.style">
<field name="name">Size 2x3</field>
<field name="html_class">col-md-9 oe-height-2</field>
</record>
<record id="website_sale.size3x3" model="website.product.style">
<field name="name">Size 2x3</field>
<field name="html_class">col-md-9 oe-height-3</field>
</record>
<record id="website_sale.size4x3" model="website.product.style">
<field name="name">Size 2x3</field>
<field name="html_class">col-md-9 oe-height-3</field>
</record>
</data>
</openerp>

View File

@ -8,9 +8,7 @@
<record id="product.product_product_4" model="product.product">
<field name="website_published" eval="True"/>
<field name="website_product_class">oe_image_full</field>
<field name="website_sizex">6</field>
<field name="website_sizey">4</field>
<field name="website_style_ids" eval="[(6,0,[ref('website_sale.image_full'),ref('website_sale.size2x2')])]"/>
<field name="website_description" type="html">
<section snippet-id="text-image" class="mt16 mb16 dark">
<div class="container">
@ -59,11 +57,11 @@
</record>
<record id="product.product_product_5" model="product.product">
<field name="website_published" eval="True"/>
<field name="website_sizex">6</field>
<field name="website_style_ids" eval="[(6,0,[ref('website_sale.size2x1')])]"/>
</record>
<record id="product.product_product_6" model="product.product">
<field name="website_published" eval="True"/>
<field name="website_product_class">oe_image_full</field>
<field name="website_style_ids" eval="[(6,0,[ref('website_sale.image_full')])]"/>
</record>
<record id="product.product_product_7" model="product.product">
<field name="website_published" eval="True"/>

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import osv, fields
class website_product_style(osv.Model):
_name = "website.product.style"
_columns = {
'name' : fields.char('Style Name', required=True, translate=True),
'html_class': fields.char('HTML Classes', size=64),
}