1 # -*- coding: utf-8 -*-
3 from openerp import SUPERUSER_ID
4 from openerp.osv import osv
5 from openerp.addons.web import http
6 from openerp.addons.web.http import request
7 from openerp.addons.website.models import website
11 def get_order(order_id=None):
12 order_obj = request.registry.get('sale.order')
13 # check if order allready exists and have access
16 order = order_obj.browse(request.cr, request.uid, order_id, context=request.context)
23 fields = [k for k, v in order_obj._columns.items()]
24 order_value = order_obj.default_get(request.cr, SUPERUSER_ID, fields, context=request.context)
25 if request.httprequest.session.get('ecommerce_pricelist'):
26 order_value['pricelist_id'] = request.httprequest.session['ecommerce_pricelist']
27 order_value['partner_id'] = request.registry.get('res.users').browse(request.cr, SUPERUSER_ID, request.uid, context=request.context).partner_id.id
28 order_value.update(order_obj.onchange_partner_id(request.cr, SUPERUSER_ID, [], order_value['partner_id'], context=request.context)['value'])
30 # add website_session_id key for access rules
31 if not request.httprequest.session.get('website_session_id'):
32 request.httprequest.session['website_session_id'] = str(uuid.uuid4())
34 order_value["website_session_id"] = request.httprequest.session['website_session_id']
35 order_id = order_obj.create(request.cr, SUPERUSER_ID, order_value, context=request.context)
36 order = order_obj.browse(request.cr, SUPERUSER_ID, order_id, context=request.context)
37 request.httprequest.session['ecommerce_order_id'] = order.id
39 return order_obj.browse(request.cr, request.uid, order_id,
40 context=dict(request.context, pricelist=order.pricelist_id.id))
42 def get_current_order():
43 if request.httprequest.session.get('ecommerce_order_id'):
44 order = get_order(request.httprequest.session.get('ecommerce_order_id'))
46 request.httprequest.session['ecommerce_order_id'] = False
51 class Website(osv.osv):
53 def preprocess_request(self, cr, uid, ids, *args, **kwargs):
54 request.context.update({
55 'website_sale_order': get_current_order(),
57 return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
59 class Ecommerce(http.Controller):
61 _order = 'website_sequence desc, website_published desc'
63 def get_categories(self):
64 domain = [('parent_id', '=', False)]
66 category_obj = request.registry.get('product.public.category')
67 category_ids = category_obj.search(request.cr, SUPERUSER_ID, domain, context=request.context)
68 categories = category_obj.browse(request.cr, SUPERUSER_ID, category_ids, context=request.context)
70 product_obj = request.registry.get('product.product')
71 groups = product_obj.read_group(request.cr, SUPERUSER_ID, [("sale_ok", "=", True), ('website_published', '=', True)], ['public_categ_id'], 'public_categ_id', context=request.context)
72 full_category_ids = [group['public_categ_id'][0] for group in groups if group['public_categ_id']]
74 for cat_id in category_obj.browse(request.cr, SUPERUSER_ID, full_category_ids, context=request.context):
75 while cat_id.parent_id:
76 cat_id = cat_id.parent_id
77 full_category_ids.append(cat_id.id)
78 full_category_ids.append(1)
80 return (categories, full_category_ids)
82 def get_bin_packing_products(self, product_ids, fill_hole, col_number=4):
84 Packing all products of the search into a table of #col_number columns in function of the product sizes
85 The website_size_x, website_size_y is use for fill table (default 1x1)
86 The website_style_ids datas are concatenate in a html class
90 product_ids: list of product template
91 fill_hole: list of extra product template use to fill the holes
92 col_number: number of columns
96 table (list of list of #col_number items)
98 'product': browse of product template,
104 product_obj = request.registry.get('product.template')
105 style_obj = request.registry.get('website.product.style')
106 request.context['pricelist'] = self.get_pricelist()
108 # search for checking of access rules and keep order
109 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)]
112 style_ids = style_obj.search(request.cr, SUPERUSER_ID, [('html_class', 'like', 'size_%')], context=request.context)
113 for style in style_obj.browse(request.cr, SUPERUSER_ID, style_ids, context=request.context):
114 size_ids[style.id] = [int(style.html_class[-3]), int(style.html_class[-1])]
120 for product in product_obj.browse(request.cr, SUPERUSER_ID, product_ids, context=request.context):
121 index = len(product_list)
123 # get size and all html classes
124 x = product.website_size_x or 1
125 y = product.website_size_y or 1
126 html_class = " ".join([str(style_id.html_class) for style_id in product.website_style_ids])
128 product_list.append({'product': product, 'x': x, 'y': y, 'class': html_class })
130 # bin packing products
134 # if not full column get next line
135 if len(bin_packing.setdefault(line, {})) >= col_number:
140 while col < col_number:
141 if bin_packing[line].get(col, None) != None:
147 # check if the box can be inserted
154 if copy_col >= col_number or bin_packing.setdefault(copy_line, {}).get(copy_col, None) != None:
175 bin_packing[line + copy_y][col + copy_x] = False
176 bin_packing[line + copy_y][col + copy_x] = product_list[index]
184 length = len(bin_packing)
186 # browse product to fill the holes
188 fill_hole_products = []
189 # search for checking of access rules and keep order
190 fill_hole = [id for id in fill_hole if id in product_obj.search(request.cr, request.uid, [("id", 'in', fill_hole)], context=request.context)]
191 for product in product_obj.browse(request.cr, request.uid, fill_hole, context=request.context):
192 fill_hole_products.append(product)
193 fill_hole_products.reverse()
195 # packaging in list (from dict)
196 bin_packing_list = []
199 bin_packing_list.append([])
201 while col < col_number:
202 if fill_hole and fill_hole_products and bin_packing[line].get(col) == None:
203 bin_packing[line][col] = {'product': fill_hole_products.pop(), 'x': 1, 'y': 1, 'class': "" }
204 bin_packing_list[line].append(bin_packing[line].get(col))
208 return bin_packing_list
210 def get_products(self, product_ids):
211 product_obj = request.registry.get('product.template')
212 request.context['pricelist'] = self.get_pricelist()
213 # search for checking of access rules and keep order
214 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)]
215 return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
217 @website.route(['/shop/', '/shop/category/<cat_id>/', '/shop/category/<cat_id>/page/<int:page>/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
218 def category(self, cat_id=0, page=0, **post):
221 self.change_pricelist(post.get('promo'))
222 product_obj = request.registry.get('product.template')
224 domain = [("sale_ok", "=", True)]
225 #domain += [('website_published', '=', True)]
227 if post.get("search"):
228 domain += ['|', '|', '|',
229 ('name', 'ilike', "%%%s%%" % post.get("search")),
230 ('description', 'ilike', "%%%s%%" % post.get("search")),
231 ('website_description', 'ilike', "%%%s%%" % post.get("search")),
232 ('product_variant_ids.public_categ_id.name', 'ilike', "%%%s%%" % post.get("search"))]
235 domain += [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
238 product_count = len(product_obj.search(request.cr, request.uid, domain, context=request.context))
239 pager = request.website.pager(url="/shop/category/%s/" % cat_id, total=product_count, page=page, step=step, scope=7, url_args=post)
241 request.context['pricelist'] = self.get_pricelist()
243 product_ids = product_obj.search(request.cr, request.uid, domain, limit=step, offset=pager['offset'], order=self._order, context=request.context)
244 fill_hole = product_obj.search(request.cr, request.uid, domain, limit=step, offset=pager['offset']+step, order=self._order, context=request.context)
247 if not request.context['is_public_user']:
248 style_obj = request.registry.get('website.product.style')
249 style_ids = style_obj.search(request.cr, request.uid, [(1, '=', 1)], context=request.context)
250 styles = style_obj.browse(request.cr, request.uid, style_ids, context=request.context)
253 'get_categories': self.get_categories,
254 'category_id': cat_id,
255 'product_ids': product_ids,
256 'product_ids_for_holes': fill_hole,
257 'get_bin_packing_products': self.get_bin_packing_products,
258 'get_products': self.get_products,
259 'search': post.get("search"),
262 'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids]
264 return request.website.render("website_sale.products", values)
266 @website.route(['/shop/product/<int:product_id>/'], type='http', auth="public", multilang=True)
267 def product(self, cat_id=0, product_id=0, **post):
270 self.change_pricelist(post.get('promo'))
272 product_obj = request.registry.get('product.template')
273 category_obj = request.registry.get('product.public.category')
275 category_ids = category_obj.search(request.cr, request.uid, [(1, '=', 1)], context=request.context)
276 category_list = category_obj.name_get(request.cr, request.uid, category_ids, context=request.context)
277 category_list = sorted(category_list, key=lambda category: category[1])
280 if post.get('category_id') and int(post.get('category_id')):
281 category = category_obj.browse(request.cr, request.uid, int(post.get('category_id')), context=request.context)
283 request.context['pricelist'] = self.get_pricelist()
284 product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
287 'category_id': post.get('category_id') and int(post.get('category_id')) or None,
288 'category': category,
289 'search': post.get("search"),
290 'get_categories': self.get_categories,
291 'category_list': category_list,
292 'main_object': product,
295 return request.website.render("website_sale.product", values)
297 @website.route(['/shop/add_product/', '/shop/category/<int:cat_id>/add_product/'], type='http', auth="public", multilang=True)
298 def add_product(self, cat_id=0, **post):
299 product_id = request.registry.get('product.product').create(request.cr, request.uid,
300 {'name': 'New Product', 'public_categ_id': cat_id}, request.context)
301 return request.redirect("/shop/product/%s/?unable_editor=1" % product_id)
303 def get_pricelist(self):
304 if not request.httprequest.session.get('ecommerce_pricelist'):
305 self.change_pricelist(None)
306 return request.httprequest.session.get('ecommerce_pricelist')
308 def change_pricelist(self, code):
309 request.httprequest.session.setdefault('ecommerce_pricelist', False)
313 pricelist_obj = request.registry.get('product.pricelist')
314 pricelist_ids = pricelist_obj.search(request.cr, SUPERUSER_ID, [('code', '=', code)], context=request.context)
316 pricelist_id = pricelist_ids[0]
319 partner_id = request.registry.get('res.users').browse(request.cr, SUPERUSER_ID, request.uid, request.context).partner_id.id
320 pricelist_id = request.registry['sale.order'].onchange_partner_id(request.cr, SUPERUSER_ID, [], partner_id, context=request.context)['value']['pricelist_id']
322 request.httprequest.session['ecommerce_pricelist'] = pricelist_id
324 order = get_current_order()
326 values = {'pricelist_id': pricelist_id}
327 values.update(order.onchange_pricelist_id(pricelist_id, None)['value'])
329 for line in order.order_line:
330 self.add_product_to_cart(order_line_id=line.id, number=0)
332 def add_product_to_cart(self, product_id=0, order_line_id=0, number=1, set_number=-1):
333 order_line_obj = request.registry.get('sale.order.line')
334 order_obj = request.registry.get('sale.order')
336 order = get_current_order()
340 request.context = dict(request.context, pricelist=self.get_pricelist())
344 # values initialisation
347 domain = [('order_id', '=', order.id)]
349 domain += [('id', '=', order_line_id)]
351 domain += [('product_id', '=', product_id)]
353 order_line_ids = order_line_obj.search(request.cr, SUPERUSER_ID, domain, context=request.context)
355 order_line = order_line_obj.read(request.cr, SUPERUSER_ID, order_line_ids, [], context=request.context)[0]
357 product_id = order_line['product_id'][0]
359 quantity = set_number
361 quantity = order_line['product_uom_qty'] + number
365 fields = [k for k, v in order_line_obj._columns.items()]
366 values = order_line_obj.default_get(request.cr, SUPERUSER_ID, fields, context=request.context)
369 # change and record value
370 vals = order_line_obj._recalculate_product_values(request.cr, request.uid, order_line_ids, product_id, context=request.context)
373 values['product_uom_qty'] = quantity
374 values['product_id'] = product_id
375 values['order_id'] = order.id
378 order_line_obj.write(request.cr, SUPERUSER_ID, order_line_ids, values, context=request.context)
380 order_line_obj.unlink(request.cr, SUPERUSER_ID, order_line_ids, context=request.context)
382 order_line_id = order_line_obj.create(request.cr, SUPERUSER_ID, values, context=request.context)
383 order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
385 return [quantity, order.get_total_quantity()]
387 @website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
388 def mycart(self, **post):
389 order = get_current_order()
390 prod_obj = request.registry.get('product.product')
393 self.change_pricelist(post.get('promo'))
398 for line in order.order_line:
399 suggested_ids += [p.id for p in line.product_id and line.product_id.suggested_product_ids or [] for line in order.order_line]
400 product_ids.append(line.product_id.id)
401 suggested_ids = list(set(suggested_ids) - set(product_ids))
403 suggested_ids = prod_obj.search(request.cr, request.uid, [('id', 'in', suggested_ids)], context=request.context)
405 # select 3 random products
406 suggested_products = []
407 while len(suggested_products) < 3 and suggested_ids:
408 index = random.randrange(0, len(suggested_ids))
409 suggested_products.append(suggested_ids.pop(index))
413 'get_categories': self.get_categories,
414 'suggested_products': prod_obj.browse(request.cr, request.uid, suggested_products, request.context),
416 return request.website.render("website_sale.mycart", values)
418 @website.route(['/shop/<path:path>/add_cart/', '/shop/add_cart/'], type='http', auth="public", multilang=True)
419 def add_cart(self, path=None, product_id=None, order_line_id=None, remove=None, **kw):
420 self.add_product_to_cart(product_id=product_id and int(product_id), order_line_id=order_line_id and int(order_line_id), number=(remove and -1 or 1))
421 return request.redirect("/shop/mycart/")
423 @website.route(['/shop/add_cart_json/'], type='json', auth="public")
424 def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
425 return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
427 @website.route(['/shop/set_cart_json/'], type='json', auth="public")
428 def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):
429 return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, set_number=set_number)
431 @website.route(['/shop/checkout/'], type='http', auth="public", multilang=True)
432 def checkout(self, **post):
433 classic_fields = ["name", "phone", "email", "street", "city", "state_id", "zip"]
434 rel_fields = ['country_id', 'state_id']
436 order = get_current_order()
438 if not order or order.state != 'draft' or not order.order_line:
439 return self.mycart(**post)
441 partner_obj = request.registry.get('res.partner')
442 user_obj = request.registry.get('res.users')
443 country_obj = request.registry.get('res.country')
444 country_state_obj = request.registry.get('res.country.state')
447 'shipping': post.get("shipping"),
448 'error': post.get("error") and dict.fromkeys(post.get("error").split(","), 'error') or {}
451 checkout = dict((field_name, '') for field_name in classic_fields + rel_fields)
452 if not request.context['is_public_user']:
453 partner = user_obj.browse(request.cr, request.uid, request.uid, request.context).partner_id
454 checkout.update(dict((field_name, getattr(partner, field_name)) for field_name in classic_fields if getattr(partner, field_name)))
455 checkout['state_id'] = partner.state_id and partner.state_id.id or ''
456 checkout['country_id'] = partner.country_id and partner.country_id.id or ''
457 checkout['company'] = partner.parent_id and partner.parent_id.name or ''
459 shipping_ids = partner_obj.search(request.cr, request.uid, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], context=request.context)
461 for k, v in partner_obj.read(request.cr, request.uid, shipping_ids[0], request.context).items():
462 checkout['shipping_'+k] = v or ''
464 values['checkout'] = checkout
465 countries_ids = country_obj.search(request.cr, SUPERUSER_ID, [(1, "=", 1)], context=request.context)
466 values['countries'] = country_obj.browse(request.cr, SUPERUSER_ID, countries_ids, request.context)
467 states_ids = country_state_obj.search(request.cr, SUPERUSER_ID, [(1, "=", 1)], context=request.context)
468 values['states'] = country_state_obj.browse(request.cr, SUPERUSER_ID, states_ids, request.context)
470 return request.website.render("website_sale.checkout", values)
472 @website.route(['/shop/confirm_order/'], type='http', auth="public", multilang=True)
473 def confirm_order(self, **post):
474 order = get_current_order()
477 partner_obj = request.registry.get('res.partner')
478 user_obj = request.registry.get('res.users')
480 if order.state != 'draft':
481 return request.redirect("/shop/checkout/")
482 if not order.order_line:
483 error.append("empty_cart")
484 return request.redirect("/shop/checkout/")
487 request.session['checkout'] = post
488 required_field = ['phone', 'zip', 'email', 'street', 'city', 'name', 'country_id']
489 for key in required_field:
490 if not post.get(key):
492 if post.get('shipping_different') and key != 'email' and not post.get("shipping_%s" % key):
493 error.append("shipping_%s" % key)
495 return request.redirect("/shop/checkout/?error=%s&shipping=%s" % (",".join(error), post.get('shipping_different') and 'on' or ''))
497 # search or create company
500 company_ids = partner_obj.search(request.cr, SUPERUSER_ID, [("name", "ilike", post['company']), ('is_company', '=', True)], context=request.context)
501 company_id = company_ids and company_ids[0] or None
503 company_id = partner_obj.create(request.cr, SUPERUSER_ID, {'name': post['company'], 'is_company': True}, request.context)
506 'phone': post['phone'],
508 'email': post['email'],
509 'street': post['street'],
510 'city': post['city'],
511 'name': post['name'],
512 'parent_id': company_id,
513 'country_id': post['country_id'],
514 'state_id': post['state_id'],
516 if not request.context['is_public_user']:
517 partner_id = user_obj.browse(request.cr, request.uid, request.uid, context=request.context).partner_id.id
518 partner_obj.write(request.cr, request.uid, [partner_id], partner_value, context=request.context)
520 partner_id = partner_obj.create(request.cr, SUPERUSER_ID, partner_value, context=request.context)
524 if post.get('shipping_different'):
526 'phone': post['shipping_phone'],
527 'zip': post['shipping_zip'],
528 'street': post['shipping_street'],
529 'city': post['shipping_city'],
530 'name': post['shipping_name'],
532 'parent_id': partner_id,
533 'country_id': post['shipping_country_id'],
534 'state_id': post['shipping_state_id'],
536 domain = [(key, '_id' in key and '=' or 'ilike', '_id' in key and value and int(value) or False)
537 for key, value in shipping_value.items() if key in required_field + ["type", "parent_id"]]
539 shipping_ids = partner_obj.search(request.cr, SUPERUSER_ID, domain, context=request.context)
541 shipping_id = shipping_ids[0]
542 partner_obj.write(request.cr, SUPERUSER_ID, [shipping_id], shipping_value, request.context)
544 shipping_id = partner_obj.create(request.cr, SUPERUSER_ID, shipping_value, request.context)
547 'partner_id': partner_id,
548 'message_follower_ids': [(4, partner_id)],
549 'partner_invoice_id': partner_id,
550 'partner_shipping_id': shipping_id or partner_id
552 order_value.update(request.registry.get('sale.order').onchange_partner_id(request.cr, SUPERUSER_ID, [], order.partner_id.id, context=request.context)['value'])
553 order.write(order_value)
555 return request.redirect("/shop/payment/")
557 @website.route(['/shop/payment/'], type='http', auth="public", multilang=True)
558 def payment(self, **post):
559 order = get_current_order()
561 if not order or not order.order_line:
562 return self.mycart(**post)
569 payment_obj = request.registry.get('portal.payment.acquirer')
570 payment_ids = payment_obj.search(request.cr, SUPERUSER_ID, [('visible', '=', True)], context=request.context)
571 values['payments'] = payment_obj.browse(request.cr, SUPERUSER_ID, payment_ids, request.context)
572 for payment in values['payments']:
573 content = payment_obj.render(request.cr, SUPERUSER_ID, payment.id, order, order.name, order.pricelist_id.currency_id, order.amount_total)
574 payment._content = content
576 return request.website.render("website_sale.payment", values)
578 @website.route(['/shop/payment_validate/'], type='http', auth="public", multilang=True)
579 def payment_validate(self, **post):
580 request.httprequest.session['ecommerce_order_id'] = False
581 request.httprequest.session['ecommerce_pricelist'] = False
582 return request.redirect("/shop/")
584 @website.route(['/shop/change_sequence/'], type='json', auth="public")
585 def change_sequence(self, id, top):
586 product_obj = request.registry.get('product.template')
588 product_obj.set_sequence_top(request.cr, request.uid, [id], context=request.context)
590 product_obj.set_sequence_bottom(request.cr, request.uid, [id], context=request.context)
592 @website.route(['/shop/change_styles/'], type='json', auth="public")
593 def change_styles(self, id, style_id):
594 product_obj = request.registry.get('product.template')
595 product = product_obj.browse(request.cr, request.uid, id, context=request.context)
599 for style in product.website_style_ids:
600 if style.id == style_id:
601 remove.append(style.id)
605 style = request.registry.get('website.product.style').browse(request.cr, request.uid, style_id, context=request.context)
608 product.write({'website_style_ids': [(3, rid) for rid in remove]})
610 product.write({'website_style_ids': [(4, style.id)]})
614 @website.route(['/shop/change_size/'], type='json', auth="public")
615 def change_size(self, id, x, y):
616 product_obj = request.registry.get('product.template')
617 product = product_obj.browse(request.cr, request.uid, id, context=request.context)
618 return product.write({'website_size_x': x, 'website_size_y': y})
620 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: