From 7778031977feb192aabd0f7dfff8b9bf3040f360 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Fri, 9 Jul 2021 11:15:22 +0200 Subject: [PATCH] [IMP] lunch: Remove lunch.product.report model The cross join with res.users caused dramatic performances issues. TaskID: 2586326 --- addons/lunch/models/lunch_product.py | 81 ++++++++++++++ addons/lunch/report/__init__.py | 1 - addons/lunch/report/lunch_product_report.py | 111 -------------------- addons/lunch/security/ir.model.access.csv | 1 - addons/lunch/security/lunch_security.xml | 6 -- addons/lunch/tests/__init__.py | 1 - addons/lunch/tests/test_product_report.py | 21 ---- addons/lunch/views/lunch_product_views.xml | 47 +-------- 8 files changed, 85 insertions(+), 184 deletions(-) delete mode 100644 addons/lunch/report/lunch_product_report.py delete mode 100644 addons/lunch/tests/test_product_report.py diff --git a/addons/lunch/models/lunch_product.py b/addons/lunch/models/lunch_product.py index a09fc64c636..e460349ec2c 100644 --- a/addons/lunch/models/lunch_product.py +++ b/addons/lunch/models/lunch_product.py @@ -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) diff --git a/addons/lunch/report/__init__.py b/addons/lunch/report/__init__.py index 2fc4ee848a1..b2c5c89a614 100644 --- a/addons/lunch/report/__init__.py +++ b/addons/lunch/report/__init__.py @@ -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 diff --git a/addons/lunch/report/lunch_product_report.py b/addons/lunch/report/lunch_product_report.py deleted file mode 100644 index b2080022daa..00000000000 --- a/addons/lunch/report/lunch_product_report.py +++ /dev/null @@ -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,)) diff --git a/addons/lunch/security/ir.model.access.csv b/addons/lunch/security/ir.model.access.csv index 91ea62dc3f8..dbed1fd6a45 100644 --- a/addons/lunch/security/ir.model.access.csv +++ b/addons/lunch/security/ir.model.access.csv @@ -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 diff --git a/addons/lunch/security/lunch_security.xml b/addons/lunch/security/lunch_security.xml index 4bf129e1104..fa374a00c67 100644 --- a/addons/lunch/security/lunch_security.xml +++ b/addons/lunch/security/lunch_security.xml @@ -88,11 +88,5 @@ ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - - - Lunch product report: Multi Company - - ['|', ('company_id', '=', False), ('company_id', 'in', company_ids), '|', ('category_id.company_id', '=', False), ('category_id.company_id', 'in', company_ids)] - diff --git a/addons/lunch/tests/__init__.py b/addons/lunch/tests/__init__.py index ce3a01ead3a..9359b9e5d9c 100644 --- a/addons/lunch/tests/__init__.py +++ b/addons/lunch/tests/__init__.py @@ -3,5 +3,4 @@ from . import common from . import test_alert -from . import test_product_report from . import test_supplier diff --git a/addons/lunch/tests/test_product_report.py b/addons/lunch/tests/test_product_report.py deleted file mode 100644 index 624d6c5ed58..00000000000 --- a/addons/lunch/tests/test_product_report.py +++ /dev/null @@ -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') diff --git a/addons/lunch/views/lunch_product_views.xml b/addons/lunch/views/lunch_product_views.xml index f9ff3a6883d..3ca699c5563 100644 --- a/addons/lunch/views/lunch_product_views.xml +++ b/addons/lunch/views/lunch_product_views.xml @@ -23,27 +23,6 @@ - - lunch.product.report.search - lunch.product.report - - - - - - - - - - - - - - - - - - lunch.product.tree lunch.product @@ -93,26 +72,10 @@ - - lunch.product.tree - lunch.product.report - 999 - - - - - - - - - - - - lunch.product.kanban - lunch.product.report + lunch.product 999 @@ -122,8 +85,6 @@ - - @@ -361,11 +322,11 @@ Order Your Lunch - lunch.product.report + lunch.product kanban,tree - - [('supplier_id.available_today', '=', True), '|', ('user_id', '=', False), ('user_id', '=', uid)] + + [('supplier_id.available_today', '=', True)]

There is no product available today