[IMP] lunch: Remove lunch.product.report model
The cross join with res.users caused dramatic performances issues. TaskID: 2586326
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import test_alert
|
||||
from . import test_product_report
|
||||
from . import test_supplier
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user