From 890573936d204e949f200c55fb0af9a442babf4b Mon Sep 17 00:00:00 2001 From: Farid Shahy Date: Thu, 3 Dec 2015 09:55:39 +0330 Subject: [PATCH 01/12] [IMP] init website_seo_module (#20) --- website_seo_sale/README.md | 0 website_seo_sale/__init__.py | 2 + website_seo_sale/__openerp__.py | 22 + website_seo_sale/controllers/__init__.py | 2 + website_seo_sale/controllers/main.py | 888 +++++++++++++++++++++++ website_seo_sale/models/__init__.py | 1 + website_seo_sale/models/product.py | 0 website_seo_sale/views/templates.xml | 83 +++ 8 files changed, 998 insertions(+) create mode 100644 website_seo_sale/README.md create mode 100644 website_seo_sale/__init__.py create mode 100644 website_seo_sale/__openerp__.py create mode 100644 website_seo_sale/controllers/__init__.py create mode 100644 website_seo_sale/controllers/main.py create mode 100644 website_seo_sale/models/__init__.py create mode 100644 website_seo_sale/models/product.py create mode 100644 website_seo_sale/views/templates.xml diff --git a/website_seo_sale/README.md b/website_seo_sale/README.md new file mode 100644 index 0000000..e69de29 diff --git a/website_seo_sale/__init__.py b/website_seo_sale/__init__.py new file mode 100644 index 0000000..9f86759 --- /dev/null +++ b/website_seo_sale/__init__.py @@ -0,0 +1,2 @@ +import controllers +import models diff --git a/website_seo_sale/__openerp__.py b/website_seo_sale/__openerp__.py new file mode 100644 index 0000000..ecaf2a5 --- /dev/null +++ b/website_seo_sale/__openerp__.py @@ -0,0 +1,22 @@ +{ + 'name': 'eCommerce SEO Optimized', + 'category': 'Website', + 'sequence': 55, + 'summary': 'Sell Your Products Online', + 'website': 'http://www.bloopark.de', + 'version': '1.0', + 'description': """ +OpenERP E-Commerce SEO Optimized +================== + + """, + 'depends': [ 'website_sale',], + 'data': [ + 'views/templates.xml', + ], + 'demo': [ + ], + 'qweb': ['static/src/xml/*.xml'], + 'installable': True, + 'application': True, +} diff --git a/website_seo_sale/controllers/__init__.py b/website_seo_sale/controllers/__init__.py new file mode 100644 index 0000000..615db5a --- /dev/null +++ b/website_seo_sale/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -* +import main \ No newline at end of file diff --git a/website_seo_sale/controllers/main.py b/website_seo_sale/controllers/main.py new file mode 100644 index 0000000..b84b875 --- /dev/null +++ b/website_seo_sale/controllers/main.py @@ -0,0 +1,888 @@ +import werkzeug + +from openerp import SUPERUSER_ID +from openerp import http +from openerp import tools +from openerp.http import request +from openerp.tools.translate import _ +from openerp.addons.website.models.website import slug +from openerp.addons.website_sale.controllers.main import website_sale, PPG, PPR, QueryURL, table_compute + +class website_seo_sale(website_sale): + + @http.route(['/shop/change_pricelist/'], type='http', auth="public", website=True) + def pricelist_change(self, pl_id, **post): + if request.website.is_pricelist_available(pl_id.id, context=request.context): + request.session['website_sale_current_pl'] = pl_id.id + request.website.sale_get_order(force_pricelist=pl_id.id, context=request.context) + return request.redirect(request.httprequest.referrer or '/shop') + + @http.route([ + '/shop', + '/shop/page/', + '/shop/category/', + '/shop/category//page/' + ], type='http', auth="public", website=True) + def shop(self, page=0, category=None, search='', ppg=False, **post): + + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + domain = request.website.sale_product_domain() + + if ppg: + try: + ppg = int(ppg) + except ValueError: + ppg = PPG + post["ppg"] = ppg + else: + ppg = PPG + + if search: + for srch in search.split(" "): + domain += ['|', '|', '|', ('name', 'ilike', srch), ('description', 'ilike', srch), + ('description_sale', 'ilike', srch), ('product_variant_ids.default_code', 'ilike', srch)] + if category: + domain += [('public_categ_ids', 'child_of', int(category))] + attrib_list = request.httprequest.args.getlist('attrib') + attrib_values = [map(int,v.split("-")) for v in attrib_list if v] + attrib_set = set([v[1] for v in attrib_values]) + + if attrib_values: + attrib = None + ids = [] + for value in attrib_values: + if not attrib: + attrib = value[0] + ids.append(value[1]) + elif value[0] == attrib: + ids.append(value[1]) + else: + domain += [('attribute_line_ids.value_ids', 'in', ids)] + attrib = value[0] + ids = [value[1]] + if attrib: + domain += [('attribute_line_ids.value_ids', 'in', ids)] + + keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_list) + + if not context.get('pricelist'): + pricelist = self.get_pricelist() + context['pricelist'] = int(pricelist) + else: + pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context) + url = "/shop" + if search: + post["search"] = search + if category: + category = pool['product.public.category'].browse(cr, uid, int(category), context=context) + url = "/shop/category/%s" % slug(category) + if attrib_list: + post['attrib'] = attrib_list + + style_obj = pool['product.style'] + style_ids = style_obj.search(cr, uid, [], context=context) + styles = style_obj.browse(cr, uid, style_ids, context=context) + + category_obj = pool['product.public.category'] + category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) + categs = category_obj.browse(cr, uid, category_ids, context=context) + + product_obj = pool.get('product.template') + + parent_category_ids = [] + if category: + parent_category_ids = [category.id] + current_category = category + while current_category.parent_id: + parent_category_ids.append(current_category.parent_id.id) + current_category = current_category.parent_id + + product_count = product_obj.search_count(cr, uid, domain, context=context) + pager = request.website.pager(url=url, total=product_count, page=page, step=ppg, scope=7, url_args=post) + product_ids = product_obj.search(cr, uid, domain, limit=ppg, offset=pager['offset'], order='website_published desc, website_sequence asc', context=context) + products = product_obj.browse(cr, uid, product_ids, context=context) + + attributes_obj = request.registry['product.attribute'] + attributes_ids = attributes_obj.search(cr, uid, [('attribute_line_ids.product_tmpl_id', 'in', product_ids)], context=context) + attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context) + + from_currency = pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + to_currency = pricelist.currency_id + compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context) + + values = { + 'search': search, + 'category': category, + 'attrib_values': attrib_values, + 'attrib_set': attrib_set, + 'pager': pager, + 'pricelist': pricelist, + 'products': products, + 'bins': table_compute().process(products, ppg), + 'rows': PPR, + 'styles': styles, + 'categories': categs, + 'attributes': attributes, + 'compute_currency': compute_currency, + 'keep': keep, + 'parent_category_ids': parent_category_ids, + 'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids], + 'attrib_encode': lambda attribs: werkzeug.url_encode([('attrib',i) for i in attribs]), + } + if category: + values['main_object'] = category + return request.website.render("website_sale.products", values) + + @http.route(['/shop/'], type='http', auth="public", website=True) + def product(self, product, category='', search='', **kwargs): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + category_obj = pool['product.public.category'] + template_obj = pool['product.template'] + + context.update(active_id=product.id) + + if category: + category = category_obj.browse(cr, uid, int(category), context=context) + category = category if category.exists() else False + + attrib_list = request.httprequest.args.getlist('attrib') + attrib_values = [map(int,v.split("-")) for v in attrib_list if v] + attrib_set = set([v[1] for v in attrib_values]) + + keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list) + + category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) + categs = category_obj.browse(cr, uid, category_ids, context=context) + + pricelist = self.get_pricelist() + + from_currency = pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + to_currency = pricelist.currency_id + compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context) + + # get the rating attached to a mail.message, and the rating stats of the product + Rating = pool['rating.rating'] + rating_ids = Rating.search(cr, uid, [('message_id', 'in', product.website_message_ids.ids)], context=context) + ratings = Rating.browse(cr, uid, rating_ids, context=context) + rating_message_values = dict([(record.message_id.id, record.rating) for record in ratings]) + rating_product = product.rating_get_stats([('website_published', '=', True)]) + + if not context.get('pricelist'): + context['pricelist'] = int(self.get_pricelist()) + product = template_obj.browse(cr, uid, int(product), context=context) + + values = { + 'search': search, + 'category': category, + 'pricelist': pricelist, + 'attrib_values': attrib_values, + 'compute_currency': compute_currency, + 'attrib_set': attrib_set, + 'keep': keep, + 'categories': categs, + 'main_object': product, + 'product': product, + 'get_attribute_value_ids': self.get_attribute_value_ids, + 'rating_message_values' : rating_message_values, + 'rating_product' : rating_product + } + return request.website.render("website_sale.product", values) + + @http.route(['/shop/pricelist'], type='http', auth="public", website=True) + def pricelist(self, promo, **post): + cr, uid, pool, context = request.cr, request.uid, request.registry, request.context + pl = pool['product.pricelist'].search(cr, SUPERUSER_ID, [('code', '=', promo)], context=context) + if pl: + if not request.website.is_pricelist_available(pl[0], context=context): + return request.redirect("/shop/cart?code_not_available=1") + + request.website.sale_get_order(code=promo, context=context) + return request.redirect("/shop/cart") + + @http.route(['/shop/cart'], type='http', auth="public", website=True) + def cart(self, **post): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + order = request.website.sale_get_order() + if order: + from_currency = order.company_id.currency_id + to_currency = order.pricelist_id.currency_id + compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context) + else: + compute_currency = lambda price: price + + values = { + 'website_sale_order': order, + 'compute_currency': compute_currency, + 'suggested_products': [], + } + if order: + _order = order + if not context.get('pricelist'): + _order = order.with_context(pricelist=order.pricelist_id.id) + values['suggested_products'] = _order._cart_accessories() + + if post.get('type') == 'popover': + return request.website.render("website_sale.cart_popover", values) + + if post.get('code_not_available'): + values['code_not_available'] = post.get('code_not_available') + + return request.website.render("website_sale.cart", values) + + @http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True) + def cart_update(self, product_id, add_qty=1, set_qty=0, **kw): + request.website.sale_get_order(force_create=1)._cart_update(product_id=int(product_id), add_qty=float(add_qty), set_qty=float(set_qty)) + return request.redirect("/shop/cart") + + @http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True) + def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None, display=True): + order = request.website.sale_get_order(force_create=1) + if order.state != 'draft': + request.website.sale_reset() + return {} + + value = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty) + if not order.cart_quantity: + request.website.sale_reset() + return {} + if not display: + return None + value['cart_quantity'] = order.cart_quantity + value['website_sale.total'] = request.website._render("website_sale.total", { + 'website_sale_order': request.website.sale_get_order() + }) + return value + + #------------------------------------------------------ + # Checkout + #------------------------------------------------------ + + def checkout_redirection(self, order): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + + # must have a draft sale order with lines at this point, otherwise reset + if not order or order.state != 'draft': + request.session['sale_order_id'] = None + request.session['sale_transaction_id'] = None + return request.redirect('/shop') + + # if transaction pending / done: redirect to confirmation + tx = context.get('website_sale_transaction') + if tx and tx.state != 'draft': + return request.redirect('/shop/payment/confirmation/%s' % order.id) + + def checkout_values(self, data=None): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + orm_partner = registry.get('res.partner') + orm_user = registry.get('res.users') + orm_country = registry.get('res.country') + state_orm = registry.get('res.country.state') + + country_ids = orm_country.search(cr, SUPERUSER_ID, [], context=context) + countries = orm_country.browse(cr, SUPERUSER_ID, country_ids, context) + states_ids = state_orm.search(cr, SUPERUSER_ID, [], context=context) + states = state_orm.browse(cr, SUPERUSER_ID, states_ids, context) + partner = orm_user.browse(cr, SUPERUSER_ID, request.uid, context).partner_id + + order = None + + shipping_id = data and data.get('shipping_id') or None + shipping_ids = [] + checkout = {} + if not data: + if request.uid != request.website.user_id.id: + checkout.update( self.checkout_parse("billing", partner) ) + shipping_ids = orm_partner.search(cr, SUPERUSER_ID, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], context=context) + else: + order = request.website.sale_get_order(force_create=1, context=context) + if order.partner_id: + domain = [("partner_id", "=", order.partner_id.id)] + user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, domain, context=dict(context or {}, active_test=False)) + if not user_ids or request.website.user_id.id not in user_ids: + checkout.update( self.checkout_parse("billing", order.partner_id) ) + else: + checkout = self.checkout_parse('billing', data) + try: + shipping_id = int(shipping_id) + except (ValueError, TypeError): + pass + if shipping_id == -1: + checkout.update(self.checkout_parse('shipping', data)) + + if shipping_id is None: + if not order: + order = request.website.sale_get_order(context=context) + if order and order.partner_shipping_id: + shipping_id = order.partner_shipping_id.id + + shipping_ids = list(set(shipping_ids) - set([partner.id])) + + if shipping_id == partner.id: + shipping_id = 0 + elif shipping_id > 0 and shipping_id not in shipping_ids: + shipping_ids.append(shipping_id) + elif shipping_id is None and shipping_ids: + shipping_id = shipping_ids[0] + + ctx = dict(context, show_address=1) + shippings = [] + if shipping_ids: + shippings = shipping_ids and orm_partner.browse(cr, SUPERUSER_ID, list(shipping_ids), ctx) or [] + if shipping_id > 0: + shipping = orm_partner.browse(cr, SUPERUSER_ID, shipping_id, ctx) + checkout.update( self.checkout_parse("shipping", shipping) ) + + checkout['shipping_id'] = shipping_id + + # Default search by user country + if not checkout.get('country_id'): + country_code = request.session['geoip'].get('country_code') + if country_code: + country_ids = request.registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context) + if country_ids: + checkout['country_id'] = country_ids[0] + + values = { + 'countries': countries, + 'states': states, + 'checkout': checkout, + 'shipping_id': partner.id != shipping_id and shipping_id or 0, + 'shippings': shippings, + 'error': {}, + 'has_check_vat': hasattr(registry['res.partner'], 'check_vat'), + 'only_services': order and order.only_services or False + } + + return values + + mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"] + optional_billing_fields = ["street", "state_id", "vat", "zip"] + mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id"] + optional_shipping_fields = ["state_id", "zip"] + + def checkout_parse(self, address_type, data, remove_prefix=False): + """ data is a dict OR a partner browse record + """ + # set mandatory and optional fields + assert address_type in ('billing', 'shipping') + if address_type == 'billing': + all_fields = self.mandatory_billing_fields + self.optional_billing_fields + prefix = '' + else: + all_fields = self.mandatory_shipping_fields + self.optional_shipping_fields + prefix = 'shipping_' + + # set data + if isinstance(data, dict): + query = dict((prefix + field_name, data[prefix + field_name]) + for field_name in all_fields if prefix + field_name in data) + else: + query = dict((prefix + field_name, getattr(data, field_name)) + for field_name in all_fields if getattr(data, field_name)) + if address_type == 'billing' and data.parent_id: + query[prefix + 'street'] = data.parent_id.name + + if query.get(prefix + 'state_id'): + query[prefix + 'state_id'] = int(query[prefix + 'state_id']) + if query.get(prefix + 'country_id'): + query[prefix + 'country_id'] = int(query[prefix + 'country_id']) + + if not remove_prefix: + return query + + return dict((field_name, data[prefix + field_name]) for field_name in all_fields if prefix + field_name in data) + + def checkout_form_validate(self, data): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + + error = dict() + error_message = [] + + # Validation + for field_name in self.mandatory_billing_fields: + if not data.get(field_name): + error[field_name] = 'missing' + + # email validation + if data.get('email') and not tools.single_email_re.match(data.get('email')): + error["email"] = 'error' + error_message.append(_('Invalid Email! Please enter a valid email address.')) + + # vat validation + if data.get("vat") and hasattr(registry["res.partner"], "check_vat"): + if request.website.company_id.vat_check_vies: + # force full VIES online check + check_func = registry["res.partner"].vies_vat_check + else: + # quick and partial off-line checksum validation + check_func = registry["res.partner"].simple_vat_check + vat_country, vat_number = registry["res.partner"]._split_vat(data.get("vat")) + if not check_func(cr, uid, vat_country, vat_number, context=None): # simple_vat_check + error["vat"] = 'error' + + if data.get("shipping_id") == -1: + for field_name in self.mandatory_shipping_fields: + field_name = 'shipping_' + field_name + if not data.get(field_name): + error[field_name] = 'missing' + + # error message for empty required fields + if [err for err in error.values() if err == 'missing']: + error_message.append(_('Some required fields are empty.')) + + return error, error_message + + def checkout_form_save(self, checkout): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + + order = request.website.sale_get_order(force_create=1, context=context) + + orm_partner = registry.get('res.partner') + orm_user = registry.get('res.users') + order_obj = request.registry.get('sale.order') + + partner_lang = request.lang if request.lang in [lang.code for lang in request.website.language_ids] else None + + billing_info = {'customer': True} + if partner_lang: + billing_info['lang'] = partner_lang + billing_info.update(self.checkout_parse('billing', checkout, True)) + + # set partner_id + partner_id = None + if request.uid != request.website.user_id.id: + partner_id = orm_user.browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id + elif order.partner_id: + user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, + [("partner_id", "=", order.partner_id.id)], context=dict(context or {}, active_test=False)) + if not user_ids or request.website.user_id.id not in user_ids: + partner_id = order.partner_id.id + + # save partner informations + if billing_info.get('country_id'): + billing_info['property_account_position_id'] = request.registry['account.fiscal.position']._get_fpos_by_region( + cr, SUPERUSER_ID, billing_info['country_id'], billing_info.get('state_id') or False, billing_info.get('zip'), billing_info.get('vat') and True or False) + if partner_id and request.website.partner_id.id != partner_id: + orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context) + else: + # create partner + billing_info['team_id'] = request.website.salesteam_id.id + partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context) + order.write({'partner_id': partner_id, 'partner_invoice_id': partner_id}) + order_obj.onchange_partner_id(cr, SUPERUSER_ID, [order.id], context=context) + + # create a new shipping partner + if checkout.get('shipping_id') == -1: + shipping_info = {} + if partner_lang: + shipping_info['lang'] = partner_lang + shipping_info.update(self.checkout_parse('shipping', checkout, True)) + shipping_info['type'] = 'delivery' + shipping_info['parent_id'] = partner_id + checkout['shipping_id'] = orm_partner.create(cr, SUPERUSER_ID, shipping_info, context) + order.write({'partner_shipping_id': checkout.get('shipping_id')}) + order_obj.onchange_partner_shipping_id(cr, SUPERUSER_ID, [order.id], context=context) + + order_info = { + 'message_partner_ids': [(4, partner_id), (3, request.website.partner_id.id)], + } + order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context) + + @http.route(['/shop/checkout'], type='http', auth="public", website=True) + def checkout(self, **post): + cr, uid, context = request.cr, request.uid, request.context + + order = request.website.sale_get_order(force_create=1, context=context) + + redirection = self.checkout_redirection(order) + if redirection: + return redirection + + values = self.checkout_values() + + return request.website.render("website_sale.checkout", values) + + @http.route(['/shop/confirm_order'], type='http', auth="public", website=True) + def confirm_order(self, **post): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + + order = request.website.sale_get_order(context=context) + if not order: + return request.redirect("/shop") + + redirection = self.checkout_redirection(order) + if redirection: + return redirection + + values = self.checkout_values(post) + + values["error"], values["error_message"] = self.checkout_form_validate(values["checkout"]) + if values["error"]: + return request.website.render("website_sale.checkout", values) + + self.checkout_form_save(values["checkout"]) + + request.session['sale_last_order_id'] = order.id + + request.website.sale_get_order(update_pricelist=True, context=context) + + extra_step = registry['ir.model.data'].xmlid_to_object(cr, uid, 'website_sale.extra_info_option', raise_if_not_found=True) + if extra_step.active: + return request.redirect("/shop/extra_info") + + return request.redirect("/shop/payment") + + #------------------------------------------------------ + # Extra step + #------------------------------------------------------ + @http.route(['/shop/extra_info'], type='http', auth="public", website=True) + def extra_info(self, **post): + cr, uid, context, registry = request.cr, request.uid, request.context, request.registry + + # Check that this option is activated + extra_step = registry['ir.model.data'].xmlid_to_object(cr, uid, 'website_sale.extra_info_option', raise_if_not_found=True) + if not extra_step.active: + return request.redirect("/shop/payment") + + # check that cart is valid + order = request.website.sale_get_order(context=context) + redirection = self.checkout_redirection(order) + if redirection: + return redirection + + # if form posted + if 'post_values' in post: + values = {} + for field_name, field_value in post.items(): + if field_name in request.registry['sale.order']._fields and field_name.startswith('x_'): + values[field_name] = field_value + if values: + order.write(values) + return request.redirect("/shop/payment") + + values = { + 'website_sale_order': order + } + + sale_order_obj = request.registry.get('sale.order') + values.update(sale_order_obj._get_website_data(cr, uid, order, context)) + + return request.website.render("website_sale.extra_info", values) + + #------------------------------------------------------ + # Payment + #------------------------------------------------------ + + @http.route(['/shop/payment'], type='http', auth="public", website=True) + def payment(self, **post): + """ Payment step. This page proposes several payment means based on available + payment.acquirer. State at this point : + + - a draft sale order with lines; otherwise, clean context / session and + back to the shop + - no transaction in context / session, or only a draft one, if the customer + did go to a payment.acquirer website but closed the tab without + paying / canceling + """ + cr, uid, context = request.cr, request.uid, request.context + payment_obj = request.registry.get('payment.acquirer') + sale_order_obj = request.registry.get('sale.order') + + order = request.website.sale_get_order(context=context) + + redirection = self.checkout_redirection(order) + if redirection: + return redirection + + shipping_partner_id = False + if order: + if order.partner_shipping_id.id: + shipping_partner_id = order.partner_shipping_id.id + else: + shipping_partner_id = order.partner_invoice_id.id + + values = { + 'website_sale_order': order + } + values['errors'] = sale_order_obj._get_errors(cr, uid, order, context=context) + values.update(sale_order_obj._get_website_data(cr, uid, order, context)) + + if not values['errors']: + # find an already existing transaction + tx = request.website.sale_get_transaction() + acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), ('company_id', '=', order.company_id.id)], context=context) + values['acquirers'] = list(payment_obj.browse(cr, uid, acquirer_ids, context=context)) + render_ctx = dict(context, submit_class='btn btn-primary', submit_txt=_('Pay Now')) + for acquirer in values['acquirers']: + acquirer.button = payment_obj.render( + cr, SUPERUSER_ID, acquirer.id, + tx and tx.reference or request.env['payment.transaction'].get_next_reference(order.name), + order.amount_total, + order.pricelist_id.currency_id.id, + values={ + 'return_url': '/shop/payment/validate', + 'partner_id': shipping_partner_id + }, + context=render_ctx) + + return request.website.render("website_sale.payment", values) + + @http.route(['/shop/payment/transaction/'], type='json', auth="public", website=True) + def payment_transaction(self, acquirer_id): + """ Json method that creates a payment.transaction, used to create a + transaction when the user clicks on 'pay now' button. After having + created the transaction, the event continues and the user is redirected + to the acquirer website. + + :param int acquirer_id: id of a payment.acquirer record. If not set the + user is redirected to the checkout page + """ + cr, uid, context = request.cr, request.uid, request.context + transaction_obj = request.registry.get('payment.transaction') + order = request.website.sale_get_order(context=context) + + if not order or not order.order_line or acquirer_id is None: + return request.redirect("/shop/checkout") + + assert order.partner_id.id != request.website.partner_id.id + + # find an already existing transaction + tx = request.website.sale_get_transaction() + if tx: + if tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ? + tx.write({ + 'acquirer_id': acquirer_id, + 'amount': order.amount_total, + }) + tx_id = tx.id + else: + tx_id = transaction_obj.create(cr, SUPERUSER_ID, { + 'acquirer_id': acquirer_id, + 'type': 'form', + 'amount': order.amount_total, + 'currency_id': order.pricelist_id.currency_id.id, + 'partner_id': order.partner_id.id, + 'partner_country_id': order.partner_id.country_id.id, + 'reference': request.env['payment.transaction'].get_next_reference(order.name), + 'sale_order_id': order.id, + }, context=context) + request.session['sale_transaction_id'] = tx_id + tx = transaction_obj.browse(cr, SUPERUSER_ID, tx_id, context=context) + + # update quotation + request.registry['sale.order'].write( + cr, SUPERUSER_ID, [order.id], { + 'payment_acquirer_id': acquirer_id, + 'payment_tx_id': request.session['sale_transaction_id'] + }, context=context) + + # confirm the quotation + if tx.acquirer_id.auto_confirm == 'at_pay_now': + request.registry['sale.order'].action_confirm(cr, SUPERUSER_ID, [order.id], context=request.context) + + return tx_id + + @http.route('/shop/payment/get_status/', type='json', auth="public", website=True) + def payment_get_status(self, sale_order_id, **post): + cr, uid, context = request.cr, request.uid, request.context + + order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context) + assert order.id == request.session.get('sale_last_order_id') + + values = {} + flag = False + if not order: + values.update({'not_order': True, 'state': 'error'}) + else: + tx_ids = request.registry['payment.transaction'].search( + cr, SUPERUSER_ID, [ + '|', ('sale_order_id', '=', order.id), ('reference', '=', order.name) + ], context=context) + + if not tx_ids: + if order.amount_total: + values.update({'tx_ids': False, 'state': 'error'}) + else: + values.update({'tx_ids': False, 'state': 'done', 'validation': None}) + else: + tx = request.registry['payment.transaction'].browse(cr, SUPERUSER_ID, tx_ids[0], context=context) + state = tx.state + flag = state == 'pending' + values.update({ + 'tx_ids': True, + 'state': state, + 'acquirer_id': tx.acquirer_id, + 'validation': tx.acquirer_id.auto_confirm == 'none', + 'tx_post_msg': tx.acquirer_id.post_msg or None + }) + + return {'recall': flag, 'message': request.website._render("website_sale.order_state_message", values)} + + @http.route('/shop/payment/validate', type='http', auth="public", website=True) + def payment_validate(self, transaction_id=None, sale_order_id=None, **post): + """ Method that should be called by the server when receiving an update + for a transaction. State at this point : + + - UDPATE ME + """ + cr, uid, context = request.cr, request.uid, request.context + email_act = None + sale_order_obj = request.registry['sale.order'] + + if transaction_id is None: + tx = request.website.sale_get_transaction() + else: + tx = request.registry['payment.transaction'].browse(cr, uid, transaction_id, context=context) + + if sale_order_id is None: + order = request.website.sale_get_order(context=context) + else: + order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context) + assert order.id == request.session.get('sale_last_order_id') + + if not order or (order.amount_total and not tx): + return request.redirect('/shop') + + if (not order.amount_total and not tx) or tx.state in ['pending', 'done']: + if (not order.amount_total and not tx): + # Orders are confirmed by payment transactions, but there is none for free orders, + # (e.g. free events), so confirm immediately + order.with_context(dict(context, send_email=True)).action_confirm() + elif tx and tx.state == 'cancel': + # cancel the quotation + sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context) + + # clean context and session, then redirect to the confirmation page + request.website.sale_reset(context=context) + if tx and tx.state == 'draft': + return request.redirect('/shop') + + return request.redirect('/shop/confirmation') + + @http.route(['/shop/confirmation'], type='http', auth="public", website=True) + def payment_confirmation(self, **post): + """ End of checkout process controller. Confirmation is basically seing + the status of a sale.order. State at this point : + + - should not have any context / session info: clean them + - take a sale.order id, because we request a sale.order and are not + session dependant anymore + """ + cr, uid, context = request.cr, request.uid, request.context + + sale_order_id = request.session.get('sale_last_order_id') + if sale_order_id: + order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context) + else: + return request.redirect('/shop') + + return request.website.render("website_sale.confirmation", {'order': order}) + + @http.route(['/shop/print'], type='http', auth="public", website=True) + def print_saleorder(self): + cr, uid, context = request.cr, SUPERUSER_ID, request.context + sale_order_id = request.session.get('sale_last_order_id') + if sale_order_id: + pdf = request.registry['report'].get_pdf(cr, uid, [sale_order_id], 'sale.report_saleorder', data=None, context=context) + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] + return request.make_response(pdf, headers=pdfhttpheaders) + else: + return request.redirect('/shop') + + #------------------------------------------------------ + # Edit + #------------------------------------------------------ + + @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True) + def add_product(self, name=None, category=0, **post): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + if not name: + name = _("New Product") + product_obj = request.registry.get('product.product') + product_id = product_obj.create(cr, uid, { 'name': name, 'public_categ_ids': category }, context=context) + product = product_obj.browse(cr, uid, product_id, context=context) + + return request.redirect("/shop/%s?enable_editor=1" % slug(product.product_tmpl_id)) + + @http.route(['/shop/change_styles'], type='json', auth="public") + def change_styles(self, id, style_id): + product_obj = request.registry.get('product.template') + product = product_obj.browse(request.cr, request.uid, id, context=request.context) + + remove = [] + active = False + for style in product.website_style_ids: + if style.id == style_id: + remove.append(style.id) + active = True + break + + style = request.registry.get('product.style').browse(request.cr, request.uid, style_id, context=request.context) + + if remove: + product.write({'website_style_ids': [(3, rid) for rid in remove]}) + if not active: + product.write({'website_style_ids': [(4, style.id)]}) + + return not active + + @http.route(['/shop/change_sequence'], type='json', auth="public") + def change_sequence(self, id, sequence): + product_obj = request.registry.get('product.template') + if sequence == "top": + product_obj.set_sequence_top(request.cr, request.uid, [id], context=request.context) + elif sequence == "bottom": + product_obj.set_sequence_bottom(request.cr, request.uid, [id], context=request.context) + elif sequence == "up": + product_obj.set_sequence_up(request.cr, request.uid, [id], context=request.context) + elif sequence == "down": + product_obj.set_sequence_down(request.cr, request.uid, [id], context=request.context) + + @http.route(['/shop/change_size'], type='json', auth="public") + def change_size(self, id, x, y): + product_obj = request.registry.get('product.template') + product = product_obj.browse(request.cr, request.uid, id, context=request.context) + return product.write({'website_size_x': x, 'website_size_y': y}) + + def order_lines_2_google_api(self, order_lines): + """ Transforms a list of order lines into a dict for google analytics """ + ret = [] + for line in order_lines: + ret.append({ + 'id': line.order_id and line.order_id.id, + 'sku': line.product_id.id, + 'name': line.product_id.name or '-', + 'category': line.product_id.categ_id and line.product_id.categ_id.name or '-', + 'price': line.price_unit, + 'quantity': line.product_uom_qty, + }) + return ret + + @http.route(['/shop/tracking_last_order'], type='json', auth="public") + def tracking_cart(self, **post): + """ return data about order in JSON needed for google analytics""" + cr, context = request.cr, request.context + ret = {} + sale_order_id = request.session.get('sale_last_order_id') + if sale_order_id: + order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context) + ret['transaction'] = { + 'id': sale_order_id, + 'affiliation': order.company_id.name, + 'revenue': order.amount_total, + 'currency': order.currency_id.name + } + ret['lines'] = self.order_lines_2_google_api(order.order_line) + return ret + + @http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True) + def get_unit_price(self, product_ids, add_qty, use_order_pricelist=False, **kw): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + products = pool['product.product'].browse(cr, uid, product_ids, context=context) + partner = pool['res.users'].browse(cr, uid, uid, context=context).partner_id + if use_order_pricelist: + pricelist_id = request.website.get_current_pricelist(context=context).id + else: + pricelist_id = partner.property_product_pricelist.id + prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [], [(product, add_qty, partner) for product in products], context=context) + return {product_id: prices[product_id][pricelist_id][0] for product_id in product_ids} \ No newline at end of file diff --git a/website_seo_sale/models/__init__.py b/website_seo_sale/models/__init__.py new file mode 100644 index 0000000..bced331 --- /dev/null +++ b/website_seo_sale/models/__init__.py @@ -0,0 +1 @@ +import product \ No newline at end of file diff --git a/website_seo_sale/models/product.py b/website_seo_sale/models/product.py new file mode 100644 index 0000000..e69de29 diff --git a/website_seo_sale/views/templates.xml b/website_seo_sale/views/templates.xml new file mode 100644 index 0000000..0b32e80 --- /dev/null +++ b/website_seo_sale/views/templates.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From ade3efdbe8fdb4f2ffb75b4a6f98f67ae1d12202 Mon Sep 17 00:00:00 2001 From: Farid Shahy Date: Thu, 3 Dec 2015 21:39:14 +0330 Subject: [PATCH 02/12] [IMP] removed category from site urls (#20) --- website_seo_sale/__openerp__.py | 2 +- website_seo_sale/controllers/main.py | 8 ++-- website_seo_sale/models/product.py | 14 ++++++ website_seo_sale/views/templates.xml | 71 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/website_seo_sale/__openerp__.py b/website_seo_sale/__openerp__.py index ecaf2a5..8104bfc 100644 --- a/website_seo_sale/__openerp__.py +++ b/website_seo_sale/__openerp__.py @@ -18,5 +18,5 @@ ], 'qweb': ['static/src/xml/*.xml'], 'installable': True, - 'application': True, + 'application': False, } diff --git a/website_seo_sale/controllers/main.py b/website_seo_sale/controllers/main.py index b84b875..3e4ef3f 100644 --- a/website_seo_sale/controllers/main.py +++ b/website_seo_sale/controllers/main.py @@ -20,8 +20,8 @@ def pricelist_change(self, pl_id, **post): @http.route([ '/shop', '/shop/page/', - '/shop/category/', - '/shop/category//page/' + '/', + '//page/' ], type='http', auth="public", website=True) def shop(self, page=0, category=None, search='', ppg=False, **post): @@ -75,7 +75,7 @@ def shop(self, page=0, category=None, search='', ppg=False, **post): post["search"] = search if category: category = pool['product.public.category'].browse(cr, uid, int(category), context=context) - url = "/shop/category/%s" % slug(category) + url = "/%s" % slug(category) if attrib_list: post['attrib'] = attrib_list @@ -149,7 +149,7 @@ def product(self, product, category='', search='', **kwargs): attrib_values = [map(int,v.split("-")) for v in attrib_list if v] attrib_set = set([v[1] for v in attrib_values]) - keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list) + keep = QueryURL('/shop/', category=category and category.id, search=search, attrib=attrib_list) category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) categs = category_obj.browse(cr, uid, category_ids, context=context) diff --git a/website_seo_sale/models/product.py b/website_seo_sale/models/product.py index e69de29..b86f22d 100644 --- a/website_seo_sale/models/product.py +++ b/website_seo_sale/models/product.py @@ -0,0 +1,14 @@ +from openerp.osv import osv, fields + + +class product_template(osv.Model): + _inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin'] + _order = 'website_published desc, website_sequence desc, name' + _name = 'product.template' + _mail_post_access = 'read' + + def _website_url(self, cr, uid, ids, field_name, arg, context=None): + res = super(product_template, self)._website_url(cr, uid, ids, field_name, arg, context=context) + for product in self.browse(cr, uid, ids, context=context): + res[product.id] = "/shop/%s" % (product.id,) + return res \ No newline at end of file diff --git a/website_seo_sale/views/templates.xml b/website_seo_sale/views/templates.xml index 0b32e80..81d1452 100644 --- a/website_seo_sale/views/templates.xml +++ b/website_seo_sale/views/templates.xml @@ -3,6 +3,8 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From ff24c693243e54da36cc899f2da57a3302327f28 Mon Sep 17 00:00:00 2001 From: Farid Shahy Date: Sun, 6 Dec 2015 17:45:04 +0330 Subject: [PATCH 03/12] Website Module URLs\nCateory still exists in urls and product urls slugged. --- website_seo_sale/__openerp__.py | 1 + website_seo_sale/controllers/main.py | 13 ++++--- website_seo_sale/models/product.py | 21 ++++++++++- website_seo_sale/views/templates.xml | 55 ++++++---------------------- website_seo_sale/views/views.xml | 32 ++++++++++++++++ 5 files changed, 72 insertions(+), 50 deletions(-) create mode 100644 website_seo_sale/views/views.xml diff --git a/website_seo_sale/__openerp__.py b/website_seo_sale/__openerp__.py index 8104bfc..cacfd38 100644 --- a/website_seo_sale/__openerp__.py +++ b/website_seo_sale/__openerp__.py @@ -13,6 +13,7 @@ 'depends': [ 'website_sale',], 'data': [ 'views/templates.xml', + #'views/views.xml', ], 'demo': [ ], diff --git a/website_seo_sale/controllers/main.py b/website_seo_sale/controllers/main.py index 3e4ef3f..d118c1d 100644 --- a/website_seo_sale/controllers/main.py +++ b/website_seo_sale/controllers/main.py @@ -8,8 +8,11 @@ from openerp.addons.website.models.website import slug from openerp.addons.website_sale.controllers.main import website_sale, PPG, PPR, QueryURL, table_compute + class website_seo_sale(website_sale): + + @http.route(['/shop/change_pricelist/'], type='http', auth="public", website=True) def pricelist_change(self, pl_id, **post): if request.website.is_pricelist_available(pl_id.id, context=request.context): @@ -20,7 +23,7 @@ def pricelist_change(self, pl_id, **post): @http.route([ '/shop', '/shop/page/', - '/', + '/category/', '//page/' ], type='http', auth="public", website=True) def shop(self, page=0, category=None, search='', ppg=False, **post): @@ -63,14 +66,14 @@ def shop(self, page=0, category=None, search='', ppg=False, **post): if attrib: domain += [('attribute_line_ids.value_ids', 'in', ids)] - keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_list) + keep = QueryURL('/', category=category and int(category), search=search, attrib=attrib_list) if not context.get('pricelist'): pricelist = self.get_pricelist() context['pricelist'] = int(pricelist) else: pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context) - url = "/shop" + url = "/" if search: post["search"] = search if category: @@ -133,7 +136,7 @@ def shop(self, page=0, category=None, search='', ppg=False, **post): values['main_object'] = category return request.website.render("website_sale.products", values) - @http.route(['/shop/'], type='http', auth="public", website=True) + @http.route(['/'], type='http', auth="public", website=True) def product(self, product, category='', search='', **kwargs): cr, uid, context, pool = request.cr, request.uid, request.context, request.registry category_obj = pool['product.public.category'] @@ -149,7 +152,7 @@ def product(self, product, category='', search='', **kwargs): attrib_values = [map(int,v.split("-")) for v in attrib_list if v] attrib_set = set([v[1] for v in attrib_values]) - keep = QueryURL('/shop/', category=category and category.id, search=search, attrib=attrib_list) + keep = QueryURL('/', category=category and category.id, search=search, attrib=attrib_list) category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) categs = category_obj.browse(cr, uid, category_ids, context=context) diff --git a/website_seo_sale/models/product.py b/website_seo_sale/models/product.py index b86f22d..ca37b0c 100644 --- a/website_seo_sale/models/product.py +++ b/website_seo_sale/models/product.py @@ -10,5 +10,22 @@ class product_template(osv.Model): def _website_url(self, cr, uid, ids, field_name, arg, context=None): res = super(product_template, self)._website_url(cr, uid, ids, field_name, arg, context=context) for product in self.browse(cr, uid, ids, context=context): - res[product.id] = "/shop/%s" % (product.id,) - return res \ No newline at end of file + res[product.id] = "/%s" % (product.id,) + return res + + +class product_product(osv.Model): + _inherit = "product.product" + + # Wrappers for call_kw with inherits + def open_website_url(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].open_website_url(cr, uid, [template_id], context=context) + + def website_publish_button(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context) + + def website_publish_button(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context) \ No newline at end of file diff --git a/website_seo_sale/views/templates.xml b/website_seo_sale/views/templates.xml index 81d1452..6fe7913 100644 --- a/website_seo_sale/views/templates.xml +++ b/website_seo_sale/views/templates.xml @@ -3,31 +3,29 @@ - -