Files
suite/product_attribute_lazy/models/product_attribute.py

159 lines
6.6 KiB
Python

import logging
from datetime import datetime
from odoo import api, fields, models, _, registry, tools
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class ProductAttribute(models.Model):
_inherit = 'product.attribute'
number_related_products = fields.Integer(compute=False, store=True) # compute='_compute_number_related_products')
product_tmpl_ids = fields.Many2many('product.template', string="Related Products", compute=False, store=True) #compute='_compute_products', store=True)
def action_open_related_products(self):
self.ensure_one()
self.env.cr.execute('SELECT product_template_id FROM product_attribute_product_template_rel WHERE product_attribute_id = %s' % (self.id, ))
tmpl_res = self.env.cr.fetchall()
tmpl_ids = [t[0] for t in tmpl_res]
return {
'type': 'ir.actions.act_window',
'name': _("Related Products"),
'res_model': 'product.template',
'view_mode': 'tree,form',
'domain': [('id', 'in', tmpl_ids)],
}
def _compute_products_sql(self):
if not self:
raise UserError('Index with SQL requires attributes.')
# ORIGINAL for replicating logic
# for pa in self:
# product_tmpls = pa.attribute_line_ids.product_tmpl_id
# pa.with_context(active_test=False).product_tmpl_ids = product_tmpls
# pa.number_related_products = len(product_tmpls)
self_ids = ','.join(str(i) for i in self.ids)
query = """
WITH current_rel AS (
SELECT
attribute_id AS product_attribute_id,
product_tmpl_id AS product_template_id
FROM product_template_attribute_line
WHERE attribute_id in (%s)
GROUP BY 1, 2
),
expanded_rel AS (
SELECT
cr.product_attribute_id as cr_product_attribute_id,
cr.product_template_id as cr_product_template_id,
paptr.product_attribute_id as paptr_product_attribute_id,
paptr.product_template_id as paptr_product_template_id
FROM current_rel cr
LEFT JOIN product_attribute_product_template_rel paptr ON paptr.product_attribute_id = cr.product_attribute_id AND paptr.product_template_id = cr.product_template_id
),
not_in_rel AS (
SELECT
cr_product_attribute_id as product_attribute_id,
cr_product_template_id as product_template_id
FROM expanded_rel
WHERE paptr_product_attribute_id is null and paptr_product_template_id is null
)
INSERT INTO product_attribute_product_template_rel (product_attribute_id, product_template_id)
SELECT product_attribute_id, product_template_id
FROM not_in_rel;
""" % (self_ids, )
query += """
WITH needs_del AS (
SELECT
t.product_attribute_id,
t.product_template_id
FROM product_attribute_product_template_rel AS t
LEFT JOIN product_template_attribute_line AS real ON
real.attribute_id = t.product_attribute_id
AND real.product_tmpl_id = t.product_template_id
WHERE t.product_attribute_id in (%s)
AND real.product_tmpl_id is null
AND real.attribute_id is null
)
DELETE FROM product_attribute_product_template_rel
WHERE product_attribute_id in (%s) AND (
(product_attribute_product_template_rel.product_attribute_id,
product_attribute_product_template_rel.product_template_id) IN (SELECT * FROM needs_del)
);
""" % (self_ids, self_ids)
for i in self.ids:
query += """
UPDATE product_attribute
SET number_related_products = (SELECT COUNT(*) FROM product_attribute_product_template_rel WHERE product_attribute_id = %s)
WHERE id = %s;
""" % (i, i)
self.env.cr.execute(query)
def _run_indexer(self, use_new_cursor=False):
indexer_use_sql = self.env['ir.config_parameter'].sudo().get_param('product_attribute_lazy.indexer_use_sql', '1') != '0'
for pa in self:
if indexer_use_sql:
pa._compute_products_sql()
else:
pa._compute_products()
if use_new_cursor:
pa._cr.commit()
_logger.info("_run_indexer is finished and committed for %s" % (pa.id, ))
@api.model
def run_indexer(self, use_new_cursor=False):
start_time = datetime.now()
attributes = None
try:
if use_new_cursor:
cr = registry(self._cr.dbname).cursor()
self = self.with_env(self.env(cr=cr))
# We want to freeze the cron that kills long running relationship queries...
watchdog_cron = self.sudo().env.ref('product_attribute_lazy.ir_cron_product_attribute_rel_query_watchdog', raise_if_not_found=False)
if watchdog_cron:
try:
with tools.mute_logger('odoo.sql_db'):
self._cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (watchdog_cron.id, ))
except Exception:
_logger.info('Attempt to run indexer aborted, as the query watchdog is already running')
self._cr.rollback()
raise UserError('Attempt to run indexer aborted, as the query watchdog is already running')
# if we could tell that it needs re-indexed....
attributes = self.env['product.attribute'].search([])
attributes._run_indexer(use_new_cursor=use_new_cursor)
except Exception:
_logger.error("Error during product attribute indexer", exc_info=True)
raise
finally:
if use_new_cursor:
try:
self._cr.close()
except Exception:
pass
if attributes:
_logger.warning('Indexer took %s seconds total for %s attributes.' % ((datetime.now()-start_time).seconds, len(attributes)))
return {}
def run_indexer_manual(self):
# intended to be called by server action. Don't allow it to overlap with cron
if not self.exists():
raise UserError('One or more selected Product Attributes are required.')
indexer_cron = self.sudo().env.ref('product_attribute_lazy.ir_cron_product_attribute_indexer')
# Avoid repeated and overlapping index processes
try:
with tools.mute_logger('odoo.sql_db'):
self._cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (indexer_cron.id, ))
except Exception:
_logger.info('Attempt to run indexer aborted, as already running')
self._cr.rollback()
raise UserError('Attempt to run indexer aborted, as already running')
self._run_indexer(True)