mail/models/discuss/mail_guest.py

205 lines
8.1 KiB
Python
Raw Normal View History

2024-05-03 12:40:35 +03:00
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pytz
import uuid
from datetime import datetime, timedelta
from functools import wraps
from inspect import Parameter, signature
import odoo
from odoo.tools import consteq, get_lang
from odoo import _, api, fields, models
from odoo.http import request
from odoo.addons.base.models.res_partner import _tz_get
from odoo.exceptions import UserError
from odoo.addons.bus.models.bus_presence import AWAY_TIMER, DISCONNECTION_TIMER
from odoo.addons.bus.websocket import wsrequest
def add_guest_to_context(func):
""" Decorate a function to extract the guest from the request.
The guest is then available on the context of the current
request.
"""
@wraps(func)
def wrapper(self, *args, **kwargs):
req = request or wsrequest
token = (
req.httprequest.cookies.get(req.env["mail.guest"]._cookie_name, "")
)
guest = req.env["mail.guest"]._get_guest_from_token(token)
if guest and not guest.timezone:
timezone = req.env["mail.guest"]._get_timezone_from_request(req)
if timezone:
guest._update_timezone(timezone)
if guest:
req.update_context(guest=guest)
if hasattr(self, "env"):
self.env.context = {**self.env.context, "guest": guest}
return func(self, *args, **kwargs)
return wrapper
class MailGuest(models.Model):
_name = 'mail.guest'
_description = "Guest"
_inherit = ['avatar.mixin']
_avatar_name_field = "name"
_cookie_name = 'dgid'
_cookie_separator = '|'
@api.model
def _lang_get(self):
return self.env['res.lang'].get_installed()
name = fields.Char(string="Name", required=True)
access_token = fields.Char(string="Access Token", default=lambda self: str(uuid.uuid4()), groups='base.group_system', required=True, readonly=True, copy=False)
country_id = fields.Many2one(string="Country", comodel_name='res.country')
lang = fields.Selection(string="Language", selection=_lang_get)
timezone = fields.Selection(string="Timezone", selection=_tz_get)
channel_ids = fields.Many2many(string="Channels", comodel_name='discuss.channel', relation='discuss_channel_member', column1='guest_id', column2='channel_id', copy=False)
im_status = fields.Char('IM Status', compute='_compute_im_status')
def _compute_im_status(self):
self.env.cr.execute("""
SELECT
guest_id as id,
CASE WHEN age(now() AT TIME ZONE 'UTC', last_poll) > interval %s THEN 'offline'
WHEN age(now() AT TIME ZONE 'UTC', last_presence) > interval %s THEN 'away'
ELSE 'online'
END as status
FROM bus_presence
WHERE guest_id IN %s
""", ("%s seconds" % DISCONNECTION_TIMER, "%s seconds" % AWAY_TIMER, tuple(self.ids)))
res = dict(((status['id'], status['status']) for status in self.env.cr.dictfetchall()))
for guest in self:
guest.im_status = res.get(guest.id, 'offline')
def _get_guest_from_token(self, token=""):
"""Returns the guest record for the given token, if applicable."""
guest = self.env["mail.guest"]
parts = token.split(self._cookie_separator)
if len(parts) == 2:
guest_id, guest_access_token = parts
# sudo: mail.guest: guests need sudo to read their access_token
guest = self.browse(int(guest_id)).sudo().exists()
if not guest or not guest.access_token or not consteq(guest.access_token, guest_access_token):
guest = self.env["mail.guest"]
return guest.sudo(False)
def _get_guest_from_context(self):
"""Returns the current guest record from the context, if applicable."""
guest = self.env.context.get('guest')
if isinstance(guest, self.pool['mail.guest']):
return guest.sudo(False).with_context(guest=guest)
return self.env['mail.guest']
def _get_timezone_from_request(self, request):
timezone = request.httprequest.cookies.get('tz')
return timezone if timezone in pytz.all_timezones else False
def _update_name(self, name):
self.ensure_one()
name = name.strip()
if len(name) < 1:
raise UserError(_("Guest's name cannot be empty."))
if len(name) > 512:
raise UserError(_("Guest's name is too long."))
self.name = name
guest_data = {
'id': self.id,
'name': self.name,
'type': "guest"
}
bus_notifs = [(channel, 'mail.record/insert', {'Persona': guest_data}) for channel in self.channel_ids]
bus_notifs.append((self, 'mail.record/insert', {'Persona': guest_data}))
self.env['bus.bus']._sendmany(bus_notifs)
def _update_timezone(self, timezone):
query = """
UPDATE mail_guest
SET timezone = %s
WHERE id IN (
SELECT id FROM mail_guest WHERE id = %s
FOR NO KEY UPDATE SKIP LOCKED
)
"""
self.env.cr.execute(query, (timezone, self.id))
def _init_messaging(self):
self.ensure_one()
# sudo: res.partner - exposing OdooBot name and id
odoobot = self.env.ref('base.partner_root').sudo()
# sudo: mail.guest - guest reading their own id/name/channels
guest_sudo = self.sudo()
return {
'channels': guest_sudo.channel_ids.sudo(False)._channel_info(),
'companyName': self.env.company.name,
'currentGuest': {
'id': guest_sudo.id,
'name': guest_sudo.name,
'type': "guest",
},
'current_partner': False,
'current_user_id': False,
'current_user_settings': False,
# sudo: ir.config_parameter: safe to check for existence of tenor api key
'hasGifPickerFeature': bool(self.env["ir.config_parameter"].sudo().get_param("discuss.tenor_api_key")),
'hasLinkPreviewFeature': self.env['mail.link.preview']._is_link_preview_enabled(),
'hasMessageTranslationFeature': False,
# sudo: bus.bus: reading non-sensitive last id
'initBusId': self.env['bus.bus'].sudo()._bus_last_id(),
'menu_id': False,
'needaction_inbox_counter': False,
'odoobot': {
'id': odoobot.id,
'name': odoobot.name,
'type': "partner",
},
'shortcodes': [],
'starred_counter': False,
}
def _guest_format(self, fields=None):
if not fields:
fields = {'id': True, 'name': True, 'im_status': True, "write_date": True}
guests_formatted_data = {}
for guest in self:
data = {}
if 'id' in fields:
data['id'] = guest.id
if 'name' in fields:
data['name'] = guest.name
if 'im_status' in fields:
data['im_status'] = guest.im_status
if "write_date" in fields:
data["write_date"] = odoo.fields.Datetime.to_string(guest.write_date)
data['type'] = "guest"
guests_formatted_data[guest] = data
return guests_formatted_data
def _set_auth_cookie(self):
"""Add a cookie to the response to identify the guest. Every route
that expects a guest will make use of it to authenticate the guest
through `add_guest_to_context`.
"""
self.ensure_one()
expiration_date = datetime.now() + timedelta(days=365)
request.future_response.set_cookie(
self._cookie_name,
self._format_auth_cookie(),
httponly=True,
expires=expiration_date,
)
request.update_context(guest=self.sudo(False))
def _format_auth_cookie(self):
"""Format the cookie value for the given guest.
:param guest: guest to format the cookie value for
:return str: formatted cookie value
"""
self.ensure_one()
return f"{self.id}{self._cookie_separator}{self.access_token}"