mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 17:31:59 +02:00
157 lines
6.2 KiB
Python
157 lines
6.2 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import pytz
|
|
import uuid
|
|
from datetime import datetime, timedelta
|
|
|
|
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.tools.misc import limited_field_access_token
|
|
from odoo.addons.mail.tools.discuss import Store
|
|
|
|
|
|
class MailGuest(models.Model):
|
|
_name = 'mail.guest'
|
|
_description = "Guest"
|
|
_inherit = ["avatar.mixin", "bus.listener.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')
|
|
email = fields.Char()
|
|
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)
|
|
presence_ids = fields.One2many("mail.presence", "guest_id", groups="base.group_system")
|
|
# sudo: mail.guest - can access presence of accessible guest
|
|
im_status = fields.Char("IM Status", compute="_compute_im_status", compute_sudo=True)
|
|
offline_since = fields.Datetime("Offline since", compute="_compute_im_status", compute_sudo=True)
|
|
|
|
@api.depends("presence_ids.status")
|
|
def _compute_im_status(self):
|
|
for guest in self:
|
|
guest.im_status = guest.presence_ids.status or "offline"
|
|
guest.offline_since = (
|
|
guest.presence_ids.last_poll
|
|
if guest.im_status == "offline"
|
|
else None
|
|
)
|
|
|
|
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']):
|
|
assert len(guest) <= 1, "Context guest should be empty or a single record."
|
|
return guest.sudo(False).with_context(guest=guest)
|
|
return self.env['mail.guest']
|
|
|
|
def _get_or_create_guest(self, *, guest_name, country_code, timezone):
|
|
if not (guest := self._get_guest_from_context()):
|
|
guest = self.create(
|
|
{
|
|
"country_id": self.env["res.country"].search([("code", "=", country_code)]).id,
|
|
"lang": get_lang(self.env).code,
|
|
"name": guest_name,
|
|
"timezone": timezone,
|
|
}
|
|
)
|
|
guest._set_auth_cookie()
|
|
return guest.sudo(False)
|
|
|
|
def _get_timezone_from_request(self, request):
|
|
timezone = request.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
|
|
for channel in self.channel_ids:
|
|
Store(bus_channel=channel).add(self, ["avatar_128", "name"]).bus_send()
|
|
Store(bus_channel=self).add(self, ["avatar_128", "name"]).bus_send()
|
|
|
|
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 _get_im_status_access_token(self):
|
|
"""Return a scoped access token for the `im_status` field. The token is used in
|
|
`ir_websocket._prepare_subscribe_data` to grant access to presence channels.
|
|
|
|
:rtype: str
|
|
"""
|
|
self.ensure_one()
|
|
return limited_field_access_token(self, "im_status", scope="mail.presence")
|
|
|
|
def _field_store_repr(self, field_name):
|
|
if field_name == "avatar_128":
|
|
return [
|
|
Store.Attr("avatar_128_access_token", lambda g: g._get_avatar_128_access_token()),
|
|
"write_date",
|
|
]
|
|
if field_name == "im_status":
|
|
return [
|
|
"im_status",
|
|
Store.Attr("im_status_access_token", lambda g: g._get_im_status_access_token()),
|
|
]
|
|
return [field_name]
|
|
|
|
def _to_store_defaults(self, target):
|
|
return ["avatar_128", "im_status", "name"]
|
|
|
|
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.
|
|
|
|
:return: formatted cookie value
|
|
:rtype: str
|
|
"""
|
|
self.ensure_one()
|
|
return f"{self.id}{self._cookie_separator}{self.access_token}"
|