import json import logging from werkzeug.exceptions import Forbidden, NotFound import werkzeug from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta 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.exceptions import ValidationError from odoo.addons.website.controllers.main import Website from odoo.addons.website_form_project.controllers.main import WebsiteForm from odoo.addons.website_event.controllers.main import WebsiteEventController from odoo.osv import expression _logger = logging.getLogger(__name__) PPG = 20 # Products Per Page PPR = 4 # Products Per Row class TableCompute(object): def __init__(self): self.table = {} def _check_place(self, posx, posy, sizex, sizey): res = True for y in range(sizey): for x in range(sizex): if posx + x >= PPR: res = False break row = self.table.setdefault(posy + y, {}) if row.setdefault(posx + x) is not None: res = False break for x in range(PPR): self.table[posy + y].setdefault(x, None) return res def process(self, products, ppg=PPG): # Compute products positions on the grid minpos = 0 index = 0 maxy = 0 x = 0 for p in products: x = min(max(p.website_size_x, 1), PPR) y = min(max(p.website_size_y, 1), PPR) if index >= ppg: x = y = 1 pos = minpos while not self._check_place(pos % PPR, pos // PPR, x, y): pos += 1 # if 21st products (index 20) and the last line is full (PPR products in it), break # (pos + 1.0) / PPR is the line where the product would be inserted # maxy is the number of existing lines # + 1.0 is because pos begins at 0, thus pos 20 is actually the 21st block # and to force python to not round the division operation if index >= ppg and ((pos + 1.0) // PPR) > maxy: break if x == 1 and y == 1: # simple heuristic for CPU optimization minpos = pos // PPR for y2 in range(y): for x2 in range(x): self.table[(pos // PPR) + y2][(pos % PPR) + x2] = False self.table[pos // PPR][pos % PPR] = { 'product': p, 'x': x, 'y': y, 'class': " ".join(x.html_class for x in p.website_style_ids if x.html_class) } if index <= ppg: maxy = max(maxy, y + (pos // PPR)) index += 1 # Format table according to HTML needs rows = sorted(self.table.items()) rows = [r[1] for r in rows] for col in range(len(rows)): cols = sorted(rows[col].items()) x += len(cols) rows[col] = [r[1] for r in cols if r[1]] return rows class WebsiteLearning(http.Controller): 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 return 'website_published desc,%s , id desc' % post.get('order', 'website_sequence desc') def _get_compute_currency_and_context(self): pricelist_context = dict(request.env.context) pricelist = False if not pricelist_context.get('pricelist'): pricelist = request.website.get_current_pricelist() pricelist_context['pricelist'] = pricelist.id else: pricelist = request.env['product.pricelist'].browse(pricelist_context['pricelist']) from_currency = request.env.user.company_id.currency_id to_currency = pricelist.currency_id compute_currency = lambda price: from_currency.compute(price, to_currency) return compute_currency, pricelist_context, pricelist def _get_search_domain(self, search, category, attrib_values): domain = [('is_training', '=', True)] 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))] 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)] return domain @http.route([ '/learning', '/learning/page/', '/learning/category/', '/learning/category//page/' ], type='http', auth="public", website=True) def learning(self, page=0, category=None, search='', **post): ppg = 20 if category: category = request.env['product.public.category'].search([('id', '=', int(category))], limit=1) if not category: raise NotFound() 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} domain = self._get_search_domain(search, category, attrib_values) keep = QueryURL('/learning', category=category and int(category), search=search, attrib=attrib_list, order=post.get('order')) compute_currency, pricelist_context, pricelist = self._get_compute_currency_and_context() request.context = dict(request.context, pricelist=pricelist.id, partner=request.env.user.partner_id) url = "/learning" if search: post["search"] = search if attrib_list: post['attrib'] = attrib_list categs = request.env['product.public.category'].search([('parent_id', '=', False)]) Product = request.env['product.template'] parent_category_ids = [] if category: url = "/learning/category/%s" % slug(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.search_count(domain) pager = request.website.pager(url=url, total=product_count, page=page, step=ppg, scope=7, url_args=post) products = Product.search(domain, limit=ppg, offset=pager['offset'], order=self._get_search_order(post)) ProductAttribute = request.env['product.attribute'] if products: # get all products without limit selected_products = Product.search(domain, limit=False) attributes = ProductAttribute.search([('attribute_line_ids.product_tmpl_id', 'in', selected_products.ids)]) else: attributes = ProductAttribute.browse(attributes_ids) values = { 'search': search, 'category': category, 'attrib_values': attrib_values, 'attrib_set': attrib_set, 'pager': pager, 'pricelist': pricelist, 'products': products, 'search_count': product_count, # common for all searchbox 'bins': TableCompute().process(products, ppg), 'rows': 5, 'categories': categs, 'attributes': attributes, 'compute_currency': compute_currency, 'keep': keep, 'parent_category_ids': parent_category_ids, } if category: values['main_object'] = category return request.render("training_base.learnings", values) @http.route(['/learning/event'], type='http', auth="public", website=True) def events(self, page=1, **searches): Event = request.env['event.event'] EventType = request.env['event.type'] searches.setdefault('date', 'all') searches.setdefault('type', 'all') searches.setdefault('country', 'all') searches.setdefault('product_id', 'all') domain_search = {} def sdn(date): return fields.Datetime.to_string(date.replace(hour=23, minute=59, second=59)) def sd(date): return fields.Datetime.to_string(date) today = datetime.today() dates = [ ['all', _('Next Events'), [("date_end", ">", sd(today))], 0], ['today', _('Today'), [ ("date_end", ">", sd(today)), ("date_begin", "<", sdn(today))], 0], ['week', _('This Week'), [ ("date_end", ">=", sd(today + relativedelta(days=-today.weekday()))), ("date_begin", "<", sdn(today + relativedelta(days=6-today.weekday())))], 0], ['nextweek', _('Next Week'), [ ("date_end", ">=", sd(today + relativedelta(days=7-today.weekday()))), ("date_begin", "<", sdn(today + relativedelta(days=13-today.weekday())))], 0], ['month', _('This month'), [ ("date_end", ">=", sd(today.replace(day=1))), ("date_begin", "<", (today.replace(day=1) + relativedelta(months=1)).strftime('%Y-%m-%d 00:00:00'))], 0], ['nextmonth', _('Next month'), [ ("date_end", ">=", sd(today.replace(day=1) + relativedelta(months=1))), ("date_begin", "<", (today.replace(day=1) + relativedelta(months=2)).strftime('%Y-%m-%d 00:00:00'))], 0], ['old', _('Old Events'), [ ("date_end", "<", today.strftime('%Y-%m-%d 00:00:00'))], 0], ] # search domains # TDE note: WTF ??? current_date = None current_type = None current_country = None for date in dates: if searches["date"] == date[0]: domain_search["date"] = date[2] if date[0] != 'all': current_date = date[1] if searches["type"] != 'all': current_type = EventType.browse(int(searches['type'])) domain_search["type"] = [("event_type_id", "=", int(searches["type"]))] if searches["country"] != 'all' and searches["country"] != 'online': current_country = request.env['res.country'].browse(int(searches['country'])) domain_search["country"] = ['|', ("country_id", "=", int(searches["country"])), ("country_id", "=", False)] elif searches["country"] == 'online': domain_search["country"] = [("country_id", "=", False)] if searches["product_id"] != 'all': domain_search["product_id"] = [("event_ticket_ids.product_id", "=", int(searches["product_id"]))] def dom_without(without): domain = [('state', "in", ['draft', 'confirm', 'done'])] for key, search in domain_search.items(): if key != without: domain += search return domain # count by domains without self search for date in dates: if date[0] != 'old': date[3] = Event.search_count(dom_without('date') + date[2]) domain = dom_without('type') types = Event.read_group(domain, ["id", "event_type_id"], groupby=["event_type_id"], orderby="event_type_id") types.insert(0, { 'event_type_id_count': sum([int(type['event_type_id_count']) for type in types]), 'event_type_id': ("all", _("All Categories")) }) domain = dom_without('country') countries = Event.read_group(domain, ["id", "country_id"], groupby="country_id", orderby="country_id") countries.insert(0, { 'country_id_count': sum([int(country['country_id_count']) for country in countries]), 'country_id': ("all", _("All Countries")) }) step = 20 # Number of events per page event_count = Event.search_count(dom_without("none")) pager = request.website.pager( url="/event", url_args={'date': searches.get('date'), 'type': searches.get('type'), 'country': searches.get('country')}, total=event_count, page=page, step=step, scope=5) order = 'website_published desc, date_begin' if searches.get('date', 'all') == 'old': order = 'website_published desc, date_begin desc' events = Event.search(dom_without("none"), limit=step, offset=pager['offset'], order=order) values = { 'current_date': current_date, 'current_country': current_country, 'current_type': current_type, 'event_ids': events, # event_ids used in website_event_track so we keep name as it is 'dates': dates, 'types': types, 'countries': countries, 'pager': pager, 'searches': searches, 'search_path': "?%s" % werkzeug.url_encode(searches), } return request.render("website_event.index", values)