# -*- coding: utf-8 -*- import logging import odoo from odoo import http, _, fields from odoo.http import request, route from odoo.tools import lazy from odoo.osv import expression from odoo.exceptions import UserError from odoo.addons.web.controllers.home import Home from odoo.addons.web.controllers.utils import ensure_db from odoo.addons.website.controllers.main import QueryURL from odoo.addons.auth_oauth.controllers.main import OAuthLogin from odoo.addons.auth_signup.models.res_users import SignupError from odoo.addons.website_sale.controllers.main import WebsiteSale, TableCompute from odoo.addons.website_sale.controllers.variant import WebsiteSaleVariantController from odoo.addons.website.controllers.main import Website from odoo.addons.http_routing.models.ir_http import slug _logger = logging.getLogger(__name__) class WebsiteSaleAlanVariant(WebsiteSaleVariantController): @route() def get_combination_info_website(self, *args, **kwargs): res = super().get_combination_info_website(*args, **kwargs) res.update({'bulk_save': False}) if "product_id" in res.keys(): product_id = request.env['product.product'].sudo().browse(res.get("product_id", 0)) current_pricelist = request.website._get_current_pricelist() if current_pricelist: pricelist_item_ids = current_pricelist.sudo()._get_applicable_rules(product_id, fields.Date.today()) template = request.env['ir.ui.view']._render_template("theme_alan.bulk_save_offers",{ 'product': product_id, 'pricelist_item_ids': pricelist_item_ids }) get_offer_date = product_id._get_offer_timing(current_pricelist) res.update({'bulk_save': template, 'offer_timer':get_offer_date}) res.update({'default_code':product_id.default_code, 'last_month_count': product_id.get_sale_count_last_month()}) return res class WebsiteSaleAlanShop(WebsiteSale): def hide_out_of_stock(self, product): combination = product.sudo()._get_first_possible_combination() combination = product.sudo()._get_combination_info(combination, add_qty=1) product_id = request.env['product.product'].sudo().browse([combination['product_id']]) website = request.env['website'].get_current_website() if not product_id.sudo().allow_out_of_stock_order and product_id.sudo().with_context(warehouse=website._get_warehouse_available()).virtual_available < 1: return False else: return product.id def _shop_lookup_products(self, attrib_set, options, post, search, website): # No limit because attributes are obtained from complete product list product_count, details, fuzzy_search_term = website._search_with_fuzzy("products_only", search, limit=None, order=self._get_search_order(post), options=options) search_result = details[0].get('results', request.env['product.template']).with_context(bin_size=True) only_stock = request.session.get("stock", False) if only_stock: search_result = request.env["product.template"].sudo().browse(list([prod_id for prod_id in map(self.hide_out_of_stock, search_result) if prod_id != False])) return fuzzy_search_term, product_count, search_result def _rbt_count(self, search_product, brand_list, tag_list, attributes): tag_count = { tag.id: len(tag.product_template_ids.sudo().filtered(lambda x: x in search_product)) for tag in tag_list} attr_count = { attr.id : len(attr.pav_attribute_line_ids.mapped('product_tmpl_id').sudo().filtered(lambda x: x in search_product)) for attr in attributes.mapped('value_ids')} brand_count = { brand.id : len(brand.brand_product_ids.sudo().filtered(lambda x: x in search_product)) for brand in brand_list} return tag_count, attr_count, brand_count def _count_category_products(self): category_count = {} all_categs = request.env['product.public.category'].search([('website_id', 'in', [False, request.website.id])]).sudo() for rec in all_categs: child_ids = all_categs.search([('id', 'child_of', rec.ids), ('website_id', 'in', [False, request.website.id])]) category_count[rec.id] = len(child_ids.mapped('product_tmpl_ids').sudo().filtered(lambda x: x.is_published)) return category_count @route() def shop(self, page=0, category=None, search='', min_price=0.0, max_price=0.0, ppg=False, **post): # Session based PPG if ppg: request.session['ppg'] = ppg else: if request.session.get('ppg', False): ppg = request.session['ppg'] else: ppg = False only_stock = request.session.get("stock", False) if post.get("stock", False) == 'active': only_stock = True elif post.get("stock", False) == 'inactive': only_stock = False request.session["stock"] = only_stock brand_list = request.httprequest.args.getlist('brand') rating_list = request.httprequest.args.getlist('rating') env_context = dict(request.env.context) env_context.update({ 'brands':brand_list, 'rating':rating_list}) request.env.context = env_context # Calling Super res = super(WebsiteSaleAlanShop, self).shop(page, category, search, min_price, max_price, ppg, **post) url = '/shop' if category: url = "/shop/category/%s" % slug(category) as_search_prds = res.qcontext['search_product'] # Load More if request.website.active_load_more: as_ppg = res.qcontext['ppg'] ofst = 0 alan_pager = request.website.pager(url=url, total=res.qcontext['search_count'], page=page, step=as_ppg, scope=res.qcontext['search_count'], url_args=post) for page in alan_pager.get('pages'): page.update(prd_ids=as_search_prds[ofst:ofst + as_ppg].ids) ofst += as_ppg res.qcontext.update({'alan_pager':alan_pager}) # For Brands Filters brand_ids = as_search_prds.mapped("product_brand_id") brand_set = [int(brand) for brand in brand_list] # For Rating Filters rating_max = 1 if len(as_search_prds.sudo().mapped("rating_avg")): rating_max = int(max(as_search_prds.sudo().mapped("rating_avg"))) + 1 rating_set = [int(rating) for rating in rating_list] tag_count, attr_count, brand_count = self._rbt_count(res.qcontext.get('search_product'), brand_ids, res.qcontext.get('all_tags', False), res.qcontext.get('attributes', False)) category_count = self._count_category_products() res.qcontext.update({ 'stock_only':request.session["stock"], 'ppg_list':request.env['as.ppg'].search([]), 'as_shop':True, 'brands':brand_ids, 'brand_set': brand_set, 'ratings':rating_max, 'rating_set': rating_set, 'category_count': category_count, 'tag_count': tag_count, 'attr_count': attr_count, 'brand_count': brand_count, 'selected_brands': request.env['as.product.brand'].browse(brand_set), 'selected_tags' : request.env['product.tag'].browse(res.qcontext.get('tags')), }) return res @route('/nextpage/products', auth='public', type="json", website=True) def nextpage_products(self, **kw): if kw.get('product_ids'): products = request.env['product.template'].sudo() if kw.get('products'): products = request.env['product.template'].sudo().browse(kw.get('products')) website = request.env['website'].get_current_website() pricelist = website.pricelist_id ppg = int(request.session.get('ppg') or 20) ppr = int(kw.get('ppr') or 4) product_ids = request.env['product.template'].sudo().browse(kw.get('product_ids')) products |= product_ids fiscal_position_sudo = website.fiscal_position_id.sudo() products_prices = lazy(lambda: product_ids._get_sales_prices(pricelist, fiscal_position_sudo)) keep = QueryURL('/shop',) return request.env['ir.ui.view']._render_template("theme_alan.shop_page_next_products",{ 'bins': lazy(lambda: TableCompute().process(product_ids.sudo(), ppg, ppr)), 'ppg':ppg, 'ppr':ppr, 'keep': keep, 'products': products, 'products_prices': products_prices, 'get_product_prices': lambda product: lazy(lambda: products_prices[product.id]), 'products_in_wishlist': request.env['product.wishlist'].sudo().current().product_id.product_tmpl_id, }) return {} @route('/get_similar_product', auth='public', type="json", website=True) def similar_product(self, **kw): product_id = kw.get('product_id') domain = expression.AND([request.website.sale_product_domain(), [('id', '=', product_id)]]) product = request.env['product.template'].sudo().search(domain, limit=1) if not product: return False else: return request.env['ir.ui.view']._render_template("theme_alan.as_similar_product", {'products':product.alternative_product_ids}) @route() def product(self, product, category='', search='', **kwargs): res = super(WebsiteSaleAlanShop, self).product(product, category, search, **kwargs) res.qcontext.update({'as_product_detail':True}) domain = request.website.sale_product_domain() + [('active','=',True), ('website_published','=',True)] prod_lst = product.search(domain , order="website_sequence").mapped("id") previous_product = next_product = False if product.id in prod_lst: last_product_idx = len(prod_lst) - 1 current_product_idx = prod_lst.index(product.id) if current_product_idx != last_product_idx: next_prod_id = prod_lst[current_product_idx + 1] next_product = product.browse(next_prod_id) if current_product_idx != 0: prev_prod_id = prod_lst[current_product_idx - 1] previous_product = product.browse(prev_prod_id) res.qcontext.update({'next_product':next_product,'previous_product':previous_product}) return res @route('/get_quick_view', auth='public', type="json", website=True) def quick_view(self, **kw): product_id = kw.get('product_id') domain = expression.AND([request.website.sale_product_domain(), [('id', '=', product_id)]]) product = request.env['product.template'].search(domain, limit=1) if not product: return False values = self._prepare_product_values(product, category='', search='', **kw) return request.env['ir.ui.view']._render_template("theme_alan.as_quick_view", values=values) @route('/advance/info/editor/', auth='user', type="http", website=True) def offer_design(self, offer): return request.render('theme_alan.as_product_advance_info_design', {'layout': offer}) @route('/get_advance_info', auth='public', type="json", website=True) def get_advance_info(self, advance_info_id): offer = request.env['as.product.extra.info'].sudo().search([('id','=',advance_info_id)], limit=1) if offer.detail_description: return offer.detail_description return "" @route('/get_color_product', auth='public', type="json", website=True) def get_color_product(self, **kw): product_id = kw.get('product_id') domain = expression.AND([request.website.sale_product_domain(), [('id', '=', product_id)]]) product = request.env['product.template'].sudo().search(domain, limit=1) if not product: return False else: return request.env['ir.ui.view']._render_template("theme_alan.as_color_product_dialog",{ 'product':product, 'product_variant':product.product_variant_ids, }) class AlanShops(http.Controller): @route(['/shop/brands', '/shop/brands/page/'], type='http', auth="public", website=True) def BrandPage(self, page=0): domain = ['&',('active','=',True), ('website_id', 'in', (False, request.website.id))] brands = request.env['as.product.brand'].sudo().search(domain, order="name asc") total = brands.sudo().search_count([]) pager = request.website.pager( url='/shop/brands', total=total, page=page, step=30, ) offset = pager['offset'] brands = brands[offset: offset + 30] return request.render("theme_alan.brand_list", {'brands':brands, 'pager': pager}) @route('/get_mini_cart', auth='public', type="json", website=True) def mini_cart(self, **kw): order = request.website.sale_get_order() suggested_products = order.website_order_line.sudo().product_id.mapped('accessory_product_ids').filtered(lambda x: (x.is_published and x.sale_ok) and x.allow_out_of_stock_order) context = {'website_sale_order': order,'suggested_products':suggested_products} value = { 'as_mini_cart_lines':request.env["ir.ui.view"]._render_template("theme_alan.as_mini_cart_lines", context),'as_mini_cart': request.env["ir.ui.view"]._render_template("theme_alan.as_mini_cart", context)} return value @route('/as_clear_cart', type="json", auth="public", website=True) def as_clear_cart(self, **kw): order = request.website.sale_get_order() request.session['website_sale_cart_quantity'] = 0 order.unlink() return {'empty_mini_cart': request.env["ir.ui.view"]._render_template("theme_alan.as_empty_mini_cart")} @route('/get_alan_configuration', auth='public', type="json", website=True) def get_alan_configuration(self, **kw): website = request.website data = { 'active_login_popup':website.active_login_popup, 'active_mini_cart':website.active_mini_cart, 'active_scroll_top':website.active_scroll_top, 'active_b2b_mode':website.active_b2b_mode, 'active_shop_quick_view': website.active_shop_quick_view, 'active_shop_rating': website.active_shop_rating, 'active_shop_similar_product': website.active_shop_similar_product, 'active_shop_offer_timer': website.active_shop_offer_timer , 'active_shop_color_variant': website.active_shop_color_variant, 'active_shop_stock_info': website.active_shop_stock_info, 'active_shop_brand_info': website.active_shop_brand_info, 'active_shop_hover_image':website.active_shop_hover_image, 'active_shop_label':website.active_shop_label, 'active_shop_clear_filter':website.active_shop_clear_filter, 'active_shop_ppg':website.active_shop_ppg, 'active_stock_only':website.active_stock_only, 'active_load_more':website.active_load_more, 'active_brand_filter':website.active_brand_filter, 'active_rating_filter':website.active_rating_filter, 'active_attribute_count':website.active_attribute_count, 'active_attribute_search':website.active_attribute_search, 'active_product_label':website.active_product_label, 'active_product_offer_timer':website.active_product_offer_timer, 'active_product_reference':website.active_product_reference, 'active_product_category':website.active_product_category, 'active_product_brand':website.active_product_brand, 'active_product_advance_info':website.active_product_advance_info, 'active_product_variant_info':website.active_product_variant_info, 'active_product_accessory':website.active_product_accessory, 'active_product_alternative':website.active_product_alternative, 'active_product_pager':website.active_product_pager, 'active_product_sticky':website.active_product_sticky, 'active_product_bulk_save':website.active_product_bulk_save, 'active_last_month_count': website.active_last_month_count } return data @route('/set_alan_configuration', auth='public', type="json", website=True) def set_alan_configuration(self, is_active, setting): request.website.write({ setting: is_active }) return True class LoginPopup(Home): @route('/get_login_popup', type='json', auth="public", website=True) def alan_login_popup(self, **kwargs): context = {} providers = OAuthLogin.list_providers(self) context.update(super().get_auth_signup_config()) context.update({'providers':providers}) signup_enabled = request.env['res.users']._get_signup_invitation_scope() == 'b2c' reset_password_enabled = request.env['ir.config_parameter'].sudo().get_param('auth_signup.reset_password') == 'True' website_logo = request.website.image_url(request.website,'logo') context.update({'signup_enabled':signup_enabled ,"reset_password_enabled":reset_password_enabled,'website_logo':website_logo}) return request.env['ir.ui.view']._render_template("theme_alan.as_shop_login",context) @route('/alan/login/authenticate', type='json', auth="none") def alan_login_authenticate(self, **kwargs): ''' Login Authentication ''' ensure_db() request.params['login_success'] = False if not request.uid: request.update_env(user=odoo.SUPERUSER_ID) values = request.params.copy() if request.httprequest.method == 'POST': try: uid = request.session.authenticate(request.db, request.params['login'], request.params['password']) request.params['login_success'] = True request.redirect(self._login_redirect(uid, redirect=None)) return request.params except odoo.exceptions.AccessDenied as e: if e.args == odoo.exceptions.AccessDenied().args: values['error'] = _("Wrong login/password") else: values['error'] = e.args[0] if 'login' not in values and request.session.get('auth_login'): values['login'] = request.session.get('auth_login') return values @route('/alan/signup/authenticate', type="json", auth="public") def alan_signup_authenticate(self,*args, **kw): ''' Signup Authentication ''' qcontext = super(LoginPopup,self).get_auth_signup_qcontext() if 'error' not in qcontext and request.httprequest.method == 'POST': try: super(LoginPopup,self).do_signup(qcontext) User = request.env['res.users'] user_sudo = User.sudo().search( User._get_login_domain(qcontext.get('login')), order=User._get_login_order(), limit=1 ) template = request.env.ref('auth_signup.mail_template_user_signup_account_created', raise_if_not_found=False) if user_sudo and template: template.sudo().send_mail(user_sudo.id, force_send=True) return {'signup_success':True} except UserError as e: qcontext['error'] = e.args[0] except (SignupError, AssertionError) as e: if request.env['res.users'].sudo().search([('login', '=', qcontext.get('login'))]): qcontext['error'] = _('Another user is already registered using this email address.') else: _logger.error("%s", e) qcontext['error'] = _('Could not create a new account.') return qcontext class B2BWebsite(Website): @route() def autocomplete(self, search_type=None, term=None, order=None, limit=5, max_nb_chars=999, options=None): result = super().autocomplete(search_type, term, order, limit, max_nb_chars, options) if request.env.user._is_public() and request.website.active_b2b_mode: result['parts']['is_b2b_mode'] = True else: result['parts']['is_b2b_mode'] = False return result @route() def hybrid_list(self, page=1, search='', search_type='all', **kw): result = super().hybrid_list(page, search, search_type, **kw) if request.env.user._is_public() and request.website.active_b2b_mode: result.qcontext['is_b2b_mode'] = True else: result.qcontext['is_b2b_mode'] = False return result