flectra/addons/im_livechat/models/im_livechat_channel.py
2018-01-16 02:34:37 -08:00

253 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import base64
import random
import re
from datetime import datetime, timedelta
from flectra import api, fields, models, modules, tools
class ImLivechatChannel(models.Model):
""" Livechat Channel
Define a communication channel, which can be accessed with 'script_external' (script tag to put on
external website), 'script_internal' (code to be integrated with flectra website) or via 'web_page' link.
It provides rating tools, and access rules for anonymous people.
"""
_name = 'im_livechat.channel'
_description = 'Livechat Channel'
def _default_image(self):
image_path = modules.get_module_resource('im_livechat', 'static/src/img', 'default.png')
return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))
def _default_user_ids(self):
return [(6, 0, [self._uid])]
# attribute fields
name = fields.Char('Name', required=True, help="The name of the channel")
button_text = fields.Char('Text of the Button', default='Have a Question? Chat with us.',
help="Default text displayed on the Livechat Support Button")
default_message = fields.Char('Welcome Message', default='How may I help you?',
help="This is an automated 'welcome' message that your visitor will see when they initiate a new conversation.")
input_placeholder = fields.Char('Chat Input Placeholder')
# computed fields
web_page = fields.Char('Web Page', compute='_compute_web_page_link', store=False, readonly=True,
help="URL to a static page where you client can discuss with the operator of the channel.")
are_you_inside = fields.Boolean(string='Are you inside the matrix?',
compute='_are_you_inside', store=False, readonly=True)
script_external = fields.Text('Script (external)', compute='_compute_script_external', store=False, readonly=True)
nbr_channel = fields.Integer('Number of conversation', compute='_compute_nbr_channel', store=False, readonly=True)
rating_percentage_satisfaction = fields.Integer(
'% Happy', compute='_compute_percentage_satisfaction', store=False, default=-1,
help="Percentage of happy ratings over the past 7 days")
# images fields
image = fields.Binary('Image', default=_default_image, attachment=True,
help="This field holds the image used as photo for the group, limited to 1024x1024px.")
image_medium = fields.Binary('Medium', attachment=True,
help="Medium-sized photo of the group. It is automatically "\
"resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views.")
image_small = fields.Binary('Thumbnail', attachment=True,
help="Small-sized photo of the group. It is automatically "\
"resized as a 64x64px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required.")
# relationnal fields
user_ids = fields.Many2many('res.users', 'im_livechat_channel_im_user', 'channel_id', 'user_id', string='Operators', default=_default_user_ids)
channel_ids = fields.One2many('mail.channel', 'livechat_channel_id', 'Sessions')
rule_ids = fields.One2many('im_livechat.channel.rule', 'channel_id', 'Rules')
@api.one
def _are_you_inside(self):
self.are_you_inside = bool(self.env.uid in [u.id for u in self.user_ids])
@api.multi
def _compute_script_external(self):
view = self.env['ir.model.data'].get_object('im_livechat', 'external_loader')
values = {
"url": self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
"dbname": self._cr.dbname,
}
for record in self:
values["channel_id"] = record.id
record.script_external = view.render(values)
@api.multi
def _compute_web_page_link(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
for record in self:
record.web_page = "%s/im_livechat/support/%i" % (base_url, record.id)
@api.multi
@api.depends('channel_ids')
def _compute_nbr_channel(self):
for record in self:
record.nbr_channel = len(record.channel_ids)
@api.multi
@api.depends('channel_ids.rating_ids')
def _compute_percentage_satisfaction(self):
for record in self:
dt = fields.Datetime.to_string(datetime.utcnow() - timedelta(days=7))
repartition = record.channel_ids.rating_get_grades([('create_date', '>=', dt)])
total = sum(repartition.values())
if total > 0:
happy = repartition['great']
record.rating_percentage_satisfaction = ((happy*100) / total) if happy > 0 else 0
else:
record.rating_percentage_satisfaction = -1
@api.model
def create(self, vals):
tools.image_resize_images(vals)
return super(ImLivechatChannel, self).create(vals)
@api.multi
def write(self, vals):
tools.image_resize_images(vals)
return super(ImLivechatChannel, self).write(vals)
# --------------------------
# Action Methods
# --------------------------
@api.multi
def action_join(self):
self.ensure_one()
return self.write({'user_ids': [(4, self._uid)]})
@api.multi
def action_quit(self):
self.ensure_one()
return self.write({'user_ids': [(3, self._uid)]})
@api.multi
def action_view_rating(self):
""" Action to display the rating relative to the channel, so all rating of the
sessions of the current channel
:returns : the ir.action 'action_view_rating' with the correct domain
"""
self.ensure_one()
action = self.env['ir.actions.act_window'].for_xml_id('im_livechat', 'rating_rating_action_view_livechat_rating')
action['domain'] = [('parent_res_id', '=', self.id), ('parent_res_model', '=', 'im_livechat.channel')]
return action
# --------------------------
# Channel Methods
# --------------------------
@api.multi
def get_available_users(self):
""" get available user of a given channel
:retuns : return the res.users having their im_status online
"""
self.ensure_one()
return self.sudo().user_ids.filtered(lambda user: user.im_status == 'online')
@api.model
def get_mail_channel(self, livechat_channel_id, anonymous_name):
""" Return a mail.channel given a livechat channel. It creates one with a connected operator, or return false otherwise
:param livechat_channel_id : the identifier if the im_livechat.channel
:param anonymous_name : the name of the anonymous person of the channel
:type livechat_channel_id : int
:type anonymous_name : str
:return : channel header
:rtype : dict
"""
# get the avalable user of the channel
users = self.sudo().browse(livechat_channel_id).get_available_users()
if len(users) == 0:
return False
# choose the res.users operator and get its partner id
user = random.choice(users)
operator_partner_id = user.partner_id.id
# partner to add to the mail.channel
channel_partner_to_add = [(4, operator_partner_id)]
if self.env.user and self.env.user.active: # valid session user (not public)
channel_partner_to_add.append((4, self.env.user.partner_id.id))
# create the session, and add the link with the given channel
mail_channel = self.env["mail.channel"].with_context(mail_create_nosubscribe=False).sudo().create({
'channel_partner_ids': channel_partner_to_add,
'livechat_channel_id': livechat_channel_id,
'anonymous_name': anonymous_name,
'channel_type': 'livechat',
'name': ', '.join([anonymous_name, user.name]),
'public': 'private',
'email_send': False,
})
return mail_channel.sudo().with_context(im_livechat_operator_partner_id=operator_partner_id).channel_info()[0]
@api.model
def get_channel_infos(self, channel_id):
channel = self.browse(channel_id)
return {
'button_text': channel.button_text,
'input_placeholder': channel.input_placeholder,
'default_message': channel.default_message,
"channel_name": channel.name,
"channel_id": channel.id,
}
@api.model
def get_livechat_info(self, channel_id, username='Visitor'):
info = {}
info['available'] = len(self.browse(channel_id).get_available_users()) > 0
info['server_url'] = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
if info['available']:
info['options'] = self.sudo().get_channel_infos(channel_id)
info['options']["default_username"] = username
return info
class ImLivechatChannelRule(models.Model):
""" Channel Rules
Rules defining access to the channel (countries, and url matching). It also provide the 'auto pop'
option to open automatically the conversation.
"""
_name = 'im_livechat.channel.rule'
_description = 'Channel Rules'
_order = 'sequence asc'
regex_url = fields.Char('URL Regex',
help="Regular expression specifying the web pages this rule will be applied on.")
action = fields.Selection([('display_button', 'Display the button'), ('auto_popup', 'Auto popup'), ('hide_button', 'Hide the button')],
string='Action', required=True, default='display_button',
help="* 'Display the button' displays the chat button on the pages.\n"\
"* 'Auto popup' displays the button and automatically open the conversation pane.\n"\
"* 'Hide the button' hides the chat button on the pages.")
auto_popup_timer = fields.Integer('Auto popup timer', default=0,
help="Delay (in seconds) to automatically open the conversation window. Note: the selected action must be 'Auto popup' otherwise this parameter will not be taken into account.")
channel_id = fields.Many2one('im_livechat.channel', 'Channel',
help="The channel of the rule")
country_ids = fields.Many2many('res.country', 'im_livechat_channel_country_rel', 'channel_id', 'country_id', 'Country',
help="The rule will only be applied for these countries. Example: if you select 'Belgium' and 'United States' and that you set the action to 'Hide Button', the chat button will be hidden on the specified URL from the visitors located in these 2 countries. This feature requires GeoIP installed on your server.")
sequence = fields.Integer('Matching order', default=10,
help="Given the order to find a matching rule. If 2 rules are matching for the given url/country, the one with the lowest sequence will be chosen.")
def match_rule(self, channel_id, url, country_id=False):
""" determine if a rule of the given channel matches with the given url
:param channel_id : the identifier of the channel_id
:param url : the url to match with a rule
:param country_id : the identifier of the country
:returns the rule that matches the given condition. False otherwise.
:rtype : im_livechat.channel.rule
"""
def _match(rules):
for rule in rules:
if re.search(rule.regex_url, url):
return rule
return False
# first, search the country specific rules (the first match is returned)
if country_id: # don't include the country in the research if geoIP is not installed
domain = [('country_ids', 'in', [country_id]), ('channel_id', '=', channel_id)]
rule = _match(self.search(domain))
if rule:
return rule
# second, fallback on the rules without country
domain = [('country_ids', '=', False), ('channel_id', '=', channel_id)]
return _match(self.search(domain))