product.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. # coding: utf-8
  2. # © 2016 David BEAL @ Akretion <david.beal@akretion.com>
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import itertools
  5. import logging
  6. from openerp import api, fields, models
  7. _logger = logging.getLogger(__name__)
  8. class ProductProduct(models.Model):
  9. _inherit = 'product.product'
  10. attribute_str = fields.Char(
  11. string='Attribute string', compute='_compute_attribute_str',
  12. store=True, index=True,
  13. help="Store all attribute values. Required to search variants "
  14. "from attributes")
  15. @api.multi
  16. @api.depends('attribute_value_ids', 'active')
  17. def _compute_attribute_str(self):
  18. for prd in self:
  19. if prd.attribute_value_ids:
  20. attrs = [x.name.lower() for x in prd.attribute_value_ids]
  21. prd.attribute_str = u' '.join(attrs)
  22. @api.multi
  23. def _compute_attrib_str_after_attrib_value_change(self):
  24. """ This method is called when you modified attribute value name
  25. on impacted products: in this case you may have a huge data volume
  26. """
  27. sql = ''
  28. product_ids = [x.id for x in self]
  29. self._cr.execute(self._get_product_info_query(), (tuple(product_ids),))
  30. product_infos = self._cr.fetchall()
  31. products_map = {x[0]: x[1] for x in product_infos}
  32. update_statement = "UPDATE product_product SET attribute_str = " \
  33. "%(info)s WHERE id = %(id)s;"
  34. for prd in self:
  35. if products_map.get(prd.id):
  36. # We do not use previous method compute_attribute_str()
  37. # for performance reason: ie 30 000 products to update
  38. # 5 seconds with this method
  39. # or 3 minutes for _compute_attribute_str() method above
  40. query_params = {'info': products_map[prd.id], 'id': prd.id}
  41. sql += self._cr.mogrify(update_statement, query_params)
  42. if sql:
  43. self._cr.execute(sql)
  44. self.env.invalidate_all()
  45. def _get_product_info_query(self):
  46. """ You may customize aggregate string according to your needs """
  47. return """
  48. SELECT re.prod_id as id, lower(string_agg(pa.name, ' ')) as string
  49. FROM product_attribute_value_product_product_rel re
  50. LEFT JOIN product_attribute_value pa ON pa.id = re.att_id
  51. WHERE re.prod_id in %s
  52. GROUP BY 1 """
  53. def search(self, cr, uid, domain, offset=0, limit=None,
  54. order=None, context=None, count=False):
  55. _logger.debug('Initial domain search %s' % domain)
  56. separator = self.pool['ir.config_parameter'].get_param(
  57. cr, uid, 'search.by.attribute.separator', ' ')
  58. domain = self.domain_replacement(domain, 'attribute_str', separator)
  59. return super(ProductProduct, self).search(
  60. cr, uid, domain, offset=offset, limit=limit, order=order,
  61. context=context, count=count)
  62. def domain_replacement(self, domain, field, separator):
  63. """ convert [expr1, expr2, expr3] in [expr1, expr2a, expr2b, expr3]
  64. according to expr => (field, 'ilike', mystring)
  65. """
  66. position = 0
  67. domain_idx = []
  68. for arg in domain:
  69. # we track position of field in domain
  70. if tuple(arg)[0] == field:
  71. domain_idx.append(position)
  72. position += 1
  73. for position in reversed(domain_idx):
  74. modified_domain = True
  75. clauses = self.domain_split(domain[position][2], field, separator)
  76. for clause in clauses:
  77. domain.insert(position, clause)
  78. # we remove initial expression with this field
  79. del domain[position + len(clauses)]
  80. if modified_domain:
  81. _logger.debug('Modified domain attr. %s' % domain)
  82. return domain
  83. def domain_split(self, value, field, separator):
  84. """ convert this string 'first second third' in this list
  85. ['&', '&',
  86. ('myfield', 'like', 'first'),
  87. ('myfield', 'like', 'second'),
  88. ('myfield', 'like', 'third')]
  89. """
  90. words = value.lower().split(separator)
  91. # we create as many expression as words in this field
  92. clauses = [[field, 'like', word] for word in words]
  93. if len(clauses) > 1:
  94. # we need explicit operator '&' to be compatible
  95. # with complex domains using '|' operator
  96. operators = list(itertools.repeat('&', len(clauses) - 1))
  97. clauses = clauses + operators
  98. return clauses