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.
 
 
 
 
 

385 lines
15 KiB

# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Ayisha Sumayya K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from collections import defaultdict
from itertools import product as cartesian_product
from datetime import datetime
from werkzeug.exceptions import NotFound
from odoo import fields, http, tools, _
from odoo.http import request
from odoo.addons.http_routing.models.ir_http import slug
from odoo.addons.website.controllers.main import QueryURL
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.osv import expression
from odoo.tools import lazy
class TableComputeCoffee(object):
""" Compute data related to coffee shop theme """
def __init__(self):
self.table = {}
def _check_place(self, posx, posy, sizex, sizey, ppr):
res = True
for yaxix in range(sizey):
for xaxix in range(sizex):
if posx + xaxix >= ppr:
res = False
break
row = self.table.setdefault(posy + yaxix, {})
if row.setdefault(posx + xaxix) is not None:
res = False
break
for x in range(ppr):
self.table[posy + yaxix].setdefault(xaxix, None)
return res
def process(self, products, ppg=20, ppr=4):
"""Compute products positions on the grid"""
minpos = 0
index = 0
maxy = 0
rec = 0
for pdct in products:
rec = min(max(pdct.website_size_x, 1), ppr)
res = min(max(pdct.website_size_y, 1), ppr)
if index >= ppg:
rec = res = 1
pos = minpos
while not self._check_place(pos % ppr, pos // ppr, rec, res, ppr):
pos += 1
if index >= ppg and ((pos + 1.0) // ppr) > maxy:
break
if rec == 1 and res == 1:
minpos = pos // ppr
for y2 in range(res):
for x2 in range(rec):
self.table[(pos // ppr) + y2][(pos % ppr) + x2] = False
self.table[pos // ppr][pos % ppr] = {
'product': pdct, 'x': rec, 'y': res,
'ribbon': pdct._get_website_ribbon(),
}
if index <= ppg:
maxy = max(maxy, res + (pos // ppr))
index += 1
rows = sorted(self.table.items())
rows = [r[1] for r in rows]
for col in range(len(rows)):
cols = sorted(rows[col].items())
rec += len(cols)
rows[col] = [r[1] for r in cols if r[1]]
return rows
class ThemeCoffeeMenu(http.Controller):
""" controller for rendering datas to menu page """
def _get_search_order(self, post):
""" OrderBy will be parsed in orm and so no direct sql injection id is
added to be sure that order is a unique sort key
"""
order = post.get('order') or \
request.env['website'].get_current_website().shop_default_sort
return 'is_published desc, %s, id desc' % order
def _get_search_domain(self, search, category, attrib_values,
search_in_description=True):
domains = [request.website.sale_product_domain()]
if search:
for srch in search.split(" "):
subdomains = [
[('name', 'ilike', srch)],
[('product_variant_ids.default_code', 'ilike', srch)]
]
if search_in_description:
subdomains.append([('website_description', 'ilike', srch)])
subdomains.append([('description_sale', 'ilike', srch)])
domains.append(expression.OR(subdomains))
if category:
domains.append([('public_categ_ids', 'child_of', int(category))])
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:
domains.append([('attribute_line_ids.value_ids', 'in', ids)])
attrib = value[0]
ids = [value[1]]
if attrib:
domains.append([('attribute_line_ids.value_ids', 'in', ids)])
return expression.AND(domains)
def sitemap_shop(env, rule, qs):
if not qs or qs.lower() in '/menu':
yield {'loc': '/menu'}
Category = env['product.public.category']
dom = sitemap_qs2dom(qs, '/menu/category', Category._rec_name)
dom += env['website'].get_current_website().website_domain()
for cat in Category.search(dom):
loc = '/menu/category/%s' % slug(cat)
if not qs or qs.lower() in loc:
yield {'loc': loc}
def _get_search_options(
self, category=None, attrib_values=None, pricelist=None, **post):
return {
'displayDescription': True,
'displayDetail': True,
'displayExtraDetail': True,
'displayExtraLink': True,
'displayImage': True,
'allowFuzzy': not post.get('noFuzzy'),
'category': str(category.id) if category else None,
'attrib_values': attrib_values,
'display_currency': pricelist.currency_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)
if attrib_set:
attribute_values = request.env['product.attribute.value'].browse(attrib_set)
values_per_attribute = defaultdict(
lambda: request.env['product.attribute.value'])
multi_value_attribute = False
for value in attribute_values:
values_per_attribute[value.attribute_id] |= value
if len(values_per_attribute[value.attribute_id]) > 1:
multi_value_attribute = True
def filter_template(template, attribute_values_list):
"""Transform product.attribute.value to
product.template.attribute.value """
attribute_value_to_ptav = dict()
for ptav in template.attribute_line_ids.product_template_value_ids:
attribute_value_to_ptav[ptav.product_attribute_value_id] = ptav.id
possible_combinations = False
for attribute_values in attribute_values_list:
ptavs = request.env['product.template.attribute.value'].browse(
[attribute_value_to_ptav[val]
for val in attribute_values
if val in attribute_value_to_ptav]
)
if len(ptavs) < len(attribute_values):
continue
if len(ptavs) == len(template.attribute_line_ids):
if template._is_combination_possible(ptavs):
return True
elif len(ptavs) < len(template.attribute_line_ids):
if len(attribute_values_list) == 1:
if any(template._get_possible_combinations(
necessary_values=ptavs)):
return True
if not possible_combinations:
possible_combinations = template._get_possible_combinations()
if any(len(ptavs & combination) == len(ptavs)
for combination in possible_combinations):
return True
return False
if not multi_value_attribute:
possible_attrib_values_list = [attribute_values]
else:
possible_attrib_values_list = [
request.env['product.attribute.value'].browse(
[v.id for v in values]) for values in cartesian_product(
*values_per_attribute.values())
]
search_result = search_result.filtered(
lambda tmpl: filter_template(tmpl, possible_attrib_values_list)
)
return fuzzy_search_term, product_count, search_result
def _menu_get_query_url_kwargs(self, category, search, attrib=None,
order=None, **post):
return {
'category': category,
'search': search,
'attrib': attrib,
'order': order,
}
def _get_additional_shop_values(self, values):
""" Hook to update values used for rendering website_sale.products template """
return {}
@http.route([
'/menu',
'/menu/page/<int:page>',
'/menu/category/<model("product.public.category"):category>',
'/menu/category/<model("product.public.category"):category>/page/<int:page>',
], type='http', auth="public", website=True, sitemap=sitemap_shop)
def menu_page(self, page=0, category=None, search='',
min_price=0.0, max_price=0.0, ppg=False, **post):
add_qty = int(post.get('add_qty', 1))
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
website = request.env['website'].get_current_website()
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
attrib_list = request.httprequest.args.getlist('attrib')
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}
keep = QueryURL('/menu', **self._menu_get_query_url_kwargs(category
and int(category), search, min_price, max_price, **post))
now = datetime.timestamp(datetime.now())
pricelist = request.env['product.pricelist'].\
browse(request.session.get('website_sale_current_pl'))
if not pricelist or request.session.\
get('website_sale_pricelist_time', 0) < now - 60*60:
pricelist = website.get_current_pricelist()
request.session['website_sale_pricelist_time'] = now
request.session['website_sale_current_pl'] = pricelist.id
request.update_context(pricelist=pricelist.id,
partner=request.env.user.partner_id)
url = "/menu"
if search:
post["search"] = search
if attrib_list:
post['attrib'] = attrib_list
options = self._get_search_options(
category=category,
attrib_values=attrib_values,
pricelist=pricelist,
**post
)
fuzzy_search_term, product_count, search_product = \
self._shop_lookup_products(attrib_set, options, post, search, website)
website_domain = website.website_domain()
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 = "/menu/category/%s" % slug(category)
pager = website.pager(url=url, total=product_count, page=page,
step=ppg, scope=7, url_args=post)
offset = pager['offset']
products = search_product[offset:offset + ppg]
ProductAttribute = request.env['product.attribute']
if products:
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(pricelist))
values = {
'order': post.get('order', ''),
'category': category,
'attrib_values': attrib_values,
'attrib_set': attrib_set,
'pager': pager,
'pricelist': pricelist,
'add_qty': add_qty,
'products': products,
'search_product': search_product,
'search_count': product_count,
'bins': lazy(lambda: TableComputeCoffee().process(products, ppg, ppr)),
'ppg': ppg,
'ppr': ppr,
'categories': categs,
'attributes': attributes,
'keep': keep,
'search_categories_ids': search_categories.ids,
'products_prices': products_prices,
'get_product_prices': lambda product: lazy(
lambda: products_prices[product.id]
),
'float_round': tools.float_round,
}
if category:
values['main_object'] = category
values.update(self._get_additional_shop_values(values))
return request.render("theme_coffee_shop.coffee_menu", values)