[NEW] training-tools addons
This commit is contained in:
343
learning_base/controler/main.py
Normal file
343
learning_base/controler/main.py
Normal file
@@ -0,0 +1,343 @@
|
||||
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/<int:page>',
|
||||
'/learning/category/<model("product.public.category"):category>',
|
||||
'/learning/category/<model("product.public.category"):category>/page/<int: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)
|
Reference in New Issue
Block a user