[IMP] lunch: Remove lunch.product.report model

The cross join with res.users caused dramatic performances issues.

TaskID: 2586326
This commit is contained in:
Yannick Tivisse
2021-07-16 12:02:56 +02:00
parent e9960ec275
commit 7778031977
8 changed files with 85 additions and 184 deletions
+81
View File
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.modules.module import get_module_resource
from odoo.tools import formatLang
from odoo.osv import expression
class LunchProductCategory(models.Model):
@@ -117,7 +121,73 @@ class LunchProduct(models.Model):
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
new_until = fields.Date('New Until')
is_new = fields.Boolean(compute='_compute_is_new')
favorite_user_ids = fields.Many2many('res.users', 'lunch_product_favorite_user_rel', 'product_id', 'user_id', check_company=True)
is_favorite = fields.Boolean(compute='_compute_is_favorite')
last_order_date = fields.Date(compute='_compute_last_order_date')
product_image = fields.Image(compute='_compute_product_image')
# This field is used only for searching
is_available_at = fields.Many2one('lunch.location', 'Product Availability', compute='_compute_is_available_at', search='_search_is_available_at')
@api.depends('image_128', 'category_id.image_128')
def _compute_product_image(self):
for product in self:
product.product_image = product.image_128 or product.category_id.image_128
@api.depends('new_until')
def _compute_is_new(self):
today = fields.Date.context_today(self)
for product in self:
if product.new_until:
product.is_new = today <= product.new_until
else:
product.is_new = False
@api.depends_context('uid')
@api.depends('favorite_user_ids')
def _compute_is_favorite(self):
for product in self:
product.is_favorite = self.env.user in self.favorite_user_ids
@api.depends_context('uid')
def _compute_last_order_date(self):
all_orders = self.env['lunch.order'].search([
('user_id', '=', self.env.user.id),
('product_id', 'in', self.ids),
])
mapped_orders = defaultdict(lambda: self.env['lunch.order'])
for order in all_orders:
mapped_orders[order.product_id] |= order
for product in self:
if not mapped_orders[product]:
product.last_order_date = False
else:
product.last_order_date = max(mapped_orders[product].mapped('date'))
def _compute_is_available_at(self):
"""
Is available_at is always false when browsing it
this field is there only to search (see _search_is_available_at)
"""
for product in self:
product.is_available_at = False
def _search_is_available_at(self, operator, value):
supported_operators = ['in', 'not in', '=', '!=']
if not operator in supported_operators:
return expression.TRUE_DOMAIN
if isinstance(value, int):
value = [value]
if operator in expression.NEGATIVE_TERM_OPERATORS:
return expression.AND([[('supplier_id.available_location_ids', 'not in', value)], [('supplier_id.available_location_ids', '!=', False)]])
return expression.OR([[('supplier_id.available_location_ids', 'in', value)], [('supplier_id.available_location_ids', '=', False)]])
def _sync_active_from_related(self):
""" Archive/unarchive product after related field is archived/unarchived """
@@ -129,3 +199,14 @@ class LunchProduct(models.Model):
if self.filtered(lambda product: not product.active and not product.supplier_id.active):
raise UserError(_("The product supplier is archived. The user have to unarchive the supplier or change the supplier of the product."))
return super().toggle_active()
def write(self, values):
if 'is_favorite' in values:
if values['is_favorite']:
commands = [(4, product.id) for product in self]
else:
commands = [(3, product.id) for product in self]
self.env.user.write({
'favorite_lunch_product_ids': commands,
})
return super().write(values)
-1
View File
@@ -2,4 +2,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import lunch_cashmove_report
from . import lunch_product_report
-111
View File
@@ -1,111 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
from odoo.osv import expression
class LunchProductReport(models.Model):
_name = "lunch.product.report"
_description = 'Product report'
_auto = False
_order = 'is_favorite desc, is_new desc, last_order_date asc, product_id asc'
id = fields.Integer('ID')
product_id = fields.Many2one('lunch.product', 'Product')
name = fields.Char('Product Name', related='product_id.name')
category_id = fields.Many2one('lunch.product.category', 'Product Category')
description = fields.Html('Description', related='product_id.description')
price = fields.Float('Price')
supplier_id = fields.Many2one('lunch.supplier', 'Vendor')
company_id = fields.Many2one('res.company')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
is_favorite = fields.Boolean('Favorite')
user_id = fields.Many2one('res.users')
is_new = fields.Boolean('New')
active = fields.Boolean('Active')
last_order_date = fields.Date('Last Order Date')
image_128 = fields.Image(compute="_compute_image_128")
# This field is used only for searching
is_available_at = fields.Many2one('lunch.location', 'Product Availability', compute='_compute_is_available_at', search='_search_is_available_at')
def _compute_image_128(self):
for product_r in self:
product = product_r.product_id
category = product_r.sudo().category_id
if product.image_128:
product_r.image_128 = product.image_128
elif category.image_128:
product_r.image_128 = category.image_128
else:
product_r.image_128 = False
def _compute_concurrency_field(self):
"""Image caching is based on the `__last_update` field (=self.CONCURRENCY_CHECK_FIELD)
But the image is never cached by the browser because the value fallbacks to
`now` when access logging is disabled. This override sets a "real" value based on the
product or category last update.
"""
for report in self:
product_last_update = report.product_id[self.CONCURRENCY_CHECK_FIELD]
category_last_update = report.category_id[self.CONCURRENCY_CHECK_FIELD]
report[self.CONCURRENCY_CHECK_FIELD] = max(product_last_update, category_last_update)
def _compute_is_available_at(self):
"""
Is available_at is always false when browsing it
this field is there only to search (see _search_is_available_at)
"""
for product in self:
product.is_available_at = False
def _search_is_available_at(self, operator, value):
supported_operators = ['in', 'not in', '=', '!=']
if not operator in supported_operators:
return expression.TRUE_DOMAIN
if isinstance(value, int):
value = [value]
if operator in expression.NEGATIVE_TERM_OPERATORS:
return expression.AND([[('supplier_id.available_location_ids', 'not in', value)], [('supplier_id.available_location_ids', '!=', False)]])
return expression.OR([[('supplier_id.available_location_ids', 'in', value)], [('supplier_id.available_location_ids', '=', False)]])
def write(self, values):
if 'is_favorite' in values:
if values['is_favorite']:
commands = [(4, product_id) for product_id in self.mapped('product_id').ids]
else:
commands = [(3, product_id) for product_id in self.mapped('product_id').ids]
self.env.user.write({
'favorite_lunch_product_ids': commands,
})
def init(self):
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute("""
CREATE or REPLACE view %s AS (
SELECT
row_number() over (ORDER BY users.id,product.id) AS id,
product.id AS product_id,
product.category_id,
product.price,
product.supplier_id,
product.company_id,
product.active,
users.id AS user_id,
fav.user_id IS NOT NULL AS is_favorite,
product.new_until >= current_date AS is_new,
orders.last_order_date
FROM lunch_product product
CROSS JOIN res_users users
INNER JOIN res_groups_users_rel groups ON groups.uid = users.id -- only generate for internal users
LEFT JOIN LATERAL (select max(date) AS last_order_date FROM lunch_order where user_id=users.id and product_id=product.id) AS orders ON TRUE
LEFT JOIN LATERAL (select user_id FROM lunch_product_favorite_user_rel where user_id=users.id and product_id=product.id) AS fav ON TRUE
WHERE users.active AND product.active AND groups.gid = %%s --only take into account active products and users
);
""" % self._table, (self.env.ref('base.group_user').id,))
@@ -17,4 +17,3 @@ lunch_topping_user,access_lunch_topping,model_lunch_topping,group_lunch_user,1,0
lunch_topping_manager,access_lunch_topping,model_lunch_topping,group_lunch_manager,1,1,1,1
lunch_supplier_user,"Lunch Supplier User Rights",model_lunch_supplier,group_lunch_user,1,0,0,0
lunch_supplier_manager,"Lunch Supplier Manager Rights",model_lunch_supplier,group_lunch_manager,1,1,1,1
lunch_product_report_user,access_lunch_product_report,model_lunch_product_report,group_lunch_user,1,1,0,0
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
17 lunch_topping_manager access_lunch_topping model_lunch_topping group_lunch_manager 1 1 1 1
18 lunch_supplier_user Lunch Supplier User Rights model_lunch_supplier group_lunch_user 1 0 0 0
19 lunch_supplier_manager Lunch Supplier Manager Rights model_lunch_supplier group_lunch_manager 1 1 1 1
lunch_product_report_user access_lunch_product_report model_lunch_product_report group_lunch_user 1 1 0 0
-6
View File
@@ -88,11 +88,5 @@
<field name="model_id" ref="model_lunch_location"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="ir_rule_lunch_product_report_multi_company" model="ir.rule">
<field name="name">Lunch product report: Multi Company</field>
<field name="model_id" ref="model_lunch_product_report"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids), '|', ('category_id.company_id', '=', False), ('category_id.company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>
-1
View File
@@ -3,5 +3,4 @@
from . import common
from . import test_alert
from . import test_product_report
from . import test_supplier
-21
View File
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.lunch.tests.common import TestsCommon
class TestLunchProductReport(TestsCommon):
def test_product_available(self):
self.assertTrue(self.env['lunch.product.report'].search([]), 'There should be some record on lunch_product_report')
def test_order_in_report(self):
pizza = self.env['lunch.product.report'].search([('product_id', '=', self.product_pizza.id)], limit=1)
self.assertEqual(pizza.name, 'Pizza')
pizza = pizza.with_user(pizza.user_id)
pizza.write({'is_favorite': True})
self.assertTrue(pizza.product_id in pizza.user_id.favorite_lunch_product_ids)
new_pizza = self.env['lunch.product.report'].search([('product_id', '=', self.product_pizza.id), ('user_id', '=', pizza.user_id.id)])
self.assertEqual(new_pizza.id, pizza.id)
self.assertEqual(new_pizza.name, 'Pizza')
+4 -43
View File
@@ -23,27 +23,6 @@
</field>
</record>
<record id="lunch_product_report_view_search" model="ir.ui.view">
<field name="name">lunch.product.report.search</field>
<field name="model">lunch.product.report</field>
<field name="arch" type="xml">
<search string="Product Search">
<field name="name" string="Product"/>
<field name="category_id" string="Category"/>
<field name="supplier_id"/>
<field name="description"/>
<group expand="0" string="Group By">
<filter name="group_by_supplier" string="Vendor" context="{'group_by': 'supplier_id'}"/>
<filter name="group_by_category" string="Category" context="{'group_by': 'category_id'}"/>
</group>
<searchpanel>
<field name="category_id" select="multi" string="Categories" icon="fa-cutlery" color="#875A7B"/>
<field name="supplier_id" select="multi" string="Vendors" icon="fa-truck"/>
</searchpanel>
</search>
</field>
</record>
<record id="lunch_product_view_tree" model="ir.ui.view">
<field name="name">lunch.product.tree</field>
<field name="model">lunch.product</field>
@@ -93,26 +72,10 @@
</form>
</field>
</record>
<record id="lunch_product_report_view_tree" model="ir.ui.view">
<field name="name">lunch.product.tree</field>
<field name="model">lunch.product.report</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<tree js_class="lunch_list">
<field name="name"/>
<field name="category_id"/>
<field name="supplier_id"/>
<field name="description"/>
<field name="product_id" invisible="1"/>
<field name="price" widget="monetary"/>
<field name="currency_id" invisible="1"/>
</tree>
</field>
</record>
<record id="view_lunch_product_kanban_order" model="ir.ui.view">
<field name="name">lunch.product.kanban</field>
<field name="model">lunch.product.report</field>
<field name="model">lunch.product</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<kanban js_class="lunch_kanban" create="0" edit="0" group_create="0" class="o_kanban_mobile">
@@ -122,8 +85,6 @@
<field name="supplier_id"/>
<field name="description"/>
<field name="currency_id"/>
<field name="user_id"/>
<field name="product_id"/>
<field name="is_new"/>
<templates>
<t t-name="kanban-box">
@@ -361,11 +322,11 @@
<record id="lunch_product_action_order" model="ir.actions.act_window">
<field name="name">Order Your Lunch</field>
<field name="res_model">lunch.product.report</field>
<field name="res_model">lunch.product</field>
<field name="view_mode">kanban,tree</field>
<field name="view_id" ref="view_lunch_product_kanban_order"/>
<field name="search_view_id" ref="lunch_product_report_view_search"/>
<field name="domain">[('supplier_id.available_today', '=', True), '|', ('user_id', '=', False), ('user_id', '=', uid)]</field>
<field name="search_view_id" ref="lunch_product_view_search"/>
<field name="domain">[('supplier_id.available_today', '=', True)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
There is no product available today