You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

303 lines
13 KiB

# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Ammu Raj (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# 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 (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import logging
from datetime import datetime
from werkzeug.exceptions import NotFound
from odoo import fields
from odoo.http import request, route
from odoo.osv import expression
from odoo.tools import float_round, groupby, lazy, SQL
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo.addons.website.controllers.main import QueryURL
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.addons.website_sale.controllers.main import TableCompute
_logger = logging.getLogger(__name__)
class WebsiteSales(WebsiteSale):
def sitemap_shop(env, rule, qs):
if not qs or qs.lower() in '/shop':
yield {'loc': '/shop'}
Category = env['product.public.category']
dom = sitemap_qs2dom(qs, '/shop/category', Category._rec_name)
dom += env['website'].get_current_website().website_domain()
for cat in Category.search(dom):
loc = '/shop/category/%s' % env['ir.http']._slug(cat)
if not qs or qs.lower() in loc:
yield {'loc': loc}
@route([
'/shop',
'/shop/page/<int:page>',
'/shop/category/<model("product.public.category"):category>',
'/shop/category/<model("product.public.category"):category>/page/<int:page>',
'''/shop/brand/<model("product.brand"):brand>'''
], type='http', auth="public", website=True, sitemap=sitemap_shop)
def shop(self, page=0, category=None, search='', min_price=0.0,
max_price=0.0, ppg=False, brand=None, **post):
"""Overrides the function for adding Brand"""
if not request.website.has_ecommerce_access():
return request.redirect('/web/login')
try:
min_price = float(min_price)
except ValueError:
min_price = 0
try:
max_price = float(max_price)
except ValueError:
max_price = 0
compute_brand = brand
Category = request.env['product.public.category']
if category:
category = Category.search([('id', '=', int(category))], limit=1)
if not category or not category.can_access_from_current_website():
raise NotFound()
else:
category = Category
product_brand = request.env['product.brand']
if not brand:
brand = product_brand
website = request.env['website'].get_current_website()
website_domain = website.website_domain()
if ppg:
try:
ppg = int(ppg)
post['ppg'] = ppg
except ValueError:
ppg = False
if not ppg:
ppg = website.shop_ppg or 20
ppr = website.shop_ppr or 4
request_args = request.httprequest.args
attrib_list = request_args.getlist('attribute_value')
attrib_values = [[int(x) for x in v.split("-")] for v in attrib_list if
v]
attributes_ids = {v[0] for v in attrib_values}
attrib_set = {v[1] for v in attrib_values}
filter_by_tags_enabled = website.is_view_active(
'website_sale.filter_products_tags')
if filter_by_tags_enabled:
tags = request_args.getlist('tags')
# Allow only numeric tag values to avoid internal error.
if tags and all(tag.isnumeric() for tag in tags):
post['tags'] = tags
tags = {int(tag) for tag in tags}
else:
post['tags'] = None
tags = {}
keep = QueryURL('/shop', **self._shop_get_query_url_kwargs(
category and int(category), search, min_price, max_price, **post))
now = datetime.timestamp(datetime.now())
pricelist = website.pricelist_id
if 'website_sale_pricelist_time' in request.session:
# Check if we need to refresh the cached pricelist
pricelist_save_time = request.session['website_sale_pricelist_time']
if pricelist_save_time < now - 60 * 60:
request.session.pop('website_sale_current_pl', None)
website.invalidate_recordset(['pricelist_id'])
pricelist = website.pricelist_id
request.session['website_sale_pricelist_time'] = now
request.session['website_sale_current_pl'] = pricelist.id
else:
request.session['website_sale_pricelist_time'] = now
request.session['website_sale_current_pl'] = pricelist.id
filter_by_price_enabled = website.is_view_active(
'website_sale.filter_products_price')
if filter_by_price_enabled:
company_currency = website.company_id.sudo().currency_id
conversion_rate = request.env['res.currency']._get_conversion_rate(
company_currency, website.currency_id,
request.website.company_id, fields.Date.today())
else:
conversion_rate = 1
url = '/shop'
if search:
post['search'] = search
options = self._get_search_options(
category=category,
attrib_values=attrib_values,
min_price=min_price,
max_price=max_price,
conversion_rate=conversion_rate,
display_currency=website.currency_id,
**post
)
fuzzy_search_term, product_count, search_product = self._shop_lookup_products(
attrib_set, options, post, search, website)
filter_by_price_enabled = website.is_view_active(
'website_sale.filter_products_price')
if filter_by_price_enabled:
# TODO Find an alternative way to obtain the domain through the search metadata.
Product = request.env['product.template'].with_context(
bin_size=True)
domain = self._get_shop_domain(search, category, attrib_values)
# This is ~4 times more efficient than a search for the cheapest and most expensive products
query = Product._where_calc(domain)
Product._apply_ir_rules(query, 'read')
sql = query.select(
SQL(
"COALESCE(MIN(list_price), 0) * %(conversion_rate)s, COALESCE(MAX(list_price), 0) * %(conversion_rate)s",
conversion_rate=conversion_rate,
)
)
available_min_price, available_max_price = \
request.env.execute_query(sql)[0]
if min_price or max_price:
# The if/else condition in the min_price / max_price value assignment
# tackles the case where we switch to a list of products with different
# available min / max prices than the ones set in the previous page.
# In order to have logical results and not yield empty product lists, the
# price filter is set to their respective available prices when the specified
# min exceeds the max, and / or the specified max is lower than the available min.
if min_price:
min_price = min_price if min_price <= available_max_price else available_min_price
post['min_price'] = min_price
if max_price:
max_price = max_price if max_price >= available_min_price else available_max_price
post['max_price'] = max_price
ProductTag = request.env['product.tag']
if filter_by_tags_enabled and search_product:
all_tags = ProductTag.search(
expression.AND([
[('product_ids.is_published', '=', True),
('visible_on_ecommerce', '=', True)],
website_domain
])
)
else:
all_tags = ProductTag
categs_domain = [('parent_id', '=', False)] + website_domain
if search:
search_categories = Category.search(
[(
'product_tmpl_ids', 'in', search_product.ids)] + website_domain
).parents_and_self
categs_domain.append(('id', 'in', search_categories.ids))
else:
search_categories = Category
categs = lazy(lambda: Category.search(categs_domain))
if category:
url = "/shop/category/%s" % request.env['ir.http']._slug(category)
pager = website.pager(url=url, total=product_count, page=page, step=ppg,
scope=5, url_args=post)
offset = pager['offset']
products = search_product[offset:offset + ppg]
ProductAttribute = request.env['product.attribute']
if products:
# get all products without limit
attributes = lazy(lambda: ProductAttribute.search([
('product_tmpl_ids', 'in', search_product.ids),
('visibility', '=', 'visible'),
]))
else:
attributes = lazy(lambda: ProductAttribute.browse(attributes_ids))
layout_mode = request.session.get('website_sale_shop_layout_mode')
if not layout_mode:
if website.viewref('website_sale.products_list_view').active:
layout_mode = 'list'
else:
layout_mode = 'grid'
request.session['website_sale_shop_layout_mode'] = layout_mode
products_prices = lazy(lambda: products._get_sales_prices(website))
products_prices_brand = lazy(
lambda: search_product._get_sales_prices(website))
product_brand = request.env['product.brand'].search([])
products_brand = request.env['product.template'].search(
['&', '&', ('brand_id', '=', brand.id), ('sale_ok', '=', True),
('is_published', '=', True)])
product_brand_count = len(products_brand)
pager_brand = website.pager(url=url,
total=product_brand_count,
page=page, step=ppg, scope=5,
url_args=post)
attributes_values = request.env['product.attribute.value'].browse(
attrib_set)
sorted_attributes_values = attributes_values.sorted('sequence')
multi_attributes_values = sorted_attributes_values.filtered(
lambda av: av.display_type == 'multi')
single_attributes_values = sorted_attributes_values - multi_attributes_values
grouped_attributes_values = list(
groupby(single_attributes_values, lambda av: av.attribute_id.id))
grouped_attributes_values.extend(
[(av.attribute_id.id, [av]) for av in multi_attributes_values])
selected_attributes_hash = grouped_attributes_values and "#attribute_values=%s" % (
','.join(str(v[0].id) for k, v in grouped_attributes_values)
) or ''
values = {
'brand': brand,
'search': fuzzy_search_term or search,
'original_search': fuzzy_search_term and search,
'order': post.get('order', ''),
'category': category,
'attrib_values': attrib_values,
'attrib_set': attrib_set,
'pager': pager_brand,
'products': products,
'search_product': search_product,
'search_count': product_brand_count, # common for all searchbox
'bins': lazy(
lambda: TableCompute().process(products_brand, ppg, ppr)),
'ppg': ppg,
'ppr': ppr,
'categories': categs,
'attributes': attributes,
'keep': keep,
'selected_attributes_hash': selected_attributes_hash,
'search_categories_ids': search_categories.ids,
'layout_mode': layout_mode,
'brands': product_brand,
'products_prices': products_prices,
'get_product_prices': lambda product: lazy(
lambda: products_prices_brand[product.id]),
'float_round': float_round,
}
if filter_by_price_enabled:
values['min_price'] = min_price or available_min_price
values['max_price'] = max_price or available_max_price
values['available_min_price'] = float_round(available_min_price, 2)
values['available_max_price'] = float_round(available_max_price, 2)
if filter_by_tags_enabled:
values.update({'all_tags': all_tags, 'tags': tags})
if category:
values['main_object'] = category
values.update(self._get_additional_extra_shop_values(values, **post))
return request.render("website_sale.products", values)