Files
odoo_source/addons/website_livechat/models/website_visitor.py
T
jorv edec9e92c7 [FIX] website_livechat: define auto_init for better installing performance
When installing the website_livechat module, the `_compute_livechat_operator_id`
method is run on every record to update the value for `livechat_operator_id`.
On a large `website_visitor` table this leads to timeouts and livechat installation
stops with a traceback.

We can safely assume that at the moment of installing livechat,
no livechat operator was attributed to any visitor. initializing the value to
a default FALSE seems acceptable.
That way PostgreSQL will not need to touch the rows at all, the operation will
be instantaneous, and the actual values will be lazily initialized when the rows
are modified later.

Example, client database with +7 million `website_visitor` records to run
`_compute_livechat_operator_id` on installation:
- before commit: more than 3 hours (timeout)
- after commit: less than 3 mins

Previous implementation: cc9f59b , c426a45

opw-3095632

rebase + squash :
Switched to using the `create_column` method instead of a raw SQL query.
Added some comments in the `_auto_init` definition.

closes odoo/odoo#108570

X-original-commit: f7eae96e1aeb29a518eddd7dd18267dfc0a39fca
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
2022-12-23 01:41:45 +01:00

122 lines
6.5 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
import json
from odoo import api, Command, fields, models, _
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools.sql import column_exists, create_column
class WebsiteVisitor(models.Model):
_inherit = 'website.visitor'
livechat_operator_id = fields.Many2one('res.partner', compute='_compute_livechat_operator_id', store=True, string='Speaking with', index='btree_not_null')
livechat_operator_name = fields.Char('Operator Name', related="livechat_operator_id.name")
mail_channel_ids = fields.One2many('mail.channel', 'livechat_visitor_id',
string="Visitor's livechat channels", readonly=True)
session_count = fields.Integer('# Sessions', compute="_compute_session_count")
def _auto_init(self):
# Skip the computation of the field `livechat_operator_id` at the module installation
# We can assume no livechat operator attributed to visitor if it was not installed
if not column_exists(self.env.cr, "website_visitor", "livechat_operator_id"):
create_column(self.env.cr, "website_visitor", "livechat_operator_id", "int4")
return super()._auto_init()
@api.depends('mail_channel_ids.livechat_active', 'mail_channel_ids.livechat_operator_id')
def _compute_livechat_operator_id(self):
results = self.env['mail.channel'].search_read(
[('livechat_visitor_id', 'in', self.ids), ('livechat_active', '=', True)],
['livechat_visitor_id', 'livechat_operator_id']
)
visitor_operator_map = {int(result['livechat_visitor_id'][0]): int(result['livechat_operator_id'][0]) for result in results}
for visitor in self:
visitor.livechat_operator_id = visitor_operator_map.get(visitor.id, False)
@api.depends('mail_channel_ids')
def _compute_session_count(self):
sessions = self.env['mail.channel'].search([('livechat_visitor_id', 'in', self.ids)])
session_count = dict.fromkeys(self.ids, 0)
for session in sessions.filtered(lambda c: c.message_ids):
session_count[session.livechat_visitor_id.id] += 1
for visitor in self:
visitor.session_count = session_count.get(visitor.id, 0)
def action_send_chat_request(self):
""" Send a chat request to website_visitor(s).
This creates a chat_request and a mail_channel with livechat active flag.
But for the visitor to get the chat request, the operator still has to speak to the visitor.
The visitor will receive the chat request the next time he navigates to a website page.
(see _handle_webpage_dispatch for next step)"""
# check if visitor is available
unavailable_visitors_count = self.env['mail.channel'].search_count([('livechat_visitor_id', 'in', self.ids), ('livechat_active', '=', True)])
if unavailable_visitors_count:
raise UserError(_('Recipients are not available. Please refresh the page to get latest visitors status.'))
# check if user is available as operator
for website in self.mapped('website_id'):
if not website.channel_id:
raise UserError(_('No Livechat Channel allows you to send a chat request for website %s.', website.name))
self.website_id.channel_id.write({'user_ids': [(4, self.env.user.id)]})
# Create chat_requests and linked mail_channels
mail_channel_vals_list = []
for visitor in self:
operator = self.env.user
country = visitor.country_id
visitor_name = "%s (%s)" % (visitor.display_name, country.name) if country else visitor.display_name
members_to_add = [Command.link(operator.partner_id.id)]
if visitor.partner_id:
members_to_add.append(Command.link(visitor.partner_id.id))
else:
members_to_add.append(Command.link(self.env.ref('base.public_partner').id))
mail_channel_vals_list.append({
'channel_partner_ids': members_to_add,
'livechat_channel_id': visitor.website_id.channel_id.id,
'livechat_operator_id': self.env.user.partner_id.id,
'channel_type': 'livechat',
'country_id': country.id,
'anonymous_name': visitor_name,
'name': ', '.join([visitor_name, operator.livechat_username if operator.livechat_username else operator.name]),
'livechat_visitor_id': visitor.id,
'livechat_active': True,
})
if mail_channel_vals_list:
mail_channels = self.env['mail.channel'].create(mail_channel_vals_list)
# Open empty chatter to allow the operator to start chatting with the visitor.
channel_members = self.env['mail.channel.member'].sudo().search([
('partner_id', '=', self.env.user.partner_id.id),
('channel_id', 'in', mail_channels.ids),
])
channel_members.write({
'fold_state': 'open',
'is_minimized': True,
})
mail_channels_info = mail_channels.channel_info()
notifications = []
for mail_channel_info in mail_channels_info:
notifications.append([operator.partner_id, 'website_livechat.send_chat_request', mail_channel_info])
self.env['bus.bus']._sendmany(notifications)
def _merge_visitor(self, target):
""" Copy sessions of the secondary visitors to the main partner visitor. """
target.mail_channel_ids |= self.mail_channel_ids
self.mail_channel_ids.channel_partner_ids = [
(3, self.env.ref('base.public_partner').id),
(4, target.partner_id.id),
]
return super()._merge_visitor(target)
def _upsert_visitor(self, access_token, force_track_values=None):
visitor_id, upsert = super()._upsert_visitor(access_token, force_track_values=force_track_values)
if upsert == 'inserted':
visitor_sudo = self.sudo().browse(visitor_id)
mail_channel_uuid = json.loads(request.httprequest.cookies.get('im_livechat_session', '{}')).get('uuid')
if mail_channel_uuid:
mail_channel = request.env["mail.channel"].sudo().search([("uuid", "=", mail_channel_uuid)])
mail_channel.write({
'livechat_visitor_id': visitor_sudo.id,
'anonymous_name': visitor_sudo.display_name
})
return visitor_id, upsert